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.
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.
The renderer is part of the main Fluid distribution held in SVN at renderer
|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.
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 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.
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
represents what would be a UIBound instance in RSF Server (strictly, a UIBoundString), by virtue of having an entry named value of type string.
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.
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 |
|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 |
|UIVerbatim||markup||string(able)||The body of the peering tag will be replaced with the unescaped text held as |
|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 |
|parentRelativeID||string||The relative path through the component tree IDs of the parent |
|parentFullID||string||The absolute path (from the component tree root) of the parent |
|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.
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
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 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:
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.
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.
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.
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.
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:
|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|