XPAGES DRAG AND DROP WITH DOJO TUTORIAL, PART 5: Wiring the drop event and performing partial updates

This is part 5 of a series on drag and drop with Xpages and Dojo.

Our last installment showed how to drop two data containers on a page that can accept Dojo drag and drop elements between them and set up the data structure to facilitate the processing of these events –namely associating the items with Notes document UNIDs. We also left off with a teaser question that we’ll get to later in this installment.

Collecting our drag & drop data

Now that we’ve instrumented our table rows so that we can semantically determine their relationship to backend Notes documents, we need to collect the rows we’ve acted upon in a UI drag & drop event and the target container that they have been dropped on. The event we’ll be listening to is the target container’s (the table we drop tables rows in to) “onDndDrop” event, an event that is published as part of the Dojo.dnd container class. We subscribe the target container by appending a dojo.connect call (line 5, see code block below) to the initialization function for the table from the previous lesson, passing the connect function the parameters of the subscribing object (the container) the event to listen to and the function to be called on the event:

  1. function initDndTable(tid) {
  2. setUNIDS(tid,true,true,2);
  3. var tVar = new dojo.dnd.Source(tid);
  4. dojo.parser.parse();
  5. dojo.connect(tVar,"onDndDrop", movedata);
  6. }

We then create a function “movedata” that collects the information we need when the event is triggered. Note that the event gets passed the source Dojo object of DnD operation, the nodes being moved and a boolean to indicate whether this is a copy operation or a move as parameters:

  1. function movedata(source, nodes, copy){
  2. if(dojo.dnd.manager().target !== this){ return; }
  3. targetName = dojo.dnd.manager().target.node.id;
  4. tNameArr = targetName.split(":");
  5. targetName = tNameArr[tNameArr.length - 1];
  6. var docsArr = new Array();
  7. for (i=0;i<nodes.length;i++){
  8. docsArr.push(nodes[i].id);
  9. }
  10. var obj = {};
  11. obj.table = targetName;
  12. obj.docs = docsArr;
  13. var tableDataJSON = dojo.toJson(obj);
  14. var stashField = document.getElementById(hiddenFldId);
  15. stashField.value = tableDataJSON;
  16. }//movedata

We’ll finish up this function in a moment, but for now take note that line 2 is to make sure that we’re only executing the code when the container that has subscribed to the event is the target of the DnD operation as the “onDndDrop” event is fired no mater where the nodes are dropped (a peculiarity of the Dojo event topic system.) On Line 3 we get the name of the receiving container from the dojo.dnd.Manager object, as this information is not passed directly to the event. The receiver is useful for us to determine the eventual operation to perform on the backend with the collection of document UNIDs we’re gathering. Note that we’re only interested in the least significant component of targetName, which corresponds to the IDs of our XPages’ data tables. Lastly, lines 10-14 take the collected data and place it in a Javascript object that we serialize to JSON to tuck neatly into our hidden field.

Performing the Partial Submit

Next we need to perform the partial submit to update the Domino backend with our move. Like the R3 days of yore, the simplest way to execute server side code on a page refresh is to embed it in a computed field, which we’ll do. Let’s drop a panel onto our XPage and drop a computed field into the panel. Then move our existing hidden field into the panel as well, taking care to place it after the computed field. Like the old days, order of placement matters for order of execution. (See screenshot.)

fields inside panel

The goal here is to trigger a partial refresh of the panel that contains the computed field and hidden field components on the drop event. Placing the computed field with the ServerSide Javascript (SSJS) logic for updating the Domino backend before the hidden field ensures that the hidden field will be recomputed for browser display properly. The logic for the move operation in SSJS gets put into the computed field’s value:

  1. try {
  2. dragdata = getComponent("inputHidden1").getValue();
  3. if (dragdata == "") return;
  4. ddObject = fromJson(dragdata);
  5. archiveAction = (ddObject.table == "dataTable2") ? "Y" : "N";
  6. for (i=0;i<ddObject.docs.length;i++){
  7. doc = database.getDocumentByUNID(ddObject.docs[i]);
  8. doc.replaceItemValue("Archive",archiveAction);
  9. doc.save(true,true);
  10. }
  11. getComponent("inputHidden1").setValue("")
  12. }
  13. catch (e) {
  14. print(e)
  15. }

