My task was to offer a kind of folder structure in a tree table and use an easy way to connect nodes with each other. The easiest way is drag & drop because this is intuitive and you can use it fast across many nodes. Alternatively, a use of arrow buttons could be used to move a node, but you often need a lot of clicks.

The first thing we need is a table structure which offers this possibility. As a reference I take the HR schema and the employees table.

HR.EMPLOYEE table

HR.EMPLOYEE table

This table has a self-reference via the ManagerId, which holds an EmployeeId again.

In a tree table this can look like the following:

treeTable structure in ADF source view

treeTable structure in ADF source view

The interesting thing is that I do not use #{node.Email} but #{node.bindings.Email.inputValue} instead. With the help of the name binding part I get access to all binding structures of this value.

The following components are needed to handle drag & drop:

drag source in ADF source view

Drag source in ADF source view

This offers the source and destination of the DropListener to be catched and used in java.

public DnDAction dropListenerTreeTable(DropEvent dropEvent) {
  RichTreeTable tree = (RichTreeTable) dropEvent.getDragComponent();
  String dragNodeVO = null;
  Transferable t = dropEvent.getTransferable();
  DataFlavor<RowKeySet> df=DataFlavor.getDataFlavor(RowKeySet.class,"rowMove");
  RowKeySet rks = t.getData(df);
  Iterator iter = rks.iterator();
  Object dragCurrentRowKey = tree.getRowKey();
  Row dragRow = null;
  if (iter.hasNext()) {
    List key = (List) iter.next();
    tree.setRowKey(key);
    JUCtrlHierNodeBinding rowBinding = (JUCtrlHierNodeBinding) tree.getRowData();
    dragRow = rowBinding.getRow();
    dragNodeVO = dragRow.getStructureDef().getDefName();
  }
  List dropRowKey = (List) dropEvent.getDropSite();
  if (dropRowKey == null) {
    return DnDAction.NONE;
  }
  tree.setRowKey(dropRowKey);
  JUCtrlHierNodeBinding dropNode = (JUCtrlHierNodeBinding) tree.getRowData();
  Row dropRow = dropNode.getRow();
  Integer dropId = (Integer) dropRow.getAttribute("EmployeeId");
  Integer childParentId = (Integer) dragRow.getAttribute("ManagerId");
  if (childParentId != dropId) {
    dragRow.setAttribute("ManagerId", dropId);
    refreshTable();
  } else {
    FacesMessage msg =
    new FacesMessage(FacesMessage.SEVERITY_ERROR, null,
    "Dies existiert unterhalb des Ziels schon!");
    FacesContext.getCurrentInstance().addMessage(null, msg);
    return DnDAction.NONE;
  }
  tree.setRowKey(dragCurrentRowKey);
  AdfFacesContext.getCurrentInstance().addPartialTarget(tree);
  return DnDAction.MOVE;
}

The main idea is to set the ManagerId from the source to the ID of the destination (new parent) and refresh the table.

To clean the connection there exists a red little arrow in every child which executes the following action:

public String removeFromParentTree() {
  Row rw = null;
  RowKeySet rks = tree.getSelectedRowKeys();
  if (rks != null) {
    Iterator rksIterator = rks.iterator();
    CollectionModel model = (CollectionModel) tree.getValue();
    JUCtrlHierBinding treeBinding = (JUCtrlHierBinding) model.getWrappedData();

    while (rksIterator.hasNext()) {
      List nodeKey = (List) rksIterator.next();
      JUCtrlHierNodeBinding node = treeBinding.findNodeByKeyPath(nodeKey);
      if (node != null) {
        rw = node.getRow();
        rw.setAttribute("ManagerId", null);
      }
    }
  }
  refreshTable(); // PPR etc.
  return null;
}

This method cleans the ManagerId  at the corresponding node.

The result can look like this:

treeTable result

treeTable result

If you drag & drop a node to another node then this node is a new child of the destination node with all its children. If you clean the connection then the node with all its children is on the top level without any parent (next to “Stephen King”).

To open all nodes recursively, which can take a long time, depending on the amount of data:

private void expandTreeChildrenNode(RichTreeTable rt, FacesCtrlHierNodeBinding node, List<Key> parentRowKey) {
  ArrayList children = node.getChildren();
  List<Key> rowKey;

  if (children != null) {
    for (int i = 0; i < children.size(); i++) {
      rowKey = new ArrayList<Key>();
      rowKey.addAll(parentRowKey);
      rowKey.add(((FacesCtrlHierNodeBinding) 
      children.get(i)).getRowKey());
      rt.getDisclosedRowKeys().add(rowKey);
      if (((FacesCtrlHierNodeBinding) (children.get(i))).getChildren() == null)
      continue;
      expandTreeChildrenNode(rt, (FacesCtrlHierNodeBinding) (node.getChildren().get(i)), rowKey);
      }
    }
  }

  public String expand() {
  if (tree != null) {
    int rowCount = tree.getRowCount();
    List<Key> rowKey;
    for (int j = 0; j < rowCount; j++) {
      // expand the main nodes
      FacesCtrlHierNodeBinding node = (FacesCtrlHierNodeBinding) 
      tree.getRowData(j);
      rowKey = new ArrayList<Key>();
      rowKey.add(node.getRowKey());
      tree.getDisclosedRowKeys().add(rowKey);
      tree.setRowKey(rowKey);
      // expand the child nodes of the main nodes
      expandTreeChildrenNode(tree, node, rowKey);
    }
  }
  return null;
}

The result is a cool solution to work with drag and drop across tree table nodes.

Wir bleiben für Sie da!

In unserem neusten Blog-Beitrag sprechen wir mit zwei neuen Mitarbeitern, die Mitten in der Corona-Krise bei virtual7 angefangen haben.

Welche Eigenheiten und Herausforderungen einen Jobwechsel in dieser Zeit bedeuten und wie ihr erster Arbeitstag im Home-Office ausgesehen hat, erfahren Sie in diesem Beitrag.