Jeremy Hodge, Feb 20, 2010 9:07:02 PM
One of the difficulties I have faced when writing user-friendly UI's using XPages is the limited amount of "data" I can embed in a generated XPage for consumption by client side scripts. Quite often I find myself wanting to do an dojo.xhrPost() to a domino agent to update the state of a document in the background, or programmatically post a partial refresh back to the server with programmatically contrived data, based on the user's actions, or the state of a dataset client-side. Quite often that action, or the target of that action is dependant upon one or more pieces of data that is not normally located in the DOM.
The typical way to embed this data in the document is through the use of the hidden input ( in XPages terms.) When dealing with small amounts of data, this is fine, but what about more complex data sets, or large data sets. For example a list generated by an repeat control () can generate a large amount of hidden inputs, and you are still faced with the dilemma of how to programmatically determine which hidden input belongs to which UI element.
The solution is rather simple. We can embed our own contextual data tags directly into the HTML! Since browsers grew up and lived through the browser wars, when standards were something to be scoffed at, and the way to win user base was to have more cool tags than your competitor (
To do this, you go to the code of your XPage, and just type in the new contextual data tag. For example, lets say we are dealing with a repeat control, and we want to get the UNID of each document that is rendered by the repeat. To do this we can code the following:
[ ... other Xpages controls go here ... ]
Now, when a user interacts with your "myPanel" for example, in a client-side onClick() event, you can easily retrieve the UNID for this document with the following code:
var unid = dojo.attr(dojo.query('UNID', this.node)[0], 'value');
This code has two important dojo functions. First the query function, which allows you to use almost all CSS3 selectors to return a nodelist (an array of HTML nodes). The first argument to this function is the CSS selector used to located your nodes. In this case, we are asking for all UNID tags. This is a VERY powerful function. I would recommend that you study and learn its power. The second argument is the "root node" at which you want to start your search. If you omit this argument, the entire DOM is searched. If you include a Node, only descendant nodes of that Node will be searched. (Again this is very powerful. For example, XPages developers can use this to locate nodes based on the element's programmed ID, instead of the full client side-id by using the CSS3 selector [id$="myPanel"])
The second function is dojo.attr which returns the value of the attribute specified in argument 2, from the node specified in argument 1.
While this example is over-simplified, you can imagine the powerful things you could put this to use in. For example, off the top of my head, i can think of using contextual data tags to:
- pass information that may be required for display in tooltips, popups, or other display-only interfaces, without having to pass all the markup associated with it (especially when it can be generated client-side, then discarded
- passing identifying data, such as UNIDs, to use in ajax calls back to the server to simulate RESTful API/CRUD calls.
- And, since you can nest the tags, you can give the data relational meaning (think embedded XML)
Happy Coding!
Hey Keith ...
The storage data in this method is akin to XML, however it really isn't actual XML in that you really couldn't easily bind it to XML data aware objects, traverse it with XPath, etc. And to prevent data from appearing on the page to the user, I am strictly using attributes, which can be a little bit limiting in what you can transmit, but with a little bit of ingenuity, usually you can get away with almost anything.
Anyway, on with an example. I'll frame this in reference to your calendar project as I believe that is the solution you are looking at implementing this for.
What I would do is create a <div class="contextual"> ... </div> with the css class definition I listed in my comment post above on Mar 5. Then within that div, place your contextual data. for example, in the XPage I might structure it like this.
<div class="contextual" id="contextual">
<xp:repeat rows="999" var="repeatVar" indexVar="repeatIndex" value="#{datasource}">
<calendarentry unid="#{repeatVar.getUniversalId()}">
<date value="#{repeatVar.dateColumn}" />
<time value="#{repeatVar.timeColumn}" />
<duration value="#{repeatVar.durationColumn}" />
<title value="#{repeatVar.titleColumn}" />
</calendarentry>
</xp:repeat>
</div>
That should give you a hidden div with an XML-like structure representing each of your calendar entries. Then create a a client-side javascript library, with the the following code and include it on your XPage.
XSP.addOnLoad(function() {
// the following lines will retrieve a nodelist (array of nodes) of each of your calendar entries
var calEntries = dojo.query('calendarentry', dojo.byId('contextual')); // iterate through each node entry to process your calendar entry
for (i=0;i<calEntries.length;i++) {
// retrieve the UNID attribute of the calendarEntry tag
var nodeUNID = dojo.attr(calEntries[i], 'UNID');
// retrieve the Date from the value attribute of the date tag below this calendar entry
var entryDate = dojo.attr(dojo.query('date', calEntries[i])[0], 'value');
// retrieve the Time from the value attribute of the time tag below this calendar entry
var entryTime = dojo.attr(dojo.query('time', calEntries[i])[0], 'value');
// retrieve the Duration from the value attribute of the duration tag below this calendar entry
var entryDuration = dojo.attr(dojo.query('duration', calEntries[i])[0], 'value');
// retrieve the Title from the value attribute of the title tag below this calendar entry
var entryTitle = dojo.attr(dojo.query('title', calEntries[i])[0], 'value');
// Do what you need to draw the calendar entries here now that you know
// entryDate, entryTime, entryDuration, entryTitle, and nodeUNID
}
})
You'll need to tweak that code to adjust to your data source, insert your code for dojox.gfx, etc, but it should get you all the data you need to do so client side. Let me know if you have anymore questions.
Great post, however I'm just starting to delve into dojo and XML and was wondering if you could provide an example of storing the data as XML and then retrieving that data using dojo.attr()?
If you have to be strict HTML compliant, either through a strict doctype or a validation service, then yes, using your own tags will cause you to fail validation, or can/will trigger quirks mode. If you have to be compliant, you can instead use a hidden span to do the same thing. For example, to contextually store a UNID, you could use:
<span class="contextual unid" title="#{java_script:listData.getUniversalId();}" />
The define a css class "contextual" like this:
.contextual { display: none; }
and to retrieve the data, you would use this client-side:
dojo.attr(dojo.query('span.contextual.unid', this.node)[0], 'title');
This is really cool
is there any drawbacks using non html tags on a webpage? could this be a problem for some browsers like doctypes and html validation services?
thanks
Thomas