JavaScript companion classes
Rationale
Some Orbeon Forms components do not require any custom JavaScript code, for example components which simply combine other controls (such as a date components made of separate input fields or dropdown menus). In such cases, you implement all the logic with XForms.
On the other hand, some components encapsulate functionality mainly implemented in JavaScript. Orbeon Forms provides an easy way to interface with the JavaScript side: each JavaScript-based component must define a JavaScript class used to handle the component's lifecycle as well as hold custom data and functions. We call this class is called the component's companion class. One instance of this class is created by Orbeon Forms for each instance of relevant (visible) control. We call these instances companion instances.
Directory layout
You place your JavaScript files alongside your XBL file. See Directory layout for details.
To include a companion JavaScript file, use the <xbl:script>
element directly within the <xbl:xbl>
element:
Creating and declaring a companion class
With Orbeon Forms 2022.1.1 and newer
[SINCE Orbeon Forms 2022.1.1]
The first parameter to declareCompanion()
must match the component's binding name, for example:
if your component's binding is
acme|multi-tool
pass
acme|multi-tool
you place the JavaScript file under
/xbl/acme/multi-tool/multi-tool.js
if your component's binding is
foo|bar
pass
foo|bar
you place the JavaScript file under
/xbl/foo/bar/bar.js
The second parameter to declareCompanion()
can either be a JavaScript object that acts as the prototype for the companion class, or (and this is new with Orbeon Forms 2022.1.1) it can also be a JavaScript class instead of a prototype. Note that this must not be an instance of the class (so don't use new
), but the class itself. For example:
Advantages of passing a JavaScript class include:
Orbeon Forms directly passes the container element in the constructor, which provides clarity over the "magic"
this.container
field.You can use inheritance easily to share code between components.
Classes have become mainstream in JavaScript since Web browsers support them natively.
With Orbeon Forms 2016.1 and newer
Orbeon Forms 2016.1 and newer provide a simple way to declare a companion class by passing a JavaScript prototype. Here is the overall structure:
The first parameter to declareCompanion()
must match the component's binding name, for example:
if your component's binding is
acme|multi-tool
pass
acme|multi-tool
you place the JavaScript file under
/xbl/acme/multi-tool/multi-tool.js
if your component's binding is
foo|bar
pass
foo|bar
you place the JavaScript file under
/xbl/foo/bar/bar.js
The second parameter to declareCompanion()
is a JavaScript object that acts as the prototype for the companion class. This is documented further below.
With Orbeon Forms 4.10 and earlier
[DEPRECATED SINCE Orbeon Forms 2022.1]
In the JavaScript file corresponding to your component, declare a companion class as follows:
YAHOO.namespace("xbl.acme")
defines a namespace for your class. All the XBL components that ship with Orbeon Forms are in thexbl.fr
namespace. If you are defining a component for your company or project named Acme, you could use the namespacexbl.acme
.ORBEON.xforms.XBL.declareClass()
defines your class as an XBL class:It takes 2 parameters: your class, and the CSS class found on the outermost HTML element that contains the markup for your components. This element is generated by Orbeon Forms, and the class name is derived from the by-name binding of your
<xbl:binding>
. For example, if the binding isacme|multi-tool
, the class name isxbl-acme-multi-tool
.
The companion class
Whether you use the declareCompanion()
method or the declareClass()
method, and whether you pass a JavaScript prototype object or a JavaScript class, Orbeon Forms internally creates a JavaScript class which derives from either the class passed or a class created from the prototype. That class:
adds a
container
propertyThis points to the outermost container HTML element associated with the component.
In your JavaScript code, you can refer to
this.container
to retrieve this element.[SINCE Orbeon Forms 2022.1.1] We recommend you use a JavaScript class's constructor instead, which is directly passed that container element.
adds or overrides (if present) the
init()
anddestroy()
methodsThis provides finer internal control over these lifecycle methods.
The overridden methods call your own
init()
anddestroy()
methods if present.In general, you don't have to worry about this. However, you shouldn't call
init()
anddestroy()
yourself in any case.
adds a static
instance()
factory method to the classWARNING: This is present for backward compatibility only and must no longer be relied on. Use
instanceForControl()
instead.
For example, if you know you have an input field with the class acme-my-input
inside your component, you get the HTML element corresponding to that input with the following jQuery:
Summary of companion class methods
The init()
method is not new in Orbeon Forms 2016.1, but when using the javascript-lifecycle
mode it is called automatically. Prior to Orbeon Forms 2016.1, or when not using the javascript-lifecycle
mode, it is called either via XForms event handlers, or as a side-effect of calls to setFocus()
or enabled()
.
Calling methods upon XForms events
With Orbeon Forms 2016.1 and newer
You can call a JavaScript method defined in your JavaScript class when an XForms event occurs. For example, to call the myFunction()
method on xxforms-visible
, write:
instanceForControl()
gets or creates the instance of the JavaScript class associated with the current component. It creates class instances as necessary, keeping track of existing instances and maintaining a 1-to-1 mapping between instances of the XBL component in the form and instances of your JavaScript class.
WARNING: You should use this only to call your own methods. Do not use this to call the init()
, destroy()
, or other lifecycle methods documented in this page.
Note that a component can be created on the server, and receive the xforms-enabled
event, but its HTML/JavaScript representation might not be visible and in fact there might not be any markup yet available for that control. This is the case, for example, for hidden switch cases, which is the construct used for hidden wizard pages. Therefore, use xxforms-visible
and xxforms-hidden
instead of xforms-enabled
/xforms-disabled
in conjunction with instanceForControl()
.
With Orbeon Forms 4.10 and earlier
[DEPRECATED SINCE Orbeon Forms 2022.1]
With Orbeon Forms 4.10 and earlier, you obtain the class using the JavaScript namespaces you declared alongside the class, and directly call the instance()
factory function:
Support for the external-value mode
Introduction
[SINCE Orbeon Forms 2016.1]
When the external-value
mode is enabled, the following two methods must be provided:
xformsUpdateValue()
xformsGetValue()
For an example, see the implementation of the fr:code-mirror
component: code-mirror.xbl
and code-mirror.js
.
The xformsUpdateValue method
The XForms engine calls this method:
if the
javascript-lifecycle
and theexternal-value
modes are enabled, just after the control is initialized,when the internal value of the control changes,
and in response to calls to
ORBEON.xforms.Document.setValue()
.
Method parameters
xformsUpdateValue()
receives a string and must update the associated JavaScript control, making the value accessible to the user.
Method return value
xformsUpdateValue()
must return:
If it sets value is synchronously:
undefined
(or not return anything).If it sets value is asynchronously: a jQuery deferred object whose
done()
method must be called once the value is known to have been fully applied. For instance:This allows the XForms engine to know when it is safe to call
xformsGetValue()
after a new value has been set.
[SINCE Orbeon Forms 2020.1]
In addition to a jQuery deferred object (with a done()
method), you can also return a JavaScript Promise
object (with a then()
method). The latter is the recommended way since JavaScript promises are implemented natively by all major browsers (except IE 11, but Orbeon Forms includes a polyfill for IE 11).
The xformsGetValue method
The XForms engine calls this method when:
it needs the control's value,
and in response to calls to
ORBEON.xforms.Document.getValue()
.
xformsGetValue()
returns a string obtained from the associated JavaScript control.
External value serialization/deserialization
[SINCE Orbeon Forms 2019.1]
By default, the external value exchanged with the client is identical to the storage value of the component.
By using the xxbl:serialize-external-value
and xxbl:deserialize-external-value
attributes on <xbl:binding>
, you can create XPath expressions that transform the external value back and forth.
This is useful if the value must contain more than the storage value of the component. For example the fr:number
component uses this to communicate a display value, an edit value and a decimal separator to the client.
For xxbl:serialize-external-value
:
XPath context item: XPath string of the control's storage value
Expression result: XPath string to send to the client's companion class's
xformsUpdateValue()
method
For xxbl:deserialize-external-value
:
XPath context item: XPath string provided by the client's companion class's
xformsGetValue()
methodExpression result: XPath string to use as the control's storage value
Support for the javascript-lifecycle mode
Introduction
[SINCE Orbeon Forms 2016.1]
When the javascript-lifecycle
mode is enabled, the following methods should be provided:
init()
destroy()
xformsUpdateReadonly()
NOTE: The XForms engine does not call these methods if they are not present.
On the JavaScript side, the lifecycle of a companion instance does not exactly follow that of the XForms controls when repeats are involved.
For an example, see the implementation of the fr:code-mirror
component.
The init method
The init()
method is called when the control becomes relevant, including:
when the page first loads and the control is initially relevant
when the control becomes relevant at a later time
when a new repeat iteration is added
when
xxf:full-update
orxxf:dynamic
replace an entire block of HTML on the client
The destroy method
The destroy()
method is called when the control becomes non-relevant, including:
when the control becomes non-relevant after the page has loaded
Since Orbeon Forms 2016.1, it is not called:
when a repeat iteration is removed
when
xxf:full-update
orxxf:dynamic
replace an entire block of HTML on the client
The assumption is that, when HTML elements are removed from the browser DOM, the associated JavaScript resources are garbage-collected. This means that you have to be careful about clean-up of event handlers in particular in such cases.
The xformsUpdateReadonly method
The xformsUpdateReadonly()
method is called when the control's readonly status changes.
It takes a boolean parameter set to true
if the control becomes readonly and to false
if the control becomes readwrite.
It is not called just after the control is initialized.
Read-only parameters
[UNTIL Orbeon Forms 2021.1]
So your JavaScript can access the current value of parameters and be notified when their value changes, include the oxf:/oxf/xslt/utils/xbl.xsl
XSL file, and call xxbl:parameter()
function for each parameter, as in:
The arguments of xxbl:parameter()
are:
The element corresponding to your component, e.g. the
<fr:currency>
element written by the user of your component. If your template matches on/*
, this will be the current node.The name of the parameter.
Then in JavaScript, you can access the current value of the property with:
Whenever the value of a parameter changes, a method of your JavaScript class is called. The name of this method is parameterFooChanged
if "foo" is the name of your property. Parameters names are in lowercase and use dash as a word separator, while the method names use camel case. E.g. if your parameter name is digits-after-decimal
, you will defined a method parameterDigitsAfterDecimalChanged
.
Sending events from JavaScript
You can dispatch custom events to bindings from JavaScript using the ORBEON.xforms.Document.dispatchEvent()
function. If you are calling it with custom events, make sure you are allowing the custom event names on the binding first:
Last updated