Friday, November 27, 2015

Dragging Drop with Primefaces

Primefaces was a new JSF library for me, but after using such JSF libraries as Richfaces and Tomahawk I quite quickly found it much more user friendly than the ones. My first complicated project with Primefaces was payment plan page. The basic schema of the page was defined as having two columns, where the left column is Payment Groups  and the right column is a Package Inventory.

When a user could copy package from the right side to the left side and thereby to attach the particular package to any chosen group.  The action of copy would be done using Primefaces's Drag-Drop components.

Here the code for the right side including the draggable tag.

<p:dataTable var="package" value="#{paymentGroupBean.packageInventoryModel.items}id="inventoryList"
    emptyMessage="No packages found with given criteria" filteredValue="#{paymentGroupBean.filteredPackages}"             scrollable="true" scrollHeight="600" >
    <p:column headerText="Filter:" width="10%">
        <h:outputText id="dragIcon" styleClass="ui-icon ui-icon-arrow-4" style="text-align:center" />
        <p:draggable for="dragIcon" revert="true" helper="clone" cursor="move"/>
    </p:column>
    <p:column filterBy="#{package.aliasDescription}filterMatchMode="contains" width="90%" >
        <h:outputText value="(#{package.aliasId})#{package.alias}/>
     </p:column>
</p:dataTable>

In the snippet of the above code there is a fragment:

<p:draggable for="dragIcon" revert="true" helper="clone" cursor="move"/> 

That fragment is in charge of to enable for users to perform the dragging of packages from the right side to the left side.

The droppable side:
<p:dataList id="paymentGroups" value="#{paymentGroupBean.paymentGroupModel.items}type="definition"    var="group"  varStatus="index" rowIndexVar="row" >
    <p:fieldset id="payment_groups_fieldset" >
     ...
     </p:fieldset>
<p:droppable for="payment_groups_fieldset" tolerance="pointer" activeStyleClass="ui-state-highlight"
         datasource=":formId:inventoryList"   onDrop="handleDrop">
    <p:ajax listener="#{paymentGroupBean.onPackageDrop}update=":formId:scrollPanelLeft :formId:inventoryList :formId:growl" />
</p:droppable>

</p:dataList>

I've defined all the area of fieldset to be as a droppable area by binding the fieldset's id to primeface droppable's tag. The performance beyond of dragging drop action implemented by function paymentGroupBean.onPackageDrop.

public void onPackageDrop(DragDropEvent ddEvent) {
    String draggedId = ddEvent.getDragId(); //Client id of dragged component         String droppedId = ddEvent.getDropId(); //Client id of dropped component   
    String droppedIdStr = extractNumber(droppedId);
    Integer indexForPaymentGroup = Integer.parseInt(droppedIdStr);
    List dropItems = droppableModel.getItems();
    UIPaymentGroup paymentGroupMap = dropItems.get(indexForPaymentGroup);
    Long paymentGroupId = paymentGroupMap.groupId;
    UIPaymentPackage paymentPackage = (UIPaymentPackage)ddEvent.getData();
    boolean result = paymentService.
    uiAddPaymentPlanToGroupByAlias(paymentGroupId,paymentPackage.aliasId);
    droppableModel.load();
}


Contrary to examples from the book where there is only one droppable area, in that case there is a list of droppable areas are placed into dataList with id "paymentGroup", therefore while the dropping action we should to define the destined droppable area. I do this by the following steps:

1. Getting droppedId
String droppedId = ddEvent.getDropId();

2. The drooppedId  I got as a html id element such as "payment_groups:2:payment_groups_fieldset" I parse it and extract numeric value to get a row number
String droppedIdStr = extractNumber(droppedId);
3.Then convert it to int
Integer indexForPaymentGroup = Integer.parseInt(droppedIdStr);
4.Then I find the selected group into the list of group items
UIPaymentGroup paymentGroupMap = dropItems.get(indexForPaymentGroup);
5. Now after I've got the chosen payment group I can use paymentService API to call DB function,
after that in the case of success We call   droppableModel.load() method to reload data with updated data and update our web page.