There have been a few posts today on StackOverflow about SSJS being run when it’s not expected. I think a lot of that comes from a misunderstanding of what’s happening on the server.
Parsing of Values
Firstly, property values are just plain text.That’s reinforced if you look at the Java class for an XPage or Custom Control in Package Explorer, under Localxsp. For example, here’s a method that creates a Computed Field:
private UIComponent createText(FacesContext context,
UIComponent parent, PageExpressionEvaluator evaluator) {
XspOutputText result = new XspOutputText();
String sourceId = "/xp:view[1]/xp:text[1]/@value";
String valueExpr = "Database title is #{database.title}, database name is #{javascript: @Subset(@DbName(), -1);}";
ValueBinding value = evaluator.createValueBinding(result, valueExpr, sourceId,Object.class);
result.setValueBinding("value", value);
return result;
}
So when we think we’re writing SSJS or EL, we’re not. We’re writing plain text part or all of which begins with a specific identifier. “${javascript:” means pass whatever follows, up to the closing “}”, to the SSJS parser at page load time. “#{javascript:”means pass whatever follows, up to the closing “}”, to the SSJS parser each time the property is updated in the XPages lifecycle. “#{myVar}” or “#{myVar.myProperty}” is passed to something called the VariableResolver and, in the case of the latter, then onto the PropertyResolver each time the property is updated in the XPages lifecycle. That’s also why Jesse Gallagher was able to add a parser for Ruby on OpenNTF, which does the same thing, picking up anything that starts “#{ruby:”.
The SSJS parser is not used for every property, only as and when part of the property starts “${javascript:” or “#{javascript:”. And only for the contents of that. So revisiting that code above, all the XPages processing sees is "Database title is #{database.title}, database name is #{javascript: @Subset(@DbName(), -1);}"
. Everything that’s not in blue is ignored and just written out to the browser as it is – the SSJS parser and VariableResolver don’t need to do anything with it.
So similarly, if you had "//Database title is #{database.title}, database name is #{javascript: @Subset(@DbName(), -1);}"
, the VariableResolver and SSJS parser would still pick up and process the bits in blue. “//” is just a literal string with no important meaning, because that part is not being passed to any parser.
Similarly for "Database title is #{database.title}, database name is #{javascript: //@Subset(@DbName(), -1);}"
, the VariableResolver and SSJS parser would still pick up and process the bits in blue. But now, the SSJS would return an empty string, because the SSJS has been commented out.
CSJS Script Blocks / EventHandlers
That becomes even more important when it comes to Script Blocks or CSJS in eventHandlers. The key thing to remember is CSJS is not parsed on the server, just on the browser. And SSJS is not parsed on the browser, it is evaluated on the server and the resulting value incorporated in the text string which will be passed to the browser and which the browser will run as client-side JavaScript.
So say you have an Edit Box whose value is #{viewScope.myVar} but has no default value, and client-side JavaScript in the eventHandler of a button that says alert('#{javascript:return viewScope.myVar}');
and runs a partial refresh. Let’s step through what happens.
- On the server, the button is processed and the SSJS passed to the parser.
- The viewScope variable has no value, so is evaluated to “”. So the resulting string passed to the browser is
alert('');
- The user enters “Test” into the Edit Box.
- The button is clicked and the pre-generated CSJS runs –
alert('');
- The partial refresh runs, passes the value from the Edit Box to the viewScope variable (in the Update Model Values phase).
- On the server, the button is processed (in the Render Response phase) and the SSJS passed to the parser.
- The viewScope variable now has a value and is evaluated to “Test”. So the resulting string passed to the browser is
alert('Test');
- The user changes the value in the Edit Box to “Test 2”.
- The button is clicked and the pre-generated CSJS runs –
alert('Test');
Once it’s broken down into the steps in the process, it becomes easier to anticipate what will happen and understand what runs where.
It also becomes clearer that the CSJS you write is just text and it’s passed to the browser as that same text. So “//” in an eventHandler or the value property of a Script Block means nothing special. It’s just a literal string that says “//”. It does not mean “this is a comment” to the server, it only means “this is a comment” to the browser, and only when it runs.
Summary
So there are a few ways to prevent SSJS running. One is to comment out the SSJS code itself, within #{javascript:…}. Another is to set loaded=”false” on the component (and remember that can also be set on an eventHandler). Or if you just want to identify errors, XPages OpenLog Logger or OpenNTF Domino API will capture uncaught errors in SSJS.