* More complete implementation of ad tree tab

- Sync of state between tree and form
- Support movement of node using drap and drop
- No search support for tree yet.
This commit is contained in:
Heng Sin Low 2008-07-20 22:16:06 +00:00
parent 5bf2d6533c
commit 4eca299ef8
3 changed files with 420 additions and 44 deletions

View File

@ -0,0 +1,220 @@
/******************************************************************************
* Copyright (C) 2008 Low Heng Sin *
* This program is free software; you can redistribute it and/or modify it *
* under the terms version 2 of the GNU General Public License as published *
* by the Free Software Foundation. This program is distributed in the hope *
* that it will be useful, but WITHOUT ANY WARRANTY; without even the implied *
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
* See the GNU General Public License for more details. *
* You should have received a copy of the GNU General Public License along *
* with this program; if not, write to the Free Software Foundation, Inc., *
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
*****************************************************************************/
package org.adempiere.webui.component;
import org.adempiere.webui.window.FDialog;
import org.compiere.model.MTree;
import org.compiere.model.MTreeNode;
import org.compiere.util.CLogger;
import org.compiere.util.DB;
import org.compiere.util.Trx;
import org.zkoss.zk.ui.event.DropEvent;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.Menuitem;
import org.zkoss.zul.Menupopup;
import org.zkoss.zul.SimpleTreeNode;
import org.zkoss.zul.Tree;
import org.zkoss.zul.Treeitem;
import org.zkoss.zul.Treerow;
/**
*
* @author Low Heng Sin
*
*/
public class ADTreeOnDropListener implements EventListener {
private SimpleTreeModel treeModel;
private MTree mTree;
private int windowNo;
private Tree tree;
private static final CLogger log = CLogger.getCLogger(ADTreeOnDropListener.class);
/**
*
* @param tree
* @param model
* @param mTree
* @param windowNo
*/
public ADTreeOnDropListener(Tree tree, SimpleTreeModel model, MTree mTree, int windowNo) {
this.tree = tree;
this.treeModel = model;
this.mTree = mTree;
this.windowNo = windowNo;
}
/**
* @param event
*/
public void onEvent(Event event) throws Exception {
if (event instanceof DropEvent) {
DropEvent de = (DropEvent) event;
log.fine("Source=" + de.getDragged() + " Target=" + de.getTarget());
if (de.getDragged() != de.getTarget()) {
Treeitem src = (Treeitem) ((Treerow) de.getDragged()).getParent();
Treeitem target = (Treeitem) ((Treerow) de.getTarget()).getParent();
moveNode((SimpleTreeNode)src.getValue(), (SimpleTreeNode)target.getValue());
}
}
}
/**
* Move TreeNode
* @param movingNode The node to be moved
* @param toNode The target node
*/
private void moveNode(SimpleTreeNode movingNode, SimpleTreeNode toNode)
{
log.info(movingNode.toString() + " to " + toNode.toString());
if (movingNode == toNode)
return;
MTreeNode toMNode = (MTreeNode) toNode.getData();
SimpleTreeNode newParent;
int index;
if (!toMNode.isSummary()) // drop on a child node
{
moveNode(movingNode, toNode, false);
}
else // drop on a summary node
{
//prompt user to select insert after or drop into the summary node
int path[] = treeModel.getPath(treeModel.getRoot(), toNode);
Treeitem toItem = tree.renderItemByPath(path);
tree.setSelectedItem(toItem);
Events.sendEvent(tree, new Event(Events.ON_SELECT, tree));
MenuListener listener = new MenuListener(movingNode, toNode);
//TODO: translation
Menupopup popup = new Menupopup();
Menuitem menuItem = new Menuitem("Insert After");
menuItem.setValue("InsertAfter");
menuItem.setParent(popup);
menuItem.addEventListener(Events.ON_CLICK, listener);
menuItem = new Menuitem("Move Into");
menuItem.setValue("MoveInto");
menuItem.setParent(popup);
menuItem.addEventListener(Events.ON_CLICK, listener);
popup.setPage(tree.getPage());
popup.open(toItem.getTreerow());
}
} // moveNode
private void moveNode(SimpleTreeNode movingNode, SimpleTreeNode toNode, boolean moveInto)
{
SimpleTreeNode newParent;
int index;
if (!moveInto)
{
newParent = treeModel.getParent(toNode);
index = newParent.getChildren().indexOf(toNode) + 1; // the next node
}
else // drop on a summary node
{
newParent = toNode;
index = 0; // the first node
}
// remove
SimpleTreeNode oldParent = treeModel.getParent(movingNode);
treeModel.removeNode(movingNode);
// insert
treeModel.addNode(newParent, movingNode, index);
int path[] = treeModel.getPath(treeModel.getRoot(), movingNode);
Treeitem movingItem = tree.renderItemByPath(path);
tree.setSelectedItem(movingItem);
Events.sendEvent(tree, new Event(Events.ON_SELECT, tree));
// *** Save changes to disk
Trx trx = Trx.get (Trx.createTrxName("ADTree"), true);
try
{
int no = 0;
MTreeNode oldMParent = (MTreeNode) oldParent.getData();
for (int i = 0; i < oldParent.getChildCount(); i++)
{
SimpleTreeNode nd = (SimpleTreeNode)oldParent.getChildAt(i);
MTreeNode md = (MTreeNode) nd.getData();
StringBuffer sql = new StringBuffer("UPDATE ");
sql.append(mTree.getNodeTableName())
.append(" SET Parent_ID=").append(oldMParent.getNode_ID())
.append(", SeqNo=").append(i)
.append(", Updated=SysDate")
.append(" WHERE AD_Tree_ID=").append(mTree.getAD_Tree_ID())
.append(" AND Node_ID=").append(md.getNode_ID());
log.fine(sql.toString());
no = DB.executeUpdate(sql.toString(),trx.getTrxName());
}
if (oldParent != newParent)
{
MTreeNode newMParent = (MTreeNode) newParent.getData();
for (int i = 0; i < newParent.getChildCount(); i++)
{
SimpleTreeNode nd = (SimpleTreeNode)newParent.getChildAt(i);
MTreeNode md = (MTreeNode) nd.getData();
StringBuffer sql = new StringBuffer("UPDATE ");
sql.append(mTree.getNodeTableName())
.append(" SET Parent_ID=").append(newMParent.getNode_ID())
.append(", SeqNo=").append(i)
.append(", Updated=SysDate")
.append(" WHERE AD_Tree_ID=").append(mTree.getAD_Tree_ID())
.append(" AND Node_ID=").append(md.getNode_ID());
log.fine(sql.toString());
no = DB.executeUpdate(sql.toString(),trx.getTrxName());
}
}
// COMMIT *********************
trx.commit(true);
}
catch (Exception e)
{
trx.rollback();
FDialog.error(windowNo, tree, "TreeUpdateError", e.getLocalizedMessage());
}
trx.close();
trx = null;
}
class MenuListener implements EventListener {
private SimpleTreeNode movingNode;
private SimpleTreeNode toNode;
MenuListener(SimpleTreeNode movingNode, SimpleTreeNode toNode) {
this.movingNode = movingNode;
this.toNode = toNode;
}
public void onEvent(Event event) throws Exception {
if (Events.ON_CLICK.equals(event.getName()) && event.getTarget() instanceof Menuitem) {
Menuitem menuItem = (Menuitem) event.getTarget();
if ("InsertAfter".equals(menuItem.getValue())) {
moveNode(movingNode, toNode, false);
} else if ("MoveInto".equals(menuItem.getValue())) {
moveNode(movingNode, toNode, true);
}
}
}
}
}

