HTML5 drag and drop demonstration in an XPage

I saw an article on HTML5 drag and drop and wondered how hard it would be in XPages.

Demonstration

Here is a link to the working demo of HTML5 drag and drop in an XPage – this is FF and Chrome only as drag/drop is not supported until IE10

How does it work?

I created an XPage and added a simple data table

Data Table
XPages Data Table

Going off the text in the article I tried to add the events to the Fruit label column – unfortunately you cannot add custom attributes to an xp:text

Cannot add custom attributes to an <xp:text
Cannot add custom attributes to an <xp:text

So once again – back to using dojo selectors to get what we want.

The HTML generated by the XPage creates all the fruit labels (Oranges, Bananas, Apples) with an id=blah:_1blah ending in “someFruit”

<tr><td class="xspColumn"><span id="view:_id1:_id2:_id31:viewPanel1:0:column3:someFruit" class="xspTextComputedField">Oranges</span></td>
<td class="xspColumn"><span id="view:_id1:_id2:_id31:viewPanel1:0:column1:computedField2" class="xspTextComputedField">2.0</span></td>
<td class="xspColumn"><span id="view:_id1:_id2:_id31:viewPanel1:0:column2:computedField3" class="xspTextComputedField">4</span></td>
</tr>
<tr><td class="xspColumn"><span id="view:_id1:_id2:_id31:viewPanel1:1:column3:someFruit" class="xspTextComputedField">Bananas</span></td>
<td class="xspColumn"><span id="view:_id1:_id2:_id31:viewPanel1:1:column1:computedField2" class="xspTextComputedField">1.0</span></td>
<td class="xspColumn"><span id="view:_id1:_id2:_id31:viewPanel1:1:column2:computedField3" class="xspTextComputedField">2</span></td>
</tr>

We can get a handle on these labels using dojo.query(“[id$=someFruit]”) which will get all elements with an id attribute ending in “someFruit”

Once we have all of those elements we can add a draggable and ondragstart attribute to them easily using dojo.attr

			dojo.query("[id$=someFruit]").forEach( function(node){
				 dojo.attr(node, "draggable", "true");
				 dojo.attr(node, "ondragstart", "dragIt(event);");
			});

Update – Thanks to Tim Tripcony for pointing this out
If you are using 8.5.3 you can add the custom attributes to the field in the designer client. This removes the need to use the dojo.query

<xp:text escape="true" id="someFruit" value="#{colname1.Fruit}" style="color:rgb(0,0,255)">
	<xp:this.attrs>
		<xp:attr name="ondragstart" value="dragIt(event);"></xp:attr>
		<xp:attr name="draggable" value="true"></xp:attr>
	</xp:this.attrs>
</xp:text>

Then we just need somewhere to drop them…
I added the same DIV elements from the example to the XPage

				<xp:td>
					<div id="place1" ondrop="dropIt(event);"
						ondragover="event.preventDefault();">
					</div>
				</xp:td>
				<xp:td>
					<div id="place2" ondrop="dropIt(event, true);"
						ondragover="event.preventDefault();">
					</div>
				</xp:td>

and added the javascript functions to a scriptblock at the bottom of the page

	//function called when drag starts
		function dragIt(theEvent) {
			//tell the browser what to drag
			theEvent.dataTransfer.setData("Text", theEvent.target.id);
		}

		//function called when element drops
		function dropIt(theEvent, keepMe) {
			//get a reference to the element being dragged
			var theData = theEvent.dataTransfer.getData("Text");
			//get the element
			var theDraggedElement = document.getElementById(theData);

			//add it to the drop element
			if (keepMe){
				//Add a clone of the element to the field - rather than move it
				var newObj=dojo.clone(theDraggedElement)
				theEvent.target.appendChild(newObj);
			} else {
				theEvent.target.appendChild(theDraggedElement);
			}
			//Add a new line for visual line up
			var theBR=document.createElement("br")
			theEvent.target.appendChild(theBR);
			//instruct the browser to allow the drop
			theEvent.preventDefault();
		}

I changed the example slightly to add a clone capability rather than just drag and drop

Drag and Drop example 1
Drag and Drop example 1 (Move)

Dragging to the first box “moves” the label

Drag and Drop example 2 (Clone)
Drag and Drop example 2 (Clone)

Dragging the label to the second box create a copy of it using dojo.clone(node)

Conclusion

This is merely a prototype but demonstrates nicely the new and exiting capabilities of HTML5 without having the need for a dojo or jQuery library to do the drag/drop for you

Demonstration (again)

Here is a link to the working demo of HTML5 drag and drop in an XPage

Don’t believe the HTML – timing is everything in XPages CSJS

So having made v2.0 of my TypeAhead control I am rapidly moving towards the next iteration making it easier to implement and not have to add functions to the fields in the designer. This becomes especially arduous if you have multiple TypeAheads in the same form and you want to add this capability to all of them.

