Projects‎ > ‎XBL‎ > ‎

XBL Components System (Implementation Notes)


This page gathers ideas about implementing component system in Orbeon Forms's XForms engine. [NOTE: MOST OF THIS IS ALREADY IMPLEMENTED AS OF MAY 2009]

See also the XBL Components Guide.

Strategy

  • Components are based on XBL 2.
  • If necessary, extensions will be built (e.g. copying nested attributes, which is not supported by xbl:attr), but if not, sticking with XBL 2 would be good.
  • Components are written in XForms. We want to avoid using Java or JavaScript scripting.
  • Components handle their own XForms single-node or node-set bindings:
    • the XForms engine does not know, just by looking at a bound node, whether the component uses plain XHTML or uses XForms.

Resource resolution within components

Requirements
  • Handling of xforms:label element within the bound element
    • Must be possible to copy it to within a group within the component
    • Resolution of single-node or node-set bindings must be possible, including @model, @bind, and reference to variables
  • Encapsulation: components can be developed in isolation, to maximize reuse
  • Communication is possible through a clean interface: events
    • listening on events dispatched to bound node from the outside
    • dispatching events to bound node from the inside
Example:

<foo:bar model="my-model" ref="name">
    <xforms:label ref="$resources/name/label"/>
    <xforms:help model="resources-model" ref="name-help"/>
    <xforms:hint ref="../my-hint"/>
</foo:bar>

Within the component:

<xforms:group ... how to bind the group to the single-node binding expressed on bound node? ...>
    <xbl:content includes="xforms|label, xforms|help, xforms|hint"/> ... how to ensure values are properly evaluated ...
    ...
</xforms:group>

Issues:
  • Id resolution
    • Is there any visibility from within the component to the outside? No.
    • Is there any visibility from outside the component to the inside? No.
    • -> how to solve resolution of @model and @bind in copied xforms:label element?
  • XPath context handling
    • Is the external XPath context "transparent", i.e. seen from within the component (including variables)? For now: yes. Long-term: hopefully no.
    • -> how to solve resolution of reference to variables within xforms:label element?
Possible solutions:
  • For context on xforms:group
    • Use of an extension function, e.g.: xxforms:component-nodeset()
      • -> this functions evaluates the single-node or node-set binding attributes on the bound element and returns a node-set
      • doesn't solve the problem of variable resolution!
  • For copied elements
    • copied elements are flagged specially
      • implementation: could be done with a Map<String id, Element element>
      • possibly: this flagging could be optionally disabled with an extension attribute on xbl:content
    • when copied elements are evaluated in context stack, they evaluate
      • within the context of the bound element,
      • including resolving attributes on bound element (or any ancestor element)
Within the component:

<xforms:group ref="xxforms:component-nodeset()">
    <xbl:content includes="xforms|label, xforms|help, xforms|hint"/>
    ...
</xforms:group>

Following are unattractive solutions because they break encapsulation:
  • allow transparency of id resolution from within the component
  • keep allowing transparency of XPath context from within the component

Dispatching events to/from components in JavaScript

We need to extend the current ORBEON.xforms.Document.dispatchEvent() to allow both a modelID and a componentID to be passed (right now there is only a targetID which is the modelID). To preserve backwards compatibility we could add the componentID as the last argument:

dispatchEvent: function(modelID, eventName, form, bubbles, cancelable,
                        incremental, ignoreErrors, componentID)


First implementation steps [MOSTLY DONE]

Goals:

  • P1: allow simple components without local models and instances [DONE]
  • P2: allow implementing a widget for xs:duration [DONE for date, phone, and other examples]

Requirements:

  • Detect XBL PI to identify an XBL document [NOT DONE, WE USE XInclude for now]
  • Load up XBL document and keep track of the contained xbl:binding elements. [DONE]
  • When loading XForms document, match elements against XBL binding [DONE]
  • Process XBL bindings to generate shadow content [DONE]
  • Create flattened tree [DONE]
    • P1: xbl:template [DONE]
    • P2: xbl:attr to forward @ref [DONE]
    • P2: xbl:content to forward elements [DONE partially with simple CSS selectors]
  • Handle local model and ids under xbl:implementation [DONE]
      • NOTE: xbl:implementation does not support xbl:attr or xbl:content, so models may be put directly under xbl:template
    • currently models and controls are only supported on containing document [DONE]
    • handle instantiation of models when used in repeats [DONE]
  • xbl:handlers/xbl:handler [DONE]
  • Need an extension to determine return value of component for input components? [DONE]
  • Event forwarding in and out [DONE]

Future thoughts

  • In the future, we may want to use the component system to implement some of the built-in control appearances, e.g. xforms:select1 as tree or menu, etc. [TODO]

