The Fluid/RSF Renderer
|
The Fluid Renderer does the work of rendering a lightweight "component tree" (a pure JSON structure) against a markup template in pure HTML to produce a user interface. This is part of an overall strategy of delivering server-neutral and markup-agnostic renderings of components, as well as operating a data binding approach that automates event handling and model updates. Note that this renderer differs from other markup processors or component frameworks in that it i) renders interfaces, that is, encoding dynamic and data binding functions, as opposed to simply processing markup, but at the same time ii) does not imply a stateful component model – the "component tree" supplied to the renderer has no lifetime after the rendering process. This renderer operates on markup templates which are identical in format to those accepted by the IKAT renderer as part of the RSF Java server-side framework. These templates differ from pure HTML only by the insertion of a single attribute, rsf:id. Despite being algorithmically equivalent to the historical server-side renderer, the Fluid renderer has no dependence or requirement on a particular server-side infrastructure, and can even perform rendering directly from the filesystem. As a (currently) client-side specific enhancement, the renderer is also capable of operating on completely pure HTML templates, with the id structure "imputed" onto the document by a set of selectors. The renderer operates its own selector engine, accepting a subset of that supported by CSS/JQuery, and completes the DOM-agnosticism and markup-friendliness that Fluid has always tried to adopt towards component development. |
Inputs to the renderer
The renderer accepts two primary inputs
- a block (or several blocks) of markup representing a template, and
- a "component tree" representing the instructions to the renderer on how to transform the markup and bind it to its model.
Optionally, the renderer can accept a 3rd argument being a list of "cutpoints" or named selectors, which impute an id structure onto the template.
Current packaging of the renderer
The renderer is part of the main Fluid distribution held in SVN at renderer
| fastXmlPull.js | An optimised and customised implementation of an XML (actually now HTML) pull parser, in pure Javascript (no further dependencies) |
| fluidParser.js | Operates the HTML pull parser to transform the markup template into a "half-baked" structure of "XMLLump" objects with mapping info. Also contains the "selector engine". |
| fluidRenderer | Performs actual rendering of the markup and component tree - as well as definitions for renderer itself, has definitions for the "components" and various drivers (AJAX and otherwise) for the renderer. |
The renderer depends on the core Fluid framework file Fluid.js.
Invoking the renderer
The simplest invocation of the renderer is via "self-rendering" - that is, using an already existing DOM node in the current document as a template for rendering against itself. This uses the fluid.selfRender driver. As a very simple example, the following markup
could be self-rendered with the following call
The first argument to fluid.selfRender may be either a raw DOM node, or else a JQuery object representing one (although there is no hard dependency between fluidRenderer.js and the JQuery framework). The second argument is a Javascript Object representing a component tree, for which the accepted syntax is documented here.
Component trees
The component trees accepted by the client-side renderer are direct equivalents of those handled by the RSF server-side framework - the full set of server-side components is documented on the Primitive Components page. The majority of these are now implemented on the clientside. Whilst there are no plans current to port the RSF Decorator infrastructure directly to the client, the Fluid renderer does have its own rich set of Decorators which are tailored for work on the client side. The currently implemented set consists of UIBound (covering the subset of its descendents of UIBoundBoolean, UIInput, UIOutput and UIBoundList), UISelect (with UISelectChoice), UILink, UIVerbatim, UIBranchContainer, UIJointContainer and UIInitBlock.
Component Tree: ID syntax and structure
To represent a repeating element use a ":" at the end of the id name. (e.g. "repeatingID:")
Branches of a component tree are represented by these repeating elements, while leafs are none repeating elements. The DOM agnostic nature of the Renderer allows you to only have to specify those leaf nodes which are are to be rendered differently than the default in the template.
sample markup template
sample hydrated component tree
Duck typing and duck compression
Duck inference for components
Javascript is supplied with a native "object-oriented" system based on prototype and this, but this is unsatisfactory and deprecated for Fluid development. RSF components represent a data-driven and finite hierarchy, and thus are suited to "structural inference" aka "duck typing", whereby the members detected by name in a raw Object allow the framework to infer component types directly. For example, the following structure
represents what would be a UIBound instance in RSF Server (strictly, a UIBoundString), by virtue of having an entry named value of type string.
Duck compression
In Javascript's completely flexible type system, we can represent component trees in simple cases in forms even closer to pure JSON than the form above. In particular, for the UIBound/UIBranchContainer family, where no input is required, we can accept arbitrary hashes (Objects) as compresssed representations of component trees, where the hash key represents the "ID" field in the above, and the hash value represents the "value". For example, an object representing three UIBound components (one of which is identical with the one above) could be as follows:
For more complex components (or those with name collisions between IDs and the duck inference fields), the "full" form of the previous section still needs to be used - however the very useful auto-expander RSF.explode may be used to convert raw JSON data structures into fully expanded and properly bound component trees, described in its own section below.
Supported component set
This table shows the supported component "types", together with the duck fields which are used to detect them, along with any other fields which are recognised and "type" information for these fields. The "duck fields" are shown in the first row of information for each type, and highlighted in background.
| Component "type" | Field name | Field type | Field Description |
|---|---|---|---|
| UIComponent | ID | string | Rendering id of the component, which pairs up with the rsf:id field in the template, if selector-based rendering is not being used |
| UIBound | value | string/boolean/Array of string | The value held within the bound component |
| valuebinding | string | An EL expression representing the path within any bound data model that the value is to be associated with. | |
| willinput | boolean | Marks this component out as performing input as well as output (not yet fully supported) | |
| submittingname | string | A name for this component which is unique within its "submitting unit" (in plain HTML, a form, and will become written as the "name" attribute of the element) | |
| UILink | target | string | The URL (href/src, etc) which is the target of this link component |
| linktext | string | The text value (if any) associated with this link - for example, for an anchor tag, will appear as the link body | |
| UIContainer | children | Array of UIComponent | The list of contained components in the container |
| localID | string | Disambiguates multiple UIContainer components with the same branch ID (will be used to compute the full ID of the component when rendered, if required) | |
| UIJointContainer | jointID | string | The component represents a "forced branch" between the current location in the template with id of ID and a different location (perhaps in a different template) with id jointID |
| UIVerbatim | markup | string(able) | The body of the peering tag will be replaced with the unescaped text held as markup |
| UIInitBlock | functionname | string | The name of the Javascript function to be invoked by the rendered init block |
| arguments | Array of string(able) | A list of arguments to be passed to the rendered function | |
| UISelect | selection | UIBound (string or list) | The user selection made from the list of alternatives | optionlist | UIBound (list) | The list of available choices for the selection | optionnames | UIBound (list) | The same list of choices, rendered as user-facing strings |
| UISelectChoice | choiceindex | integer | The index of the choice within the parent UISelect control's selection entry that this component represents |
| parentRelativeID | string | The relative path through the component tree IDs of the parent UISelect control -- this may begin with path segments ..:: indicating an upward step through the tree | |
| parentFullID | string | The absolute path (from the component tree root) of the parent UISelect control -- that is, its fullID | |
| UIMessage | messagekey | string/Array of string | Message key(s) to be resolved to a bundle |
| args | Array of string(able) | Arguments to be interpolated into the message format |
Note that these components do form a (conceptual) "hierarchy", in an arrangement which agrees with that in the Primitive Components page.
Decorators
Every component, regardless of type, may be decorated with any number of decorators, entered into a field named decorators. Like component trees, these may be written out in a "fully hydrated" form with separate type fields, or in many cases can be usefully condensed onto a few or a single object.
For more information, see Renderer Decorators
Data binding
As well as performing "output only" rendering of data, the fluid renderer is also capable of managing a (bidirectional) association between the rendered markup and a data model. The data model is a Javascript object (root) which is (optionally) supplied at render-time, is processed alongside the component tree, and "bound" into the rendered markup.
As with "RSF Server", the data model association is bidirectional. Firstly, at render-time, any bound (UIBound) component which has a valuebinding set, but no value initialised, will have the value queried from the model as part of a "fixup". Secondly, as the markup is rendered, the list of fossils (an index of the submittingname field of each bound component, its old value (that is, the value at time of rendering), and the EL path into the model) is rendered as a fossil bolus into the root node of the rendered markup, alongside the physical data model itself.
The fossil bolus
The bolus is currently marked into the DOM by a call to jquery.Data for the root node under a name of fluid-binding-root. The utility function for users who wish to perform this manually (either for advanced purposes, or to rebind the model or fossils) looks as follows:
Using data binding
The fossil bolus makes various often tedious model update procedures extremely simple. Firstly, any updated value arising from a DOM node, can be transparently served by a call to fluid.applyChange - for example, when using the Inline Edit control, an update event handler can simply be written as follows:
By using the autoBind option to rendering, even writing this handler is unnecessary - any changes made in the UI will be automatically reflected in the tree.
Secondly, the bolus can be very easily used to implement "reset/changed value" functionality, where a control can be easily returned to its original condition by backing out the effects of user changes, on a local or global bases by inspecting the fossil record.
fluid.explode for convenient data binding
Writing the EL expressions necessary to specify the data binding required for a number of controls can be verbose and boring, and so the framework includes a utility fluid.explode to automate this for some simple cases. Where we can operate the convention that the id assigned to a set of components should be equal to the key used for matching fields in an Object in the model, we can use fluid.explode to perform simultaneous duck decompression and data binding. For example, given the structure
we could apply fluid.explode in a loop like this:
(note that the above loop could be condensed considerably by use of the utility fluid.transform)
which would produce the following nearly fully decompressed tree, suitable for rendering rows producing both input and output:
It is imagined that lots more "component-building kit" methods will be available
so that the work of JSON transformation from data model to component form will be
as easy and transparent as possible.
Selector-based rendering
One of the possible entries in the opts argument to fluid.selfRender (and also in the more advanced drivers discussed below}} is a named selector list called cutpoints, which allows the use of pure markup (without any rsf:id) structure) to be used as a template. As a simple example, here is our little table header from before, but with a plain CSS class notating the header cell to be repeated, rather than the rsf:id:
In order to use this with the same component tree as before, we can make the following call to selfRender:
The value selector is a standard CSS selector nominating the node which is to "receive" an id, and the id value in each entry in the list is the id that nodes matching the selector are to receive (should this structure perhaps be a hash rather than a list, once we can support comma-separated selectors?)
The selector subset currently supported is basic, but actually accounts for the vast bulk of selectors typically written in practice (as reported by JQuery developers) - we support selectors by id (#), class (.), tag name as well as child (>) and descendent specifiers.
Advanced renderer driving
Going beyond simple calls to selfRender (from which the return value is the parsed template, allowing simple further "re-instantiation" of the templated markup), it is possible to drive the process in a more advanced, manual fashion - this is appropriate especially when rendering with multiple templates.
See Advanced Renderer Driving for information.
Options structure for configuring rendering
Like a Fluid component, the renderer takes an Options structure which is used to configure the details of its rendering process. Unlike options for most Fluid components, this structure sometimes has a bi-directional aspect - it can be used for returning information about the rendering process back to the client, as well as for receiving it. Here is a list of the supported fields and types within the renderer options:
| Field | Description | Type | In/Out |
|---|---|---|---|
| model | Perhaps the most important parameter, contains the "data model" to which value bindings expressed within the tree will be expressed. | free Object | Mostly In, but the supplied model may be written to later as a result of servicing user actions, especially if the parameter autoBind is supplied. |
| autoBind | If set, any user modification of fields with valuebindings s set will immediately be reflected in the current state of the supplied model | boolean | In |
| document | If set, will cause the rendered ids to be uniquified against the supplied document, rather than the current one | document | In |
| debugMode | If set, mismatches between template and component tree will be highlighted in an unreasonable garish pink colour | boolean | In |
| idMap | This map operates in conjunction with the identify decorator which may be attached to nodes in the component tree. Whilst rendering takes place, this map will fill up with a lookup from the supplied nickname to the finally rendered id | free Object | In/Out |
| messageLocator | Configures the lookup from (I18N) messages referenced in the component tree, to some source of a message bundle | function(key, args)->message | In |
| messageSource | Will construct a messageLocator from a raw bundle specification via a call to fluid.resolveMessageSource | MessageSource structure | In |
| renderRaw | Will XMLEncode the rendered markup before insertion into the document. Can be useful for debugging | boolean | In |
| cutpoints | This is properly a directive to the parser, rather than the renderer, but the options structure is shared. This contains a list of pairs of id, selector which will be used to impute an rsf:id structure onto a document, by means of matching the paired selector | Array of Cutpoint object | In |