XPAGES DRAG AND DROP WITH DOJO TUTORIAL, PART 4: dragging between containers and setting the stage for partial (AJAX) updates.

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

Apologies for the long delay between part 3 of this series and part 4. In my defense, I was holding out for release 8.5.1 to see if any new functionality was brought to the table that eased Dojo DND development in XPages. I’d also like to acknowledge Paul Withers’ and Declan Lynch’s help in bouncing ideas around and answering some of my more boneheaded XPages questions. Lastly, Jeremy Hodge’s sage advice on the Dojo and XSP script library helped resolve one last thorny issue that was preventing the publication of this tutorial installment.

Dragging between containers

We ended our last session by properly instrumenting our XPage data table and view controls to serve as Dojo drag and drop containers. After throwing in some Dojo setup code, the containers then receive drag and drop behavioral functionality. Now we’d like to drag and drop between multiple containers. A typical use case would be to drag documents from one view or folder to another. In our example, we’ll be dragging documents from a main view into one that represents an archive. To accomplish this, we embed two containers, in our case data tables attached to views:

  1. Drag two data table components onto the page
  2. Associate the data tables with view data sources.
  3. Right-click inside the data tables to insert/append a column
  4. Drag a computed field into the middle row of the column and bind it to a value in your data source.
  5. Add the Dojo.dnd tags to the view containers by drilling down to the All Properties tab of the data tables’ properties view, setting the rowClasses property to “dojoDndItem” and the styleClass property to “Container.”
  6. Add the Dojo.dnd prerequisite resources:
    1. DojoModule “dojo.dnd.Source”
    2. DojoModule “dojo.parser”
    3. DojoModule “dojo.dnd.Move”
    4. Dojo’s native dnd.css, called using the technique outlined here and in part 2 of this tutorial. Note that if you’re using release 8.5.1, IBM has updated the Dojo library and the reference should be to dojo-1.3.2 instead of dojo-1.1.1.
    5. dnd.css stylesheet (our custom stylesheet from the previous exercises, used to add interactive cues to our drag and drop operation)

So far, we’ve done nothing different than in our previous examples. Now that we’ve got two DND containers to work with though, we’ll be dealing with more complex Javascript so we’ll move most of our code to a script library for code sanity. One neat trick I’ve picked up is to embed an output script block component (new 8.5.1 feature) on the page and create Javascript variables that contain the generated DOM IDs of components we need to reference in the client. In source mode, the script block should look like this:

  1. <xp:scriptBlock id="scriptBlock1">
  2. <xp:this.value><![CDATA[var mainTableId = "#{id:dataTable1}";
  3. var archiveTableId = "#{id:dataTable2}"]]></xp:this.value>
  4. </xp:scriptBlock>

Domino will render the EL {id:dataTable1} as the true DOM ID of the dataTable1 component, which we can use to build our DND containers after the page completes loading. (Hat tip to Matt White via Paul Withers.)

Let’s create a new Javascript code library and create a new function that will take a DOM ID and create a Dojo.dnd source container object from it:

  1. function initDndTable(tid) {
  2. var tVar = new dojo.dnd.Source(tid)
  3. }//initDndTable

We can then call this function twice from another function:

  1. function initDndTables(){
  2. initDndTable(mainTableId);
  3. initDndTable(archiveTableId);
  4. }//initDndTables

with the calling parameters being the global table id variables we’ve defined in the script block on the XPage previously.

(You may be wondering why I’m overcomplicating this with separate functions. In truth, at this stage, there’s no need to break out the initialization of the Dojo.dnd containers, but the time will soon come when the need to initialize a specific container will arise.)

Lastly, we kick off the whole process by instructing Dojo to initialize and parse the tables in its onLoad event thusly:

  1. XSP.addOnLoad(initDndTables);

Notice the use of the XSP object instead of the Dojo (Dojo.addOnLoad) object. The XSP object is IBM’s override of Dojo, with extensions for providing custom XPages functionality. You can browse the uncompressed XSPClientDojo.js file in your NOTESDATA\domino\js\dojo-1.3.2\ibm\xsp\widget\layout directory.