Implementation log

  • Annotator (XFACH)  annotates contents of xbl:xbl subtrees as well
  • Extractor extracts xbl:xbl with everything preserved under xbl:xbl
  • Limitation: for now, it is necessary to manually put ids on elements to which components are bound, e.g. <foo:bar id="my-foo-bar">.
    • This is because XFACH does not know, when adding ids, whether foo:bar is handled by a component or not. Short term is ok, but inconvenient. [TO FIX]
  • XFECH for now considers any element not in the XHTML namespace to be a potential component.
    • This is because again we do not know whether foo:bar is handled by a component or not. Short term is ok as we use only XHTML and XForms elements, but to fix later. [TO FIX]
  • XFormsComponentControl represents a custom component in the tree of controls in XFormsControls
  • XFormsControlFactory is able to use XFormsStaticState to create instances of XFormsComponentControl
  • XHTMLBodyHandler registers handlers for all elements that have bindings
  • XFormsComponentHandler outputs an enclosing <div> and then plays the expanded template obtained from XFormsStaticState
  • Controls get "effective ids" prefixed by the bound element id, e.g. my-foobar1$my-input, or my-foo$my-bar$my-input
    • XFormsControls handles id prefixes
    • HandlerContext handles id prefixes
  • XML events do not catpure / bubble outside of component boundary
  • foo:bar/@ref must not be handled by XFormsControls (don't push the binding)
  • attribute forwarding: xbl:attr="ref", xbl:attr="ref foo:bar=a:b xbl:text=a:b foo:bar=xbl:text"
  • expanded XBL templates (shadow trees) have two versions
    • with non-XForms markup (full shadow tree)
    • without non-XForms markup (compact shadow tree)
    • -> we must keep the full shadow tree in XFormsStaticState for subsequent page renders (see explanations below)
  • nested XForms models are extracted from shadow trees
    • model are stored in a Map by "prefixed id", e.g. my-component$my-model
    • however "id" attributes within models, instances, etc. are not touched
  • id resolution
    • handle ids used by:
      • actions (@control, ...)
      • controls (@model, @bind)
      • XPath functions (instance(), ...) 
    • visibility:
      • ids referred to from within a component must be within that component strictly
      • a component cannot see outer models or controls
      • a component cannot see inner models or controls either
      • special function to allow component to bind to the outside: xxforms:component-context()
  • XFormsContainer
    • contains
      • models
      • XFormsContextStack
    • XFormsContainer hierarchy
      • start at XFCD which derives from XFormsContainer
      • then points to children, children point to parent
      • created
        • is not rebuilt as controls are rebuilt
        • containers are added on-demand, when building component tree
          • -> with model initialization
        • containers also added on-demand when restoring dynamic state
          • -> with just model restoration
    • each concrete component has separate XFormsContainer
    • each control has a reference to its current XFormsContainer
    • each model has a reference to its current XFormsContainer
    • @model and @bind resolution works because bindings are handled with local XFormsContextStack
    • accessing context outside the component
      • implemented xxforms:component-context()
      • if a component doesn't have local models, inherit the context of the parent
    • dynamic state
      • serialization recurses through containers
      • restoration creates containers as it goes, creating nested models to restore state
    • deferred event handling recurses into containers
    • event dispatch has been moved to XFormsContainer
    • fixed dispatching of events through component (do not go through!)
  • Instance inspector
    • works as XBL component
    • refactored to use variables
  • Reworked annotator (XFDA)
    • Keeps track of bound element names; this means XBL must be included before any binding is used
    • Adds automatic id on bound elements if necessary
    • Skips processing of contents of bound element: this will be done by XBL processor
    • XBL processor uses annotator to annotate shadow trees after transformation
    • Annotator also now handles better id conflicts with auto ids
  • Handling of components with local models within repeat implemented
TODO P1:
  • Implement support for flexible model placement in Orbeon Forms
    • with model scoping, models could be put onto XFormsContextStack, like variables
    • this is mostly independent from XBL
    • as if xforms:model is itself a control
    • actually xforms:model behaves more like xxforms:variable
  • Implement new strategy (see "Resource Resolution Within Components") for
    • binding to external context
    • handling of copied labels, etc.
  • ...
TODO P2:
  • search for "TODO: Nested containers" [STILL THERE AS OF 2009-02-27]
    • handling of models in offline mode
    • setindex action
    • xxforms:list-models()
    • schema dependencies in XFormsToXHTML
  • BUG: if an id appears in an XBL template, and that id also appears within the body of the document, XFACH throws an exception
    • -> must have a separate id space for each binding [STILL AN ISSUE?]
  • Event handling
    • retargetting of DOMFocusIn/Out
  • ...
  • Check: xml:base handling on shadow trees?
  • Check: location data on shadow trees?

Note about static state and shadow trees

Static state document contains:
  • Full xbl:xbl, including non-XForms markup
Static state object (XFormsStaticState) contains:
  • Full shadow trees
  • Compact shadow trees (created from full shadow trees)
The general strategy of XFormsToXHTML before XBL was to:
  • Not store non-XForms markup, except where needed (instances, schemas, labels, ...)
  • For initial rendering, temporarily store the whole input document into a SAXStore, and then replay it once and discard the SAXStore
  • In Noscript mode, the whole input document is kept in the state though, as this is needed for further renderings
Now with XBL, the non-XForms markup in xbl:xbl is kept. The reason for the change is that the full shadow trees, created out of xbl:xbl/xbl:template, are needed for rendering. In case the static state is cached, those full shadow trees musts be available in the static state. Ergo full xbl:xbl must be kept in the static state document, even though after a single page render full shadow trees are no longer needed for that page (but may be needed by further pages using the same cached XFormsStaticState).

We can't really apply to component subtrees the same strategy we apply to the main tree, as the shadow trees require template processing. In order to change that, we would need to unroll all the shadow trees into the controls document, and then only keep compact xbl:xbl trees. But this would require processing XBL components before reaching XFECH, which seems like a big change at the moment.

Conclusion: static state document for now keeps the full xbl:xbl, and XFormsStaticState keeps the full and compact shadow trees.

ċ
duration.xbl
(7k)
Alessandro Vernet,
Aug 3, 2008, 11:16 AM
Comments