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!