Lines 2 & 3 (obviously) halt execution of the script if there’s nothing to do, especially on initial page load. Line 4 reconstitutes the Javascript object from the JSON representation we stashed in the hidden field in our earlier client-side script. In our example the archive action is determined by the target table name. If we dropped documents onto the archive table, we’ll set their “Archive” field value to “Y.” We then cycle through the UNIDs, grab the document objects from the database and perform the updates’ Lastly, we reset the hidden field back to empty. Note that if we didn’t place the computed field before the hidden field, the hidden field would not be updated and the client value after the refresh would still contain the JSON data we stashed there.

Now the only thing we need to do to complete the demo is to perform the partial submit on the drag and drop event. We can programmatically perform the submit from the client using the XSP.PartialRefreshPost method (hat tip: Jeremy Hodge) by appending the code to our movedata function:

  1. XSP.partialRefreshPost(computePanelId, {
  2. onStart: null,
  3. onComplete: null,
  4. onError: null
  5. })

The variable computePanelId refers to the panel that contains our computed field and hidden (stash) field. We’ll add this variable to the script block component’s code in our XPage as follows:

  1. var computePanelId = "#{id:computePanel1}"

Saving and running the completed XPage will result in a drag and drop operation that will update the backend in real-time on the drop event in the client browser!

The UI Gotcha (paginators)

If you’ve been following along, you’ll know that there’s a UI “gotcha” that I’ve alluded to at the end of the last lesson. Our drag and drop functionality is predicated on a Javascript initialization that creates the dojo.dnd containers and preps them for DnD operations. If we use a partially refreshing data component such as a view or data table, however, the table rows only partially refresh, and the dojo containers are not reinitialized with the new data. Thus using your XPage components’ pagers to page through a data container breaks the functionality. In theory, Dojo provides an “onComplete” event to fire off a script after a partial occurs that should allow us to trigger our initializations and reset our DnD functionality. As Domino is computing the partial refresh events for us, however, how to do this is not obvious. Once again, Jeremy Hodge to the rescue with a method to overrode the partial refresh event listener on the page to add a custom onComplete handler that calls our initDndTable function with the table ID we need to refresh taken from the event’s refresh ID parameter:

  1. XSP._inheritedPartialRefresh = XSP._partialRefresh;
  2. XSP._partialRefresh = function(method,form,refreshId,options){
  3. dojo.publish("/XSP/partialRefresh", new Array(method,form,refreshId,options));
  4. this._inheritedPartialRefresh(method,form,refreshId,options);
  5. }
  6. dojo.subscribe("/XSP/partialRefresh", null, function(method,form,refreshId,options) {
  7. if ((refreshId == mainTableId) || (refreshId == archiveTableId)){
  8. if (options.onComplete)
  9. options._inheritedOnComplete = options.onComplete;
  10. options.onComplete = function() {
  11. initDndTable(refreshId);
  12. if (this.inheritedOnComplete)
  13. this.inheritedOnComplete();
  14. }
  15. }
  16. });

The script overrides the XSP._partialrefresh method, taking care to call the inherited functionality when done with out custom code. Line 7 adds the custom onComplete functionality specifically to our two data tables and consists of triggering the initDndTable function with the table id to refresh. Remember in our last lesson when we alluded to the value of breaking out the table initialization into a separate function to be fired for each table? This is the reason. Drop this override code into your script library to complete the demo and generate a fully functioning drag and drop environment with paged data tables. Read Jeremy’s whole post on the XPages Blog for more information on how this works.

The complete source for this demo can be downloaded here.

Learning More

We’ev only touched on the capabilities of Dojo’s DND framework in this series. For example, you may want to learn how to enable or disable the copy feature of your DnD elements or add custom event handlers and triggers. Sitepen’s Dojo Drag and Drop, Part 1: dojo.dnd is an excellent starting point for learning DnD and contains links to other DnD related articles on the sitepen blog. The Dojocampus reference guide on Dojo.dnd is also useful, as is the API reference.

Rate me as an author on Scribnia!

SHAMELESS PLUG: Like what you see? Hire Me! Contact info@critical-masses.com or use the contact page to learn more about engaging Jake Ochs for your development needs.