On Friday I blogged about how you can’t include Expression Language in Client-Side JavaScript for an eventHandler. (Because of problems with the blog that prevented comments, I had to re-post it.)
The other benefit of blogging is that someone can usually give you some more information that makes everything drop into place. Toby Samples tweeted the explanation about it being a MethodBinding versus a ValueBinding:
@PaulSWithers Comments still seem to be down for me, but in short onComplete is a ValueBinding, script is a MethodBinding
— Toby Samples (@tsamples) October 3, 2014
The difference will be confusing for many who have not dug into the back-end Java classes used by the XPages runtime. But all XPages developers are intimately acquainted with MethodBindings and ValueBindings and it becomes a more relevant factor when working with beans or other Java objects. And the main reason I’m writing this blog post is to get it clear in my own head!
ValueBindings are encountered throughout XPages. Firstly, it does not refer to the binding of the value property of an Edit Box or Computed Field to a back-end object. That’s the most common example you’ll probably use. A ValueBinding is basically created whenever you compute most properties of controls – value, rendered, text, categoryFilter, onComplete. (After all, an eventHandler is a control that has properties, like onComplete.) The following code highlights this:
private UIComponent createComputedfield1(FacesContext context,
UIComponent parent, PageExpressionEvaluator evaluator) {
XspOutputText result = new XspOutputText();
String sourceId = "computedField1/xp:this.value[1]/text()";
String valueExpr = "#{javascript:var msgs = facesContext.getMessages();\nvar count = 0;\nwhile (msgs.hasNext()) {\n\tvar obj = msgs.next();\n\tcount++;\n}\nreturn \"You have \" + @Text(@Integer(count)) + \" message(s) on the page\";}";
ValueBinding value = evaluator.createValueBinding(result, valueExpr, sourceId,Object.class);
result.setValueBinding("value", value);
result.setEscape(true);
String sourceId2 = "computedField1/@rendered";
String renderedExpr = "#{javascript:facesContext.getMessages().hasNext();}";
ValueBinding rendered = evaluator.createValueBinding(result, renderedExpr, sourceId2,boolean.class);
result.setValueBinding("rendered", rendered);
setId(result, "computedField1");
return result;
}
The first ValueBinding to the value property uses SSJS to compute the number of messages on the page. The second ValueBinding to the rendered property uses SSJS to see if there are any messages displayed. Specifically looking at the second example, a ValueBinding computes a String (renderedExpr) relative to an ID (sourceId2 – computedField1/@rendered) and converts it to a Java class (boolean.class), relative to a component (result). The following line always calls result.setValueBinding() and stores the outcome in a property.
The Expression Language in a ValueBinding looks for the value of a property of an object, so #{database.filePath} gets the database object, looks for the property filePath, which will usually be private and accesses it via its getter – getFilePath(). (For boolean properties, “is” will be used instead of “get”, so isReadOnly().) This is nothing peculiar to XPages, this is standard for Expression Language.
A MethodBinding does something similar, but passes the string to either the script or action property. So this is the Java code for a MethodBinding:
private UIComponent createEventHandler(FacesContext context,
UIComponent parent, PageExpressionEvaluator evaluator) {
XspEventHandler result = new XspEventHandler();
String sourceId = "button1/xp:eventHandler[1]/xp:this.action[1]/text()";
MethodBinding action = evaluator.createMethodBinding(result,
"#{javascript:viewScope.put(\"refreshed\",\"Done\");}",
null,null, sourceId);
result.setAction(action);
result.setRefreshId("mainArea");
result.setSubmit(true);
result.setEvent("onclick");
MethodBinding script = evaluator.createMethodBinding(result,
"return confirm(\"Are you sure you want to continue?\");",
null,null, null);
result.setScript(script);
result.setRefreshMode("partial");
return result;
}
Expression Language can be used in a MethodBinding. Those who have used managed beans and wanted to avoid SSJS as much as possible will have set the action of a button to something like #{myBean.save}. Note the absence of parentheses and that this maps to a save() method not a getSave() or isSave() method. Others have also blogged how, in the current implementation of JSF in XPages, parameter cannot be passed.
This means that you can use Expression Language for the CSJS in an eventHandler as follows:
<xp:button value="Bean" id="button4">
<xp:eventHandler event="onclick" submit="false">
<xp:this.script><![CDATA[#{csjsBean.getElBinding}]]></xp:this.script>
</xp:eventHandler>
</xp:button>
csjsBean.getElBinding() returns a String “alert(‘Hello’)“, which is then written out onto the page as CSJS. You can’t combine Expression Language with anything else, so alert('#{csjsBean.getElBinding}'); won’t work.
In reality, there are unlikely to be many use case for this. One possible use case is to return a string that maps to a CSJS function, e.g. if getELBinding() returned “testEL()”, which was a CSJS function. There may be scenarios where the CSJS you want to run varies significantly, depending on back-end business logic, so running the business logic on the server to ensure only the relevant function is available on the page may be preferable.
However, as always when combining server and client-side code, make sure you have Doc Brown with you and are thinking fourth dimensionally: remember that whatever CSJS is available on the web page will depend on what the result of the back-end logic was last time the server ran it!
The onComplete property of the eventHandler or view.postScript() method may be what you need if your back-end logic depends on front-end changes by the user.
If the CSJS function to run depends on, for example, the current user or the current stage in the workflow, then using EL to call a bean (or other Java class) to return the relevant CSJS method may be a relevant use case.