Let’s improve the look of our page a bit by styling the containers. To begin with, put a CSS .container selector in your dnd.css file (or any CSS that you’re referencing) and set it to float: left. You may also have multiple style classes in your StyleClass property, so in addition to having the “container” styleClass for the data tables, add “archive_table” to the styleClass for the second table and set it’s margin-left property to 50px to space it a bit horizontally from the first table. This is what the tail of your referenced CSS file should look like:

  1. .container { float: left; }
  2. .archive_table { margin-left: 50px; }

I also added a thin black border around the data tables. This is what the two table should look like in your browser:
Two containers side by side in a web browser

Done! You now have two data tables associated with Notes views that can interchange rows via drag and drop.

TIP: Unhide new controls (hidden input, output script) available in 8.5.1 on the palette by editing the palette from the Domino Designer preferences in the global Notes preferences dialog. (Hat Tip: Declan Lynch)

Associating nodes with Notes documents

The next hurdle to clear is that we need some way of associating the table rows in the DOM with the backend documents in Notes so that we can perform a drag & drop operation and then instruct the server to update the database with the information reflected in the browser. Let’s do this by appending a column to each of our data tables with hidden HTML fields containing the row documents' Unique IDs. Note that 8.5.1 has a new hidden field control which we will use in the next iinstallment but that does not seem to work with data inside of repeat controls. So, for the time being, we’ll drop a computed field into our column and use Javascript to emit the HTML for the hidden field:

  1. '<input type="hidden" value="' + mainData.getDocument().getUniversalID() + '">'

MainData is the collection name specified in the Data Table’s main property tab. To complete this operation the field’s “escape” property (in the All Properties tab of the field’s properties view) must be set to false so the HTML is passed through unaltered. Additionally, make sure the field’s name or ID is also blanked out and the field’s “disableTheme” property is set to true. Nulling the field’s ID property and disabling the theme prevents Domino from wrapping the field in a span tag -making it that much easier to programmatically reach for later manipulation.

Now that we have data to allow us to associate the view elements with documents, lets take the process one step further and assign the id of each table row, which is our visual representation of a document, the DOM ID of the document’s UNID. We can create a function that will walk a datatable, extract the ID from the column number containing the IDs and assign the ID value to the row’s ID:

  1. function setUNIDS(tableID,hasTitle,hasTH,UNIDRow){
  2. table1 = dojo.byId(tableID);
  3. var childrenIndex = 0;
  4. if (hasTitle) childrenIndex++;
  5. if (hasTH) childrenIndex++;
  6. var tRows = table1.children[childrenIndex].children;
  7. //walk rows
  8. for (var i=0;i<tRows.length;i++){
  9. UNID = tRows[i].children[UNIDRow - 1].children[0].value;
  10. tRows[i].id = UNID;
  11. }
  12. }//setUNIDs

Of note is that the function params include booleans indicating whether the table has a table header row and whether you’ve enabled the table title by filling in the “caption” property of the table. The function will compute the offset accordingly to access the rows containing actual data. Also note that the UNIDRow param is not zero based. In our case, the UNIDs are in row 2 of our view, so that will be the value passed to the function.

if we now add our setUNIDs function to the initDndTable function, we’ll have our table rows’ DOM ID’s set to the documents’ UNIDs for easier manipulation:

  1. function initDndTable(tid) {
  2. setUNIDS(tid,true,true,2);
  3. var tVar = new dojo.dnd.Source(tid)
  4. }//initDndTable

Inspecting the HTML in Firebug will verify that the table row ID’s are now UNIDs:

Row IDs in Firebug

Get the raw XSP code for our example thus far here and the Javascript library here.

In our next installment we'll finish up this example by wiring the drop event to a function that collects the data from the event and submits a partial update request to the server to make the Domino application's data consistent with the browser representation. We'll finish up with a discussion of a nagging UI issue (can you spot it?) and how to fix it.

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.