The plan is to use a jQuery or dojo selector to get a handle on all the TypeAhead fields on the form and add the necessary function calls to each of them in one simple script library. (Because this is currently a capability without jQuery I am sticking with dojo, more verbose as it is)

So I created multiple TypeAhead fields (Copy/Paste) on the same XPage and looked at HTML to find out the selector I would need to create. I right click on the web page and view source and the out of the box HTML looks like this (pretty simple right..?):

<span
	id="view:_id1:_id2:_id30:_id43"
	dojoType="ibm.xsp.widget.layout.data.TypeAheadReadStore"
	jsId="view__id1__id2__id30__id43" mode="full">
</span>
<input type="text"
	id="view:_id1:_id2:_id30:inputText1"
	name="view:_id1:_id2:_id30:inputText1"
	class="xspInputFieldEditBox"
	dojoType="ibm.xsp.widget.layout.TypeAhead"
	store="view__id1__id2__id30__id43">
<br>
<span
	id="view:_id1:_id2:_id30:_id48"
	dojoType="ibm.xsp.widget.layout.data.TypeAheadReadStore"
	jsId="view__id1__id2__id30__id48" mode="full">
</span>
<input type="text"
	id="view:_id1:_id2:_id30:inputText2"
	name="view:_id1:_id2:_id30:inputText2"
	class="xspInputFieldEditBox"
	dojoType="ibm.xsp.widget.layout.TypeAhead"
	store="view__id1__id2__id30__id48">

So foolishly I thought – we need a simple attribute selector which will select attribute dojoType is like TypeAhead.

(I always start with changing the CSS as an easy indicator that I have a handle on the element)

	dojo.query('input[dojoType$=TypeAhead]').forEach(function(node){
   		dojo.style(node, {"border" : "2px solid red"})
 	 });

The Problem

I get a big fat nothing..

The most boring picture in blog history
Probably the most boring picture in blog history

So then I pulled up firebug to make sure I wasn’t being stupid, and sure enough I was…..it is not like I have already written about this before or anything….In firebug the actual DOM looks like this:

Right Click on the field and view in firebug
Right Click on the field and Inspect Element with firebug

 

Looking at the DOM within Firebug reveals Marky's stupidity
Looking at the DOM within Firebug reveals Marky's stupidity

Oh DUH – it is a dojo widget (remember) and once the page is loaded, dojo does some major DOM manipulation to create the functionality. This is actually a really nice demonstration of how XPages and dojo work in tandem. XPages creates some fairly innocuous HTML and dojo tranforms it into considerably more complex and voluminous HTML.

So looking at the DOM we have a problem because the final field which is created (that I want to get a handle on) does not have anything indicating that it is a TypeAhead and how can I “select” this out without adding client side events in the XPage….

The Answer

The trick is to execute or “selector” before the dojo widget takes over and messes up the DOM. Instead of adding the code to the onClientLoad event (which is also when the dojo widget magic is triggered) we need to add it to a script block inside of the HTML – before the page has finished loading.

This is a subtle yet important timing detail during the rendering of HTML pages. You can add a script tag to an HTML page and have it execute before the page is rendered to the user, but it can only take action on what has already been “loaded”…this is also why dojo and jQuery are usually triggered when the HTML document is finally loaded – to make sure all the necessary fields are “loaded” on the page.

In this case we are going to add the script block right at the end of the XPage to make sure that everything we are looking for is “loaded” before we act on it. The script block is added at the bottom of the XPage after all the fields. This will execute before the onClientLoad event.

Adding an in-line script block at the end of the XPage

NOTE

I am being very specific in the selector to only select INPUT (input) with Attribute dojoType ([dojoType$]) which ends in TypeAhead – see here for more information on dojo selectors

And looking at the Sourcecode you can see the script is the last thing on the page before closing </the xp:view>.

	<xp:scriptBlock id="scriptBlock1">
		<xp:this.value>
		<![CDATA[
			dojo.query('input[dojoType$=TypeAhead]').forEach(function(node, index, arr){
   				dojo.style(node, {"border" : "2px solid red"})
 			 });
		]]>
		</xp:this.value>
	</xp:scriptBlock>
	</xp:view>

This time we get a much more satisfactory result:

Selecting the elements before they are manipulated by dojo
Selecting the elements before they are manipulated by dojo

 

But wait – there’s more to this that meets the eye….

What is also interesting to note is that the styling applied to the INPUT field before the dojo widget has it’s way with the HTML is retained….but it is NOT applied to the INPUT anymore..not even close…..

Don't trust the HTML

 

Because of this, I am not sure at this point if I am going to be able to add the timing handlers for the TypeAhead – it will be fun finding out though, watch this space….