There are many ways to reassign employees. You, as a programmer or DBA could, for instance, write an update statement to do that. But let’s say that you don’t want to lose your friends at the HR department. They want you to build a real flashy GUI to achieve the very same functionality. Ooops. No more lunch breaks for a week or two, because you planned this task to take only half an hour (the time that writing an update statement takes, if you type very slowly and test it intensively). ADF 11g provides you with two very handy features. First there’s the tree which is not new, but enhanced. Second there’s drag and drop functionality. Here is a way to use those two into a powerful combination.
The initial steps are as usual:
• create a new JDeveloper application
• in the Model project, create Business Components from Tables for table EMPLOYEES and DEPARTMENTS (HR schema)
• in the View project, create a new JSF page;
Now you create a tree. This is a lot more straightforward then it was in any of the previous releases of ADF. You just drop the DepartmentView1 from the datacontrol on the page as a tree. The treebinding editor opens and from there you can add all of the child collections as child nodes.
That is all that it takes to create a fully functional tree. Run the page. Just expand and collapse some nodes to test if the tree is working correctly.
Let’s add the drag and drop functionality. We need to add a collectionDragSource to the tree.
<af:tree id="tree" value="#{bindings.DepartmentsView1.treeModel}" var="node" selectionListener="#{bindings.DepartmentsView1.treeModel.makeCurrent}" rowSelection="single" inlineStyle="height:800px;"> <af:collectionDragSource actions="MOVE" modelName="employee"/> <f:facet name="nodeStamp"> <af:outputText value="#{node}"/> </f:facet> </af:tree>
Because we want to use the same tree as droptarget, we need to add a collectionDropTarget as well. Do that right after the collectionDragSource, but before the <f:facet>.
<af:collectionDropTarget actions="MOVE" modelName="employee" dropListener="#{reAssign.onTreeDrop}"/>
You will notice the dropListener. This is a callback to a method that can handle the drag and drop action. You can create this method yourself, or just copy the code and configure the bean in the config.xml.
<managed-bean> <managed-bean-name>reAssign</managed-bean-name> <managed-bean-class> nl.amis.empdept.view.beans.reAssign </managed-bean-class> <managed-bean-scope>request</managed-bean-scope> </managed-bean>
When you are dragging and dropping, the actual goal is to reassign the dragged employee to the department where you dropped it on. Therefore you need more information than just the employee data that you dragged and dropped. You also need the data were you dropped upon.
So what did you actually drop? In the onTreeDrop method in the reAssign bean you can find the droppedValue by invoking the getData method on the dropEvent.
DataFlavor<RowKeySet> df = DataFlavor.getDataFlavor(RowKeySet.class); RowKeySet droppedValue = dropEvent.getTransferable().getData(df); Object[] keys = droppedValue.toArray(); Key empKey = null; // get the employee which we want to update the department for (int i = 0; i < keys.length; i++) { List list = (List)keys[i]; empKey = (Key)list.get(1);
The object Array keys contains all the dropped rowkeys. The first position of the list contains the departmentId of the dropped employee, and the second entry contains the employeeId. So now you know which employee you dropped.
Let’s find out where you dropped the employee on? The dropEvent class has a getDropSite method which will provide you with the necessary information. This method can return a null value indicating that the drop occurred outside the data portion. If the result is null you will do nothing. Because in this case you know that you are dropping on a tree, you can cast the getDropComponent to a RichTree, and from thereon continue processing.
Object serverRowKey = dropEvent.getDropSite(); if (serverRowKey != null) { RichTree richTree = (RichTree)dropEvent.getDropComponent(); richTree.setRowKey(serverRowKey); int rowIndex = richTree.getRowIndex(); Number departmentNew = (Number)((JUCtrlHierNodeBinding)richTree.getRowData(rowIndex)).getAttribute("DepartmentId"); }
Now you have the Id of the department you dropped in, and also the employee that was dragged. So far, you can actually only drop ON a department node, not IN it. That is because the DepartmentId is only available in the department node. So if you drop ON an employee you have to go 1 level up to find the DepartmentId. If you add the following, then it’s also possible to drop IN a department node:
Number departmentNew = null; if (richTree.getDepth() == 0) { departmentNew = (Number)((JUCtrlHierNodeBinding)richTree.getRowData(rowIndex)).getAttribute("DepartmentId"); } else { departmentNew = (Number)((JUCtrlHierNodeBinding)richTree.getRowData(rowIndex)).getParent().getAttribute("DepartmentId"); }
The only thing left to do is update the employee with the new departmentId.
Get a handle to the applicationModule, find the employee that needs to be updated, set the new departmentId, commit, clear the cache of the viewobject and requery it.
EmpDeptServiceImpl am = getAm(); EmployeesViewRowImpl empRow = (EmployeesViewRowImpl)am.getEmployeesView1().getRow(empKey); empRow.setDepartmentId(departmentNew); am.getDBTransaction().commit(); // requery the employees view am.getDepartmentsView1().clearCache(); am.getDepartmentsView1().executeQuery();
That’s all. It’s not necessary to refresh the tree by invoking addPartialTarget(), because the target component is automatically redrawn in response to a successful drop.
Now you have a working tree with drag and drop functionality.
When you drag an employee from one to another department…
…this employee is reassigned! Cool isn’t it?
It even gets better. In the reassign method the droppedValue is already put into an array (the keys array), so it is possible to iterate all these values.
This means that you can handle multiply employees in one go and even then drag and drop works!
The only thing you have to change in your page is the rowSelection attribute of the tree element. This attribute can have three values: none, single and multiple.
So if you change it into multiple you should be able to select multiple employees and reassign them in one single drop action.
And on top of that, you can reassign employees from different departments to one department in one action.
You can do this by using the control-button and skip the department root nodes.
Select some employees…
…and drop them on a department.
They are all reassigned.
Yes! Now the HR department will throw you a party!
You can download the example workspace here.
Hi Rohit,
I’ve just uploaded an ADF 11.1.1.2 workspace. You can find the link to this workspace at the bottom of this post.
Where do I get the source code from ?
To reply to myself, after a lot of pains… exception happens because of DisplayRow=selected, so _do_not set this property for af:treeTable when you doing drag-and-drop…
Hi,
Excellent example.
As I can see, this is the two-level af:tree. Did you tried to do the same with multi-level ( > 2) af:treeTable ?
Whatever I do, I am stucking with :
java.lang.NullPointerException
at oracle.adfinternal.view.faces.model.binding.RowDataManager.getRowIndex(RowDataManager.java:189)
– Can you post some example regarding drag-and-drop in the multilevel af:treeTable ?
Hi,
There is a bug I believe in the af:tree in 11g in that if you have >2 levelsit does notrender properly. I have created a tree using view objects and 2 view links in between (thus creating a 3 level hierarchy) and when I try to drill down to the 3rd level the expand node icon just flashes when I try to drill down. Have you experienced this and if so I would be very interested in hearing how to work around it – I have curently got it logged as an official bug in Metalink.
thanks
Hi Luc, This is a great functionality and well explained. How do i achieve this same functionality using Toplink. Thanks ansar
I agree that this should be possible through the binding layer.
However, the treeBindingEditor only creates the iteratorBinding for the highest node. In this case the department node. All of the attributes that are present in the tree node defs will be shown in the tree. I don’t want to show the departmentId of the employee in the tree, so I cannot create an attribute binding for that.
Default, there are no usable bindings available to update the departmentId of the employee.
Therefor I think that it’s not straigthforward to handle all this via the binding layer.
Hi,
why do you use the access to the EO to update the model ?
EmpDeptServiceImpl am = getAm();
EmployeesViewRowImpl empRow =
(EmployeesViewRowImpl)am.getEmployeesView1().getRow(empKey);
empRow.setDepartmentId(departmentNew);
am.getDBTransaction().commit();
// requery the employees view
am.getDepartmentsView1().clearCache();
am.getDepartmentsView1().executeQuery();
This can be doen through the binding layer entirely and also would allow you to submit the change for a later commit
Frank
Hi Luc,
: very nice article. great functionality, very well explained and with the occasional tongue-in-cheek thrown in that makes it a nice read as well. Cool stuff, 11g! You make it seem very simple.
best regards, Lucas
Very nice, I will bookmark this entry. I got it working in Flex but not in jdev 11g. Please make some more cool dnd samples.