Search code examples
javascriptxpagesjavabeansxpages-ssjsautosave

Trying to trigger SSJS from Javascript using 'executeOnServer' on an XPage


I have a rather long, tabbed XPage form to be populated by visitors, and I'd like to auto-save in the backend every few minutes.

The entire form represents a managed bean, and I can populate/save a Domino document from the bean values using a button manually just by calling...

portfolio.save(sessionScope.unid,false)

I located this excellent post by Jeremy Hodge (great how the older tips stand the test of time!) that explains the technique...

http://xpages.info/XPagesHome.nsf/Entry.xsp?documentId=88065536729EA065852578CB0066ADEC

I then found a forum response from Paul Calhoun that describes the use of the technique with a timer to fire off regular document saves...

https://www-10.lotus.com/ldd/xpagesforum.nsf/xpTopicThread.xsp?documentId=B5ECAEC50241A3EE85257B32008248FC

I have implemented the technique on my XPage, but the SSJS simply isn't firing. I've even placed 'onStart' and 'onComplete' events to validate everything is working as expected. The counter works, the console shows the timer and the alerts appear every 5 seconds or so... but the SSJS simply isn't initiated.

My eventHandler that does the work...

<xp:eventHandler event="onfubar" id="autoSave" submit="false">
    <xp:this.action><![CDATA[#{javascript:portfolio.save(sessionScope.unid,false);}]]>
    </xp:this.action>
</xp:eventHandler>

... and the event handler that triggers the regular save (yes, I know that I've butchered the 'executeOnServer' command and manually added the additional parameters - just trying to isolate the issue)...

    <xp:eventHandler event="onClientLoad" submit="false">
        <xp:this.script><![CDATA[
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);
}

var x = 0;
var form = document.forms[0];

t = new dojox.timing.Timer(3600);
t.onTick = function() {
    x+=1;
    console.info(x);
    if(x > 4){
        x = 0;
        console.info("reset");
        executeOnServer('autoSave','@none',{
            onStart: function() { alert('starting!'); },
            onComplete: function() { alert('complete!'); },
            onError: function() { alert('DANGER WILL ROBINSON!'); }
        });
    }
}

t.onStart = function() {
 console.info("Starting timer");
}

t.start();]]></xp:this.script>
    </xp:eventHandler>

I know that I must be missing something, and I'm probably staring straight at it, but I simply can't figure out where I've gone wrong.

Thanks very much for any insight into the issue (and a point in the right direction!).


Solution

  • It can actually be way simpler than the snippets you found (you can drop all of that)... Consider the following bean:

    public class PortfolioBean implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        private Map<String, Object> data = new HashMap<String, Object>();
    
        public Map<String, Object> getData() {
            return data;
        }
    
        public void autoSave(String unid, Boolean something) {
            System.out.println("The unid is " + unid + " and something is " + something);
    
            data.put("stamp", "autosave at " + new Date());
        }
    
    }
    

    And the following xsp source:

    <xp:view xmlns:xp="http://www.ibm.com/xsp/core">
    
        <xp:div id="myForm" style="padding: 20px;">
            <xp:eventHandler event="onautosave" id="eventAutoSave"
                submit="false" action="#{javascript:portfolio.autoSave(sessionScope.unid, false)}" />
    
            <p>Stamp</p>
    
            <xp:inputText id="inputStamp" value="#{portfolio.data.stamp}" size="60" />
        </xp:div>
    
        <xp:scriptBlock value="
            XSP.addOnLoad(function() {
                window.setInterval(function() {
                    XSP.partialRefreshPost('#{id:inputStamp}', {
                        execId: '#{id:myForm}',
                        params: { '$$xspsubmitid' : '#{id:eventAutoSave}' },
                        onComplete: function() { console.log('complete at ' + new Date()); }
                    });
                }, 5000);
            });" />
    
    </xp:view>
    

    I created a myForm container to create an "anchor" and narrow down the portion of the page that gets evaluated, read submitted or executed (best practice in view of best performances). myForm is therefore the execution id. It's fundamental that the execution id belongs to a an element that contains the event handler you want to fire. If, for example, I placed the onautosave event outside the myForm div the event would not be fired. If you couldn't care less (bad!) you could remove the execId: '#{id:myForm}' property passed to the object in the XSP.partialRefreshPost method call and, at this point, it would work regardless of the xsp components arrangement.

    In addition, with the XSP.partialRefreshPost you can decide whether you want to partial refresh any element of the page or not. I put #{id:inputStamp} so that you could see the input field getting filled out with the text resulting from firing the autosave. If you know that you don't have to display any DOM change that would result from the server-side method evaluation you can specify an empty string (like this XSP.partialRefreshPost('',) and that would mean norefresh on the client side. But you can still do something at the completion of the operation by using the onComplete property as you can see from the example.

    In the params property goes an additional object with a property that defines the id of the action you want to fire: { '$$xspsubmitid' : '#{id:eventAutoSave}' }.

    In conclusion, every 5 seconds, the above code will:

    1. print on the Domino console some text, to show you that the event was evaluated server-side
    2. fill out the field with a stamp and the date of the execution
    3. print at browser console the time the method has run

    No additional dependencies or snippets. It works just like that out of the box.