IntroductionWhy are components needed?The Orbeon Forms XForms engine proposes out of the box a set of controls, including input fields, radio buttons, etc. Those are typically implemented natively within the XForms engine.Beyond the basic set of controls, there is an obvious need for creating new reusable controls. It would be difficult to modify the XForms engine itself. Orbeon Forms therefore proposes a complete framework inspired by the XBL 2 specification to address this need.
See the documentation on already implemented components to get a feel for what is possible. Terminology
Mini-FAQWhat can component do?A lot! Among other things, they can:
What does a component implementation look like?That's the cool part: a component implementation looks very much like regular XForms within HTML!A component is really a way of packaging and reusing a piece of XForms and HTML markup. So when writing a component, you can leverage all your knowledge of XForms and HTML. How do you use a component?This is even easier: each component is assigned an element name. For example, must built-in Orbeon Forms components are in the "fr:" namespace (for Form Runner), and so you write things like:<fr:fields-date ref="my-date">Most components try to follow the XForms philosophy: for example, they use common attributes like ref, and usually allow nesting <xforms:label>, <xforms:help>, etc.Why not implement components in Java?This could be attractive, but then:
Why use XBL and not simply XSLT?Using XSLT to implement components is not always a good alternative:
Using XBL addresses all these issues.
Convinced? If not, read on! In the section about extensions, you'll find out that you can embed XSLT transformations within Orbeon Forms XBL components.
Getting startedCopying an existing componentThe best thing to do is to start looking at existing components:
To create your own component:
Mini-tutorialEncapsulationThe component system favors a strong encapsulation so that components can be:
The default: strong encapsulationBy default, within an<xbl:binding> element, encapsulation is strong: this means that XForms controls, models, and event handlers cannot:
If you place models within <xbl:implementation> or <xbl:template>, the same rule that applies in a top-level XForms document apply:
Why and how to break the encapsulation?There are cases where a component can live in its own little world. For example, consider a component such as the Twitter Timeline component included with Orbeon Forms: it obtains its data through the Twitter API, and simply displays it. It doesn't need to access data or interact with the XForms document in which it is used (although you could imagine such interactions).But in other cases, components need access to the outside world! For example, XPath expressions to evaluate within the XPath context in scope where the component is used. Creating a single-node bindingWith Orbeon Forms 4Orbeon Forms 4 provides direct support for bindings with the binding mode:
This automatically means that the component supports all the XForms binding attributes:
When a component has a binding, UI events are dispatched depending on the bound item:
You can access the actual bound node via the xxforms:binding() function:
The id passed must be the id of the xbl:binding element.The xxforms:binding-context() function returns the XPath evaluation context of the binding
With Orbeon Forms 3.9Here is how you create a single-node binding in an XBL template:<xbl:template> <xforms:group xbl:attr="model context ref bind" xxbl:scope="outer"> ... </xforms:group></xbl:template>This is what the code does:
Now within that group, you might want to access resources, such as controls, instances, etc., which are local to the component again. To achieve this, you use xxbl:scope="innner":<xforms:group xxbl:scope="inner"> ....</xforms:group>Now one issue with this is: how do you get, from an inner scope, access to something in the outer scope? Typically, you do this with variables: <xforms:group xbl:attr="model context ref bind" xxbl:scope="outer"> <xforms:group xxbl:scope="inner"> <!-- Variable pointing to external single-node binding --> <xxforms:variable name="binding" as="node()?"> <xxforms:sequence select="." xxbl:scope="outer"/> </xxforms:variable> ... </xforms:group></xforms:group>The variable named binding above:
NOTE: You notice, based on the example above, that XBL itself doesn't know anything about single-node bindings: instead, you create such bindings using a group, XForms constructs, and XBL extensions. Adding support for a value[SINCE: 2012-11-20] Some native XForms controls, like xforms:input, xforms:textarea, etc. support holding a value. This means in particular that the control is able to dispatch xforms-value-changed events. Some controls on the other hand, like xforms:group, xforms:repeat, can't hold a value and don't dispatch xforms-value-changed events.The default for XBL components is that they don't hold a value. Use the value mode to add value behavior:
The xxforms:value() function allows access to the control's value from the inside, by using the binding id:
This is not supported with Orbeon Forms 3.9. Adding LHHA elementsOrbeon Forms 4 provides direct support for label, help, hint and alert (LHHA):
This automatically adds support for LHHA to the component:
By default, markup is output for the LHHA elements. You can disable this with the custom-lhha mode:
With this mode, no markup is output, and the component author can access the LHHA values with XPath functions:
With Orbeon Forms 3.9Typical XForms controls feature<xforms:label>, <xforms:help>, (shortened to LHHA for "label", "help", "hint", and "alert") etc. You can easily copy the ones specified by the user within the XBL component:<xforms:group xbl:attr="model context ref bind" xxbl:scope="outer"> <xbl:content includes="xforms|label,xforms|help,xforms|hint,xforms|alert"/>What this does is place the elements within the group. If the user writes: <fr:cool-stuff ref="my-node"> <xforms:label>Enter your philosophical thought</xforms:label></fr:cool-stuff>The result, within the XBL component, will be: <xforms:group ref="my-node" xxbl:scope="outer"> <xforms:label>Enter your philosophical thought</xforms:label> ...</xforms:group>NOTE: <xbl:content> uses a CSS selector notation instead of XPath.A basic componentYou can find the component discussed in this section in the Orbeon Forms distribution:
With Orbeon Forms 4Orbeon Forms 4 modes make this very simple:
With Orbeon Forms 3.9A super-simple component template with just one<xforms:input> will look like this:<xbl:template>To complete that component, you must place it within an XBL file, complete with namespace declarations: <xbl:xbl xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xxforms="http://orbeon.org/oxf/xml/xforms" xmlns:fr="http://orbeon.org/oxf/xml/form-runner" xmlns:xbl="http://www.w3.org/ns/xbl" xmlns:xxbl="http://orbeon.org/oxf/xml/xbl"> <xbl:binding element="fr|tutorial-input"> <xbl:template> ... </xbl:template> </xbl:binding></xbl:xbl>In the above:
The Orbeon XBL files are placed undeer the xbl/orbeon directory and in the "fr:" namespace and are automatically included. However your own XBL files need to be included explicitly in your page, e.g.: <xhtml:head> <!-- Main XForms model --> <xforms:model/> <xi:include href="oxf:/xbl/acme/cool-stuff/cool-stuff.xbl" xxi:omit-xml-base="true"/></xhtml:head>Using local stateYou can find the component discussed in this section in the Orbeon Forms distribution:
How would you go about this? The binding between an XForms control and instance data is direct: the value entered by the user is stored into the instance as soon as the user moves out of the field, and you can't just write a transformation inbetween. So we need some intermediate state to store the value entered by the user. To do so, we create a local instance. You can put it under the <xbl:implementation> or <xbl:template> elements in your XBL file:
Here we store a single value, so we just use a single root element in the instance: <value/>.The local input field just points to the local instance instead of pointing to the external single-node binding:
So here, the input field points to the <value> element. NOTE: You could also write ref=".", which would work because, like at the top-level of an XForms document, the default XPath context is the root element of the first instance in the first model. Using instance() is a bit more explicit.What is needed now is, when the local value changes, to copy it to the external single-node binding. You do so with an event handler:
What's missing now is to reverse the value:
Finally, the opposite operation is needed: when the component first comes to life, and when the external value changes, the internal value must update:
So here you go: you have a fully working non-trivial component:
XForms modelsPlacement of local modelsA component can have its own set of XForms models, called local models. For each instance of the component, a new copy of the models is made, so that component instances behave completely independently from each other.With XForms 1.1, the standard convention is to place your models under the <xhtml:head> element (even though some new model scoping rules are being developed for Orbeon Forms). An XBL component does not have an <xhtml:head> element, so Orbeon Forms supports placing models in two places:
<xbl:implementation> is to place new methods and properties. Orbeon Forms uses XForms as the implementation or "scripting" language of XBL components, so it does not have methods and properties. But models, through events, can implement behavior, so allowing model placement under <xbl:implementation>seems to fit the intent of XBL.Local model-related eventsXForms 1.1 specifies the following event sequence upon value changes, insertions, etc.:
XBL components with local models are no different and they receive these events when needed. Construction and destruction of local modelsIt is possible to use components within repeats. These rules apply:
Event handling
Event propagationXBL promotes a strong encapsulation of data and behavior. In particular events which target elements within the component typically are invisible to the component user.With Orbeon Forms 4[SINCE 2012-06-08]
With Orbeon Forms 3.9With Orbeon Forms 3.9 up to 2012-06-08 builds, the following applies:
The action handler above observes events going through element <foo:bar>, assuming <foo:bar> has an XBL binding.Similarly, the following observes events targeted directly to <foo:bar>:
Note that to achieve this the XBL engine does some special work here:
With Orbeon Forms 4.0, you can disable this automatic processing of nested event handlers, although you should only need this for very special components:
It is also possible to attach handlers by id, like with any XForms control: <foo:bar id="my-foobar"> ...</foo:bar>...<xforms:action ev:event="xforms-value-changed" ev:observer="my-foobar"> ...</xforms:action>NOTE: As of 2009-10, with Orbeon Forms, only standard XForms controls and elements which have an XML binding can be used as event observers. Other elements, such as an HTML <div>, cannot be event observers. Some components, such as a tabview, in effect behave like XForms grouping controls (like xforms:group, xforms:switch/case, xforms:repeat). With such components, a lot of content is typically nested under the bound node:
It is up to the component author to handle nested content properly. When using xbl:content, the XBL engine does the work for you:
Component author: hooking-up creation and destruction event handlers
Since xforms-ready is not dispatched to local models, here is how you can register handlers to perform initializations when the component is created and destroyed: <xbl:template>
<xforms:group id="component-group">
<xforms:action ev:event="xforms-enabled" ev:target="component-group">
<!-- Perform construction here --> <xxforms:script>...</xxforms:script>
</xforms:action>
<xforms:action ev:event="xforms-disabled" ev:target="component-group">
<!-- Perform destruction here -->
<xxforms:script>...</xxforms:script>
</xforms:action>
... Rest of component ...
</xforms:group>
</xbl:template>Note the ev:target attributes, which ensure that only events actually targeting this particular group are handled. If you omit that attribute, you might observe more than one event for creation or destruction, which is in general not desired.
Component author: dispatching events from within the component This allows a component to send information to the outside world. A component can dispatch events to its bound element by using xforms:dispach and using the id of the xbl:binding element as target. Example: <xbl:binding id="foobar-component" element="fr|foobar"> <xbl:template> <!-- Local controls --> <xforms:trigger id="internal-trigger-1"> <xforms:label>Dispatch outside</xforms:label> <xforms:dispatch ev:event="DOMActivate" name="my-event" target="foobar-component"> <xxforms:context name="fr:my-context" select="42"/> </xforms:dispatch> </xforms:trigger> </xbl:template></xbl:binding>The component user can listen to this event as expected, for example: <fr:foobar id="my-foobar"> <xforms:message ev:event="my-event"><xforms:output value="concat('Got it: ', event('fr:my-context'))"/></xforms:message></fr:foobar>The use of the "fr:" prefix in the event context information is not mandatory: you can use your own namespace prefix. However, it is good practice to use a prefix so as to prevent name conflicts with standard XForms event context information. Component author: listening for events dispatched to the componentThis allows a component to receive information from the outside world.You can register event handler attached to the bound node inside your component with the xbl:handlers/xbl:handler elements: <xbl:binding id="fr-bar" element="fr|bar"> <xbl:handlers> <!-- Handlers are attached to the bound node --> <xbl:handler event="my-event" phase="target"> <xforms:setvalue model="model" ref="value1" value="event('fr:one')"/> <xforms:setvalue model="model" ref="value2" value="event('fr:two')"/> </xbl:handler> </xbl:handlers>The xbl:handler element looks very much like an xforms:action element. In particular, it supports the following attributes:
[TODO: In JavaScript, we can only dispatch events to a model. So will dispatch to dispatch the event to a model inside the component (see below for more on that), which in turn can used the method above to dispatch the event to the component.] Component user: dispatching events to the componentThe following example responds to a button being activated and dispatches an event with custom context information to an fr:bar component:<fr:bar id="my-bar"/><xforms:trigger> <xforms:label>Insert</xforms:label> <xforms:dispatch ev:event="DOMActivate" name="my-event" targetid="my-bar"> <xxforms:context name="fr:one" select="'Red'"/> <xxforms:context name="fr:two" select="'Blue'"/> </xforms:dispatch></xforms:trigger>When the event my-event reaches the component, it activates the event handler registered with xbl:handler. That handler has access to the custom context information using the event() function. Event handlers are attached to the bound node but they execute within the context of the component, which means that they have access to XForms elements declared within the component. This includes:
CSS selectorsThexbl:content element allows copying elements which are descendant elements from the bound node. The selectors work as if applied to an XML document rooted at the bound node.Say you have this markup: <fr:inline-input ref="name">
<xforms:label>Name</xforms:label>
</fr:inline-input>The implementation of the fr:inline-input component can copy the nested xforms:label element under a group as follows:<xforms:group> <xbl:content includes="xforms|label"/>The CSS selection expression above is exactly equivalent to writing in XPath: descendant-or-self::xforms:labelNote the difference of notation in XML/XPath and in CSS to refer to qualified element names:
Now here is a more complex scenario: <fr:link-select1 ref="gender"> <xforms:label>Gender</xforms:label> <xforms:itemset nodeset="instance('genders')/gender"> <xforms:label ref="label"/> <xforms:value ref="value"/> </xforms:itemset></fr:link-select1>You would think that the implementation of the fr:link-select1 component could simply copy the nested xforms:label element as follows:<xforms:group> <xbl:content includes="xforms|label"/>xforms|label actually returns all descendant label elements, including the xforms:label element under xforms:itemset.As of 2012-06-05, the recommend way to express this is as follows:
The :root pseudo-class refers to the bound element (here fr:link-select1). The > combinator "describes a childhood relationship between two elements", like the XPath / axis. So the result is equivalent to the XPath:
NOTE: Prior to 2012-06-05 builds, you had to write the following:
The drawback of that method was that the CSS expression could catch nested fr|link-select1 elements. This was not an issue with fr|link-select1 specifically, but could be an issue in other cases, such as fr|tabview, which allows nested content. Further, repeating the name of the bound element was error prone.Level of supportSupported element and attributes from the XBL 2 specification:
Unsupported element and attributes from the XBL 2 specification:
ExtensionsAll the generic extensions are in the namespacehttp://orbeon.org/oxf/xml/xbl, and the usual mapping of this namespace is xmlns:xxbl="http://orbeon.org/oxf/xml/xbl".xxbl:container attributeThe xxbl:container attribute on xbl:binding allows specifying the name of the HTML element that the XBL binding uses to encapsulate content. By default, this is xhtml:div. Here is how to change it to xhtml:span:
xxbl:attr="xforms:alert/(@context | @ref | @bind | @model)"xbl:template/xxbl:transform attributeWhen more flexibility is needed than XBL can provide, the xxbl:transform attribute is your friend.<xbl:template xxbl:transform="processorName"> Transformation (in line)</xbl:template>config input to the content of the xbl:template and its data input to the bound element and replaces the content of the xbl:template by the data output of the processor. The most frequent expected use is to run XSLT transformations. For instance, to create a widget that alternates styles in table rows within an xforms:repeat:<xbl:binding id="foo-table-alternate" element="foo|table-alternate"> <xbl:template xxbl:transform="oxf:xslt"> <xsl:transform version="2.0"> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*[not(name() = ('style1', 'style2'))]|node()"/> </xsl:copy> </xsl:template> <xsl:template match="foo:table-alternate"> <xhtml:table> <xsl:apply-templates select="@*|node()"/> </xhtml:table> </xsl:template> <xsl:template match="xforms:repeat/xhtml:tr" > <xxforms:variable name="position" select="position()"/> <xforms:group ref=".[$position mod 2 = 1]"> <xhtml:tr xbl:attr="style=style1"> <xsl:apply-templates select="@*|node()"/> </xhtml:tr> </xforms:group> <xforms:group ref=".[not($position mod 2 = 1)]"> <xhtml:tr xbl:attr="style=style2"> <xsl:apply-templates select="@*|node()"/> </xhtml:tr> </xforms:group> </xsl:template> </xsl:transform> </xbl:template></xbl:binding>This widget can be invoked through: <foo:table-alternate class="gridtable" style1="background: red" style2="background: white"> <xhtml:tr> <xhtml:th>Label</xhtml:th> <xhtml:th>Value</xhtml:th> </xhtml:tr> <xforms:repeat nodeset="item"> <xhtml:tr> <xhtml:td> <xforms:output value="@label"/> </xhtml:td> <xhtml:td> <xforms:output value="@value"/> </xhtml:td> </xhtml:tr> </xforms:repeat></foo:table-alternate>The xbl:template/@xxbl:transform="oxf:xslt" attribute specifies that its child element (xsl:transform) is considered as an XSLT transformation, runs against the bound element (foo:table-alternate), and the result of this transformation is used as the actual content of the xbl:template.The result of your transformation will often contain XBL attributes, and in this sample xbl:attr attributes are used to set the style of the rows with: <xhtml:tr xbl:attr="style=style1">.The result of the transformation has to be a (well formed) single rooted XML fragment. This might seem obvious, but that means that you might have to encapsulate the sub elements of xbl:template in an XHTML div or span compared to what you would have done if you were not applying a transformation.The transformation has full access to the bound element and can transform any of its child nodes or attributes. This is the case even when bound elements (or widget references To keep things as encapsulated as possible and not change the behavior of bound elements that are potentially embedded, it is a good practice to define tight transformation templates that affect only the nodes that are meant to be transformed. In the example given above, one might for instance argue that <xsl:template match="foo:table-alternate/xforms:repeat/xhtml:tr"> would be safer than <xsl:template match="xforms:repeat/xhtml:tr">.You can use <xsl:message terminate="yes"> to report to the user errors that occur during the XSLT transformation. For example:<xsl:message terminate="yes">Terminating!</xsl:message>This results in an error will be output in the log, and the following error message will show in the browser: ![]() xxbl:global elementThe <xxbl:global> element allows an XBL binding to place global markup that is included in a page only once.
How this works:
The component can dispatch events to global controls if the outer scope is the top-level scope, with the xxbl:scope="outer" attribute. For instance, if in the <xxbl:global> you defined an <xxforms:dialog id="my-dialog">, then from within the component, you can run the following action:
NOTE: A future enhancement to this feature might restrict id and XPath scope of global markup to that of the XBL binding. xxbl:mirror attribute[SINCE 2013-01-23] This attribute, placed on a local XForms instance, tells the XBL engine to automatically mirror changes between that instance and the XBL component's bound node. For mirroring to work, the XBL component must either:
At most one instance in a given XBL component may have xxbl:mirror="true".Lifecycle:
Example:
ConventionsWhenever it is possible, XBL components should follow patterns found in XForms controls. For instance, if it makes sense to think that the component is bound to a node, then the component should support single node binding attributes on the component element, just like an XForms control would. ParametersA number of component take "parameters" that can be specified by users. Consider the existing date picker component. You bind it to a node which contains the date entered by the user, but can also provide a minimum and maximum date. We call those min/max dates parameters. Parameters can be:
JavaScript Define a class for your componentThe XBL component shipped with Orbeon Forms are stored in their own directory under/xbl/orbeon. For instance, all the files for the currency component are under /xbl/orbeon/currency. To include a JavaScript file, use the <xbl:script> element directly inside the <xbl:xbl>:<xbl:xbl> <xbl:script src="/xbl/orbeon/currency/currency.js"/> <xbl:binding id="fr-currency" element="fr|currency"> .... </xbl:binding></xbl:xbl>In the JavaScript file corresponding to your component, declare a class as follows: YAHOO.namespace("xbl.fr");YAHOO.xbl.fr.Currency = function() {};ORBEON.xforms.XBL.declareClass(YAHOO.xbl.fr.Currency, "xbl-fr-currency");YAHOO.xbl.fr.Currency.prototype = { attribute1: null, attribute2: null, init: function() { ... }, valueChanged: function() { ... }, ...};
Call methods of your class on XForms eventsYou can call a JavaScript method defined in your JavaScript class when an XForms event occurs. For instance, to call theinit() method when on xforms-enabled, write:<xxforms:script ev:event="xforms-enabled">YAHOO.xbl.fr.Currency.instance(this).init();</xxforms:script>The instance() method acts as an object factory for your component: it returns an instance of your class corresponding to the "current" component. It creates an instance of your class as necessary, and keeps track of existing objects, maintaing a 1-to-1 mapping between instances of the XBL component in the form and instance of your JavaScript class.Read-only parametersSo your JavaScript can access the current value of parameters and be notified when their value changes, include theoxf:/oxf/xslt/utils/xbl.xsl XSL file, and call xxbl:parameter() function for each parameter, as in:<xbl:xbl> <xbl:script src="/xbl/orbeon/currency/currency.js"/> <xbl:binding id="fr-currency" element="fr|currency"> <xbl:template xxbl:transform="oxf:unsafe-xslt"> <xsl:transform version="2.0"> <xsl:import href="oxf:/oxf/xslt/utils/xbl.xsl"/> <xsl:template match="/*"> ... <xsl:copy-of select="xxbl:parameter(., 'prefix')"/> <xsl:copy-of select="xxbl:parameter(., 'digits-after-decimal')"/> ... </xsl:template> </xsl:transform> </xbl:template> </xbl:binding></xbl:xbl>The arguments of xxbl:parameter() are:
var prefixElement = YAHOO.util.Dom.getElementsByClassName("xbl-fr-currency-prefix", null, this.container)[0];var prefix = ORBEON.xforms.Document.getValue(prefixElement.id);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[Since 2012-04-09] 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:
Aspects of components[IN PROGRESS]There are several types of components:
Other topics to address[IN PROGRESS]
|