View File

@ -9,34 +9,70 @@
* You should have received a copy of the GNU General Public License along *
* with this program; if not, write to the Free Software Foundation, Inc., *
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
* For the text or an alternative of this public license, you may reach us *
* Posterita Ltd., 3, Draper Avenue, Quatre Bornes, Mauritius *
* or via info@posterita.org or http://www.posterita.org/ *
*****************************************************************************/
package org.adempiere.webui.component;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Level;
import org.compiere.model.MTree;
import org.compiere.model.MTreeNode;
import org.compiere.util.CLogger;
import org.compiere.util.Env;
import org.zkoss.lang.Objects;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.SimpleTreeNode;
import org.zkoss.zul.Tree;
import org.zkoss.zul.Treecell;
import org.zkoss.zul.Treecol;
import org.zkoss.zul.Treecols;
import org.zkoss.zul.Treeitem;
import org.zkoss.zul.TreeitemRenderer;
import org.zkoss.zul.Treerow;
import org.zkoss.zul.event.TreeDataEvent;
/**
*
* @author Low Heng Sin
*
*/
public class SimpleTreeModel extends org.zkoss.zul.SimpleTreeModel implements TreeitemRenderer {
public class SimpleTreeModel extends org.zkoss.zul.SimpleTreeModel implements TreeitemRenderer, EventListener {
private static final CLogger logger = CLogger.getCLogger(SimpleTreeModel.class);
private boolean itemDraggable;
private List<EventListener> onDropListners = new ArrayList<EventListener>();
public SimpleTreeModel(SimpleTreeNode root) {
super(root);
}
public static SimpleTreeModel initADTree(Tree tree, int AD_Tree_ID, int windowNo) {
MTree vTree = new MTree (Env.getCtx(), AD_Tree_ID, false, true, null);
MTreeNode root = vTree.getRoot();
SimpleTreeModel treeModel = SimpleTreeModel.createFrom(root);
treeModel.setItemDraggable(true);
treeModel.addOnDropEventListener(new ADTreeOnDropListener(tree, treeModel, vTree, windowNo));
Treecols treeCols = new Treecols();
tree.appendChild(treeCols);
Treecol treeCol = new Treecol();
treeCols.appendChild(treeCol);
tree.setPageSize(-1);
try {
tree.setTreeitemRenderer(treeModel);
tree.setModel(treeModel);
} catch (Exception e) {
logger.log(Level.SEVERE, "Failed to setup tree");
}
return treeModel;
}
public static SimpleTreeModel createFrom(MTreeNode root) {
SimpleTreeModel model = null;
Enumeration nodeEnum = root.children();
@ -70,8 +106,15 @@ public class SimpleTreeModel extends org.zkoss.zul.SimpleTreeModel implements Tr
Treecell tc = new Treecell(Objects.toString(node));
Treerow tr = null;
if(ti.getTreerow()==null){
tr = new Treerow();
tr = new Treerow();
tr.setParent(ti);
if (isItemDraggable()) {
tr.setDraggable("true");
}
if (!onDropListners.isEmpty()) {
tr.setDroppable("true");
tr.addEventListener(Events.ON_DROP, this);
}
}else{
tr = ti.getTreerow();
tr.getChildren().clear();
@ -81,4 +124,99 @@ public class SimpleTreeModel extends org.zkoss.zul.SimpleTreeModel implements Tr
ti.setValue(node);
}
/**
* Add to root
* @param newNode
*/
public void addNode(SimpleTreeNode newNode) {
SimpleTreeNode root = (SimpleTreeNode) getRoot();
root.getChildren().add(newNode);
fireEvent(root, root.getChildCount() - 1, root.getChildCount() - 1, TreeDataEvent.INTERVAL_ADDED);
}
@Override
public SimpleTreeNode getRoot() {
return (SimpleTreeNode) super.getRoot();
}
@Override
public SimpleTreeNode getChild(Object parent, int index) {
return (SimpleTreeNode) super.getChild(parent, index);
}
public void removeNode(SimpleTreeNode treeNode) {
int path[] = this.getPath(getRoot(), treeNode);
if (path != null && path.length > 0) {
SimpleTreeNode parentNode = getRoot();
int index = path.length - 1;
for (int i = 0; i < index; i++) {
parentNode = getChild(parentNode, path[i]);
}
parentNode.getChildren().remove(path[index]);
fireEvent(parentNode, path[index], path[index], TreeDataEvent.INTERVAL_REMOVED);
}
}
public void setItemDraggable(boolean b) {
itemDraggable = b;
}
public boolean isItemDraggable() {
return itemDraggable;
}
public void addOnDropEventListener(EventListener listener) {
onDropListners.add(listener);
}
public void onEvent(Event event) throws Exception {
if (Events.ON_DROP.equals(event.getName())) {
for (EventListener listener : onDropListners) {
listener.onEvent(event);
}
}
}
public SimpleTreeNode getParent(SimpleTreeNode treeNode) {
int path[] = this.getPath(getRoot(), treeNode);
if (path != null && path.length > 0) {
SimpleTreeNode parentNode = getRoot();
int index = path.length - 1;
for (int i = 0; i < index; i++) {
parentNode = getChild(parentNode, path[i]);
}
return parentNode;
}
return null;
}
public void addNode(SimpleTreeNode newParent, SimpleTreeNode newNode,
int index) {
newParent.getChildren().add(index, newNode);
fireEvent(newParent, index, index, TreeDataEvent.INTERVAL_ADDED);
}
public SimpleTreeNode find(SimpleTreeNode fromNode, int recordId) {
if (fromNode == null)
fromNode = getRoot();
MTreeNode data = (MTreeNode) fromNode.getData();
if (data.getNode_ID() == recordId)
return fromNode;
if (isLeaf(fromNode))
return null;
int cnt = getChildCount(fromNode);
for(int i = 0; i < cnt; i++ ) {
SimpleTreeNode child = getChild(fromNode, i);
SimpleTreeNode treeNode = find(child, recordId);
if (treeNode != null)
return treeNode;
}
return null;
}
}

View File

@ -74,8 +74,6 @@ import org.zkoss.zul.Separator;
import org.zkoss.zul.SimpleTreeNode;
import org.zkoss.zul.Toolbarbutton;
import org.zkoss.zul.Tree;
import org.zkoss.zul.Treecol;
import org.zkoss.zul.Treecols;
import org.zkoss.zul.Treeitem;
/**
@ -394,21 +392,7 @@ DataStatusListener, ValueChangeListener, IADTabpanel
if (gridTab.isTreeTab() && tree != null) {
int AD_Tree_ID = MTree.getDefaultAD_Tree_ID (
Env.getAD_Client_ID(Env.getCtx()), gridTab.getKeyColumnName());
MTree vTree = new MTree (Env.getCtx(), AD_Tree_ID, false, true, null);
MTreeNode root = vTree.getRoot();
SimpleTreeModel treeModel = SimpleTreeModel.createFrom(root);
Treecols treeCols = new Treecols();
tree.appendChild(treeCols);
Treecol treeCol = new Treecol();
treeCols.appendChild(treeCol);
tree.setPageSize(-1);
try {
tree.setTreeitemRenderer(treeModel);
tree.setModel(treeModel);
} catch (Exception e) {
logger.log(Level.SEVERE, "Failed to setup tree");
}
SimpleTreeModel.initADTree(tree, AD_Tree_ID, windowNo);
}
}
@ -689,7 +673,7 @@ DataStatusListener, ValueChangeListener, IADTabpanel
public void dataStatusChanged(DataStatusEvent e)
{
//TODO: ignore non-ui thread event for now
//ignore background event
if (Executions.getCurrent() == null) return;
int col = e.getChangedColumn();
@ -730,12 +714,60 @@ DataStatusListener, ValueChangeListener, IADTabpanel
//sync tree
if (tree != null) {
setSelectedNode(gridTab.getRecord_ID());
if ("Deleted".equalsIgnoreCase(e.getAD_Message()))
if (e.Record_ID != null
&& e.Record_ID instanceof Integer
&& ((Integer)e.Record_ID != gridTab.getRecord_ID()))
deleteNode((Integer)e.Record_ID);
else
setSelectedNode(gridTab.getRecord_ID());
else
setSelectedNode(gridTab.getRecord_ID());
}
}
private void setSelectedNode(int recordId) {
if (recordId < 0) return;
private void deleteNode(int recordId) {
if (recordId <= 0) return;
SimpleTreeModel model = (SimpleTreeModel) tree.getModel();
if (tree.getSelectedItem() != null) {
SimpleTreeNode treeNode = (SimpleTreeNode) tree.getSelectedItem().getValue();
MTreeNode data = (MTreeNode) treeNode.getData();
if (data.getNode_ID() == recordId) {
model.removeNode(treeNode);
return;
}
}
SimpleTreeNode treeNode = model.find(null, recordId);
if (treeNode != null) {
model.removeNode(treeNode);
}
}
private void addNewNode() {
if (gridTab.getRecord_ID() > 0) {
String name = (String)gridTab.getValue("Name");
String description = (String)gridTab.getValue("Description");
boolean summary = gridTab.getValueAsBoolean("IsSummary");
String imageIndicator = (String)gridTab.getValue("Action"); // Menu - Action
//
SimpleTreeModel model = (SimpleTreeModel) tree.getModel();
SimpleTreeNode treeNode = model.getRoot();
MTreeNode root = (MTreeNode) treeNode.getData();
MTreeNode node = new MTreeNode (gridTab.getRecord_ID(), 0, name, description,
root.getNode_ID(), summary, imageIndicator, false, null);
SimpleTreeNode newNode = new SimpleTreeNode(node, new ArrayList());
model.addNode(newNode);
int[] path = model.getPath(model.getRoot(), newNode);
Treeitem ti = tree.renderItemByPath(path);
tree.setSelectedItem(ti);
}
}
private void setSelectedNode(int recordId) {
if (recordId <= 0) return;
if (tree.getSelectedItem() != null) {
SimpleTreeNode treeNode = (SimpleTreeNode) tree.getSelectedItem().getValue();
@ -744,31 +776,16 @@ DataStatusListener, ValueChangeListener, IADTabpanel
}
SimpleTreeModel model = (SimpleTreeModel) tree.getModel();
SimpleTreeNode root = (SimpleTreeNode) model.getRoot();
SimpleTreeNode treeNode = find(model, root, recordId);
SimpleTreeNode treeNode = model.find(null, recordId);
if (treeNode != null) {
int[] path = model.getPath(model.getRoot(), treeNode);
Treeitem ti = tree.renderItemByPath(path);
tree.setSelectedItem(ti);
} else {
addNewNode();
}
}
private SimpleTreeNode find(SimpleTreeModel model, SimpleTreeNode root, int recordId) {
MTreeNode data = (MTreeNode) root.getData();
if (data.getNode_ID() == recordId)
return root;
if (model.isLeaf(root))
return null;
int cnt = model.getChildCount(root);
for(int i = 0; i < cnt; i++ ) {
SimpleTreeNode child = (SimpleTreeNode) model.getChild(root, i);
SimpleTreeNode treeNode = find(model, child, recordId);
if (treeNode != null)
return treeNode;
}
return null;
}
public void valueChange(ValueChangeEvent e)
{
if (gridTab.isProcessed()) // only active records
@ -847,3 +864,4 @@ DataStatusListener, ValueChangeListener, IADTabpanel
}
}