Subscribe via FeedJeremy Hodge, Apr 14, 2010, 9:17:31 PM
Occasionally, I want to force an event handler to execute on the server in an XPage based on a condition I find during the execution of a client-side script. For example, I might have an object that the user is interacting with that is generated client-side, and therefore has no server-side defintion, and thus no server generated event handlers.
Well, there is a way to execute a server side event handler from a client script, and there is a lot of hidden power in this capability to boot.
Let's start with a simple example. Let's say you have a created a dijit.Toolbar at the top of your form that has a button. When the user clicks this button, you want to save the document. (Yes, normally you can just create an xp:div with a dojoType of dijit.Toolbar, and place a button on it in XPage markup and accomplish the same thing, but we're starting with an easy example, remember ;D ).
So lets start by creating that toolbar clientside with some javascript:
// create the new toolbar and add inside the body tag...
var myToolbar = new dijit.Toolbar({
id:'myToolbar',
style: {
position: 'fixed',
top:0,
left:0,
height:'22px',
width:'100%'
}
});
dojo.body.addChild(myToolbar);
Now, lets add a button to that toolbar:
myToolbar.addChild(new dijit.form.Button({
label: 'Save Document',
showLabel: true,
id: 'saveDocumentButton',
onClick: function() {
}
}));
Ok, now that gets us a toolbar at the top of the page, with a button that says "Save Document", but doesn't do anything when clicked.
Now, let's look at the code we put in our XPage markup to code to create the event handler. So first we have to decide where to put our event handler. The simple rule, is where it makes sense! For example, in this case we are saving a document. So if the data source is defined in the XPage object, it would suit us fine to place the event handler right inside the XPage's xp:view tag, just below the data definition. If the data is defined in an xp:panel, then it makes sense that we would want it inside the xp:panel that defines the data document so we process the proper document. For this example, lets assume our data is defined at the XPage node, so we'll put our event handler just after our xp:this.data tag.
(pipes '|' are replacing the > and < since the blog is continually eating them)
|xp:view .... |
|xp:this.data|
|xp:dominoDocument var="myDocument"
formName="myForm" action="editDocument"
documentId="#{java_script:viewScope.currentDocId}"|
|/xp:dominoDocument|
|/xp:this.data|
|xp:eventHandler event="onfubar" id="eventhandler1a" submit="false"|
|xp:this.action|
|xp:saveDocument||/xp:saveDocument|
|/xp:this.action|
|/xp:eventHandler|
|!-- everything else goes here --|
|/xp:view|
Ok, so there it is an eventHandler defined for the XPage, that really doesn't connect to anything. There are a few noteworthy items here. First, the event we specified. Normally this would be something like "onclick" or "onmousedown" ... In our case, we actually don't want to connect this event handler to anything, but we have to give it an event name, or we'll get errors, so we just specify a junk name of "onfubar". Next, we specified an ID for the event handler, in this case "eventhandler1a". We'll need to know this later on to specify which event handler we want to run (Yes that does mean you can have multiple event handlers defined, and can call any which one you want, more on that later.)
Now, let's look at the client side script used to execute this server side event handler. They key to making this work is setting the value of the hidden field $$xspsubmitid to the id of the event handler we want to execute. Let's wrap it all up in a nice little function that we can call at any time:
var executeOnServer = function () {
// must supply event handler id or we're outta here....
if (!arguments[0])
return false;
// the ID of the event handler we want to execute
var functionName = arguments[0];
// OPTIONAL - The Client Side ID that you want to partial refresh after executing the event handler
var refreshId = (arguments[1]) ? arguments[1] : "@none";
var form = (arguments[1]) ? XSP.findForm(arguments[1]) : dojo.query('form')[0];
// OPTIONAL - Options object contianing onStart, onComplete and onError functions for the call to the
// handler and subsequent partial refresh
var options = (arguments[2]) ? arguments[2] : {};
// Set the ID in $$xspsubmitid of the event handler to execute
dojo.query('[name="$$xspsubmitid"]')[0].value = form.id + ':' + functionName;
XSP._partialRefresh("post", form, refreshId, options);
}
Ok, so there is a bit of "magic" in that code as it basically mimics parts of the XSP object to set up the call and execute the event. But the important parts are the arguments you pass to the function. Basically to call the server side event and cause no partial refresh to occur, just call executeOnServer with a single argument of the id of the event handler you want to execute, in our case, we would call it like this:
executeOnServer('eventhandler1a');
If I wanted to partial refresh an object, say for example we had an xp:panel with an id of "refreshTarget", we could refresh that panel after executing the handler by calling executeOnServer like this:
executeOnServer('eventhandler1a', dojo.query('[id$="refreshTarget"]')[0].id);
And finally, if I wanted to execute more code in an onStart, onComplete, or onError event of the partial refresh, I could call executeOnServer like this:
executeOnServer('eventhandler1a', dojo.query('[id$="refreshTarget"]')[0].id, {
onStart: function() { alert('starting!'); },
onComplete: function() { alert('complete!'); },
onError: function() { alert('DANGER WILL ROBINSON!'); }
});
Now, if I wanted to call executeOnServer, and have the capability to do onStart, onComplete, etc, but don't want to actually partially refresh anything, you can use '@none' as the partial Refresh Id, like this:
executeOnServer('eventhandler1a', '@none', {
onStart: function() { alert('starting!'); },
onComplete: function() { alert('complete!'); },
onError: function() { alert('DANGER WILL ROBINSON!'); }
});
As I mentioned before, you can create multiple eventhandlers, and call any one of them at any time. For example, in your XPages markup:
|xp:view .... |
|xp:this.data|
|xp:dominoDocument var="myDocument"
formName="myForm" action="editDocument"
documentId="#{java_script:viewScope.currentDocId}"|
|/xp:dominoDocument|
|/xp:this.data|
|xp:eventHandler event="onfubar" id="saveDoc" submit="false"|
|xp:this.action|
|xp:saveDocument||/xp:saveDocument|
|/xp:this.action|
|/xp:eventHandler|
|xp:eventHandler event="onfubar" id="deleteDoc" submit="false"|
|xp:this.action|
|xp:deleteDocument||/xp:deleteDocument|
|/xp:this.action|
|/xp:eventHandler|
|!-- everything else goes here --|
|/xp:view|
And all you'd have to do is call
executeOnServer('saveDoc');
or
executeOnServer('deleteDoc');
to execute the appropriate event handler. The possibilities here are profond, as you now have the ability to very powerfully connect your client side, and your server side event models in a very cohesive way. With a little ingenuity beyond what I have shown here, you can actually pass objects back and forth between the server and client sides, and have both sides operate their respective functions accordingly.
Happy Coding!
15 responses to Programmatically Triggering an XPages Server Side Event Handler from a Client Side Script
David Gilmore, June 19, 2011 at 8:59 PM
- I'm using a stripped-down version of this on a custom control to link a XPage event handler to a djit.form.ComboBox. It appears to work, in that it does refresh the page, but two things:
1. print() in the SSJS of the eventHandler() never produces output on the console as expected.
2. After reloading the page, the dijit.form.ComboBox tosses an error in d._toDom, which it does not do on intial load.
- If I take out the XSP._partialRefresh() the page reloads without error. I suspect that's changing the DOM in some manner that's incompatible with dojo, but I haven't figured out how.
Any clues would be greatly appreciated!...
Sergey, August 31, 2010 at 9:22 AM
Jeremy,
I just want to say: thank you a LOT for this great sharing! I've implemented this technic in my project and it works great.
kester simm, July 22, 2010 at 6:34 AM
Just noticed an error put the function in an variable t.ex utils and it will work:
var utils={
getComponentID:function(id){
var componentID='';
dojo.query('[id*=\"'+id+'\"]').forEach(function(node, index, arr){
var result=node.id.split(':');
if(result[result.length-1]==id){
componentID=node.id;
}
});
return componentID;
}
}
kester simm, July 21, 2010 at 8:47 AM
Really usefull code. I had one problem though with the partial uppdate id.
if you only have one node which has in the id "refreshTarget" then it will work
e.g.
dojo.query('[id$="refreshTarget"]')[0].id
otherwise if you have serveral nodes containing "refreshTarget" then this could be a problem
i wrote the following code to get exact the write node:
getComponentID:function(id){
var componentID='';
dojo.query('[id*=\"'+id+'\"]').forEach(function(node, index, arr){
var result=node.id.split(':');
if(result[result.length-1]==id){
componentID=node.id;
}
});
return componentID;
}
if you did the code direct in a script block then you only need:
dojo.byId('#{id:refreshTarget}').id or XSP.getElementById("#{id:refreshTarget}");
but we all use script libs so that's not available. :-)
i also wrote a function which gets the node:
getComponent:function(id){
var component='';
dojo.query('[id*=\"'+id+'\"]').forEach(function(node, index, arr){
var result=node.id.split(':');
if(result[result.length-1]==id){
component=node;
}
});
return component;
}
JJTB Somhorst, June 24, 2010 at 10:57 AM
Thanks for this tip Jeremy. I just tested it a bit around and was able to create an custom control with it that listens real time to the progress of an agent. So the agent does its calculations and saves the progress in a document. While the agent is running that document is read by a timed refresh of a custom control.
I dont run into the problem that buttons and comboboxes loose their focus / control ?
Jeremy Hodge, May 11, 2010 at 12:57 PM
Hello Antonio, you can see an example @ the XPages Blog samples site:
http://www.xpagecontrols.com/xpagesblog.nsf/programmatically-triggering-an-xpages-server-side-event-hand.xsp
This has a demo and all the sample code posted.
Antonio, May 11, 2010 at 12:50 PM
Hi :
I am trying to implement this example but I have some problems, Could you please send me this code implemented ? thanks in advance
David Gilmore, April 23, 2010 at 10:19 PM
- Thanks Jeremy. I've had continual, random, and recurrent issues with sever-side actions simply not working. I suspect I'm back in that boat, if you see it triggering, and what you see is quite frankly what I would have expected, since the |saveDocument| is the same in either case. (sigh) There it is. Still a brilliant bit of work!
Jeremy Hodge, April 23, 2010 at 7:37 PM
It appears to be triggering the postSaveDocument without issue.
You can reference this example to see what I mean:
http://www.xpagecontrols.com/xpagesblog.nsf/programmatically-triggering-an-xpages-server-side-event-hand.xsp
The example edits a document, and when you click a button, it uses the executeOnServer function to execute the event, which does a simple xp:saveDocument ... then the postSaveDocument increments a click counter, and the counter is returned to the browser, incremented. The example has a slightly updated version of the executeOnServer function that fixes an issue with executing an event handler in a custom control, but you need to send the full id of the event handler, not just the name. It's covered in detail in the example.
David Gilmore, April 23, 2010 at 7:19 PM
Sadly I don't see a |saveDocument| triggered in this manner firing |postSaveDocument| automagically like it does when I put |saveDocument| on clickable event handler (a link for example). I put two |eventHandler| on initially, one exactly as your example and one with submit="true". I called the latter, and Firebug shows the server hit, but nothing else runs. Did I do something wrong?
David Gilmore, April 23, 2010 at 6:25 PM
BRILLIANT! I knew there had to be some simple way as well, and simply hadn't dug in the right place, I guess. Hat's off to this bit of work...
stephen hood, April 15, 2010 at 5:28 PM
Jeremy I have been checking out Xpages in 8.5.1 using the free designer and right out of the box started having problems with some basic controls on just a simple test page.
It looks as if standard controls like the combo boxes lose their keyboard functionality IF they are the target of a partial refresh.
I HAVE to use the mouse to get the keyboard control back on track. Problems manifest differently depending on browser.
Take out partial refresh and everything works as expected but then I obviously lose the cascading lookup, enable/disable features I'm looking to incorporate by just refreshing part of the page those controls are contained by.
Have you been seeing this kind of stuff or am I going insane :).
Trying to get some feedback from people that are obviously getting deeper into the Xpages stuff. Right now it's feeling like Xpages is a waste of time on really straight forward requirements.
My first impressions are the client-side js, server side js, partial refreshes, documents, XSPDocuments, JSF lifecycle, dojo widget complexity is resulting in an unstable event model for even relatively simple requirements in the UI.
Which kind of defeats the point of Xpages. Really appreciate any thoughts on your xp. Privately if necessary.
Jeremy Hodge, April 15, 2010 at 11:05 AM
@Patrick ... Good ol' reverse engineering ... I figured there has to be some way for the client side to notify the server side which handler to execute since you can have multiple handlers within a refresh area ... so from there its diving into the uncompressed javascript files on the server (in the data/domino/js/dojo-1.3.2/ibm/... folder) and then experimentation with the domino generated code to see how/where it identified the event handler.
Patrick Picard, April 15, 2010 at 10:56 AM
That is extremely cool stuff and goes a long way towards MVC. Since there is so little Xpages documentation, how do you manage to dig things up like that?
Paul Wthers, April 15, 2010 at 7:01 AM
As you say, it's very pwoerful and a great trick