Search
Categories
Tags
Latest Comments

Entries in events (3)

Thursday
Apr152010

Programmatically Triggering an XPages Server Side Event Handler from a Client Side Script

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!

Friday
Nov062009

XPage Cheat Sheet #1 - The Page Lifecycle (Update)

A few weeks ago I wrote a blog outlining the XPage lifecycle.  It appears that with 8.5.1 there have been a few changes made.  The following is an updated account of the lifecycle.

This example is based upon a single XPage (X) with 2 custom controls (A & B). Controls seem to be processed in the order they are listed.

The following are executed ONCE only:-
X.beforePageLoad
A.beforePageLoad
A.afterPageLoad
B.beforePageLoad
B.afterpageLoad
X.afterPageLoad

The next set of events are ONLY executed when a Full or Partial refresh occurs:-
X.afterRestoreView
A.afterRestoreView
B.afterRestoreView
If any control events have been fired they are executed following the afterRestoreView events.

The following now occur every time following the PageLoad or restoreView events
X.beforeRenderResponse
A.beforeRenderResponse (only occured with PageLoad in 8.5.0)
B.beforeRenderResponse (only occured with PageLoad in 8.5.0)
X.afterRenderResponse
A.afterRenderResponse (only occured with PageLoad in 8.5.0)
B.afterRenderResponse (only occured with PageLoad in 8.5.0)

The key difference for 8.5.1 being that the beforeRenderResponse and afterRenderResponse are now executed for the XPage and ALL custom controls regardless of whether a partial refresh or full refresh occurs.
Note: This is only a partial listing as the Document events are not (yet) included.

The same observations are still relevant:-
  1. PageLoad events are a good place to place code you only wish to execute the very first time the page is created.
  2. afterRestoreView events seem to be the best place for code that you wish to execute each time a refresh occurs.
  3. Events on a page/control will execute after the afterRestoreView events have fired so they will have access to any variables/object you create in these events.
  4. To have code execute every time the page is processed you need to place it in a PageLoad and an afterRestoreView event
  5. Wen placing code in a control's afterPageLoad event don't assume the beforePageLoad events of the other controls have fired or that the page's afterPageLoad has fired.
You can test this out on-line using a demo XPage I have created as part of a new XPages Kindergarten section of the .Domino Framework: http://www.dominoframework.com/DominoFramework.nsf/XPK_Events.xsp

Wednesday
Sep092009

Client-Side Events for Partial Updates: onStart, onComplete, onError

Hello All you XPages geeks out there.  Today I bring you a bit of good news from the land of 8.5.1!

There are three new CLIENT-SIDE events you can hook into when you do a partial update.

onStart - gets called just before the XSP handler submits the AJAX call to get and process the partial update

onComplete - gets called after the partial update is completed and has been processed, and the DOM updated

onError - gets called if an error happens during the xhrGet or xhrPost process (equiv to the error parameter of dojo's xhrGet/xhrPost) - IF YOU DEFINE onError YOU OVERRIDE THE DEFAULT ERROR MESSAGE. Which is a good thing because you can give your users better context to the problem, etc.

What can you do with these? Well, for example:

- if you have a Partial Update that is going to take a long time, with onStart, you can set the default 'timeout parameter' for a partial update (XSP.submitLatency = 10000 // 10 Seconds (default is 6)), and warn your user that its going to take a bit longer than expected, etc.

- For the long partial update, with onComplete you can notify your user the update is complete, or If you use dojo's drag and drop functionality, you could use the onComplete to re-setup your drag and drop sources or targets. Or you could have the element start "invisible" and use the onComplete to fade in the element using dojo.fadeIn() for some extra "flair"

Ok, 'nuff examples, where do you set these up? Well, in your event handler that triggers the partial update of course! For example, if you have a button, and that button triggers a partial update, expand the button in the outline, and you'll see a Node for Event handler. Click that node, and in the All Properties, under 'events' you'll find the three events!

Happy Coding!

 

Oh, and BTW: IBM Lotus Notes/Domino and Lotus Notes Traveler 8.5.1 is prerelease code and there are no guarantees from IBM that the functionality presented or discussed will be in the final shipping product.