Child pages
  • Fluid Renderer - Background

Documentation for a historical release of Infusion: 1.3
Please view the Infusion Documentation site for the latest documentation.
If you're looking for Fluid Project coordination, design, communication, etc, try the Fluid Project Wiki.

Skip to end of metadata
Go to start of metadata

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.

On this Page

Inputs to the renderer

The renderer accepts two primary inputs

  1. a block (or several blocks) of markup representing a template, and
  2. 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

<tr id="table-header">
  <th>Count</th><th>Name</th><th rsf:id="header:">1</th><th>Median ave</th>
</tr>

could be self-rendered with the following call

fluid.selfRender($("#table-header"),  {"header:": [1, 2, 3, 4, 5]} );

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

  <table>
    <tr id="table-row">
      <td class="table-data">
        <a class="table-data-link"></a>
      </td>
    </tr>
  </table>

sample hydrated component tree

var hydratedComponentTree = {
    children: [{
        ID: "data:",
        value: "No Link"
    },
    {
        ID: "data:",
        children: [{
            ID: "link",
            target: "#",
            linktext: "Link"
        }]
    }]
};

var selectorMap = [{selector: ".table-data", id: "data:"},
    {selector: ".table-data-link", id: "link"}];
    
fluid.selfRender($("#table-row"), hydratedComponentTree, {cutpoints: selectorMap});

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

{ID: "entry-field",
 value: "this-value",
 }

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:


{ "entry-field": "this-value",
  "field-list:": ["value1", "value"]
}

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 nameField typeField Description
UIComponentIDstringRendering id of the component, which pairs up with the rsf:id field in the template, if selector-based rendering is not being used
UIBoundvaluestring/boolean/Array of stringThe value held within the bound component
valuebindingstringAn EL expression representing the path within any bound data model that the value is to be associated with.
willinputbooleanMarks this component out as performing input as well as output (not yet fully supported)
submittingnamestringA 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)
UILinktargetstringThe URL (href/src, etc) which is the target of this link component
linktextstringThe text value (if any) associated with this link - for example, for an anchor tag, will appear as the link body
UIContainerchildrenArray of UIComponentThe list of contained components in the container
localIDstringDisambiguates multiple UIContainer components with the same branch ID (will be used to compute the full ID of the component when rendered, if required)
UIJointContainerjointIDstringThe 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
UIVerbatimmarkupstring(able)The body of the peering tag will be replaced with the unescaped text held as markup
UIInitBlockfunctionnamestringThe name of the Javascript function to be invoked by the rendered init block
argumentsArray of string(able)A list of arguments to be passed to the rendered function
UISelectselectionUIBound (string or list)The user selection made from the list of alternatives
optionlistUIBound (list)The list of available choices for the selection
optionnamesUIBound (list)The same list of choices, rendered as user-facing strings
UISelectChoicechoiceindexintegerThe index of the choice within the parent UISelect control's selection entry that this component represents
parentRelativeIDstringThe 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
parentFullIDstringThe absolute path (from the component tree root) of the parent UISelect control -- that is, its fullID
UIMessagemessagekeystring/Array of stringMessage key(s) to be resolved to a bundle
argsArray 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:

  fluid.bindFossils = function(node, data, fossils) {
    $.data(node, "fluid-binding-root", {data: data, fossils: fossils});
    },

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:

    function commitChange(node) {
      var newValue = $(node).val();
      fluid.applyChange(node, newValue);
      }

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

  var dataTable = [
    {sku: "thing1", description: "explodes"},
    {sku: "thing2", description: "disinteggrates"}
   ]

we could apply fluid.explode in a loop like this:

    var tree = [];
    for (var i = 0; i < dataTable.length; ++ i) {
      var row = fluid.explode(dataTable[i], i);
      tree[tree.length] = row;
      }
    tree = {"table-row:": tree};

(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:

  {"table-row:": [
    [{ID: "sku",
     value: "thing1",
     valuebinding: "1.sku"},
    {ID: "description",
     value: "explodes",
     valuebinding: "1.description"}
     ],
    [{ID: "sku",
     value: "thing2",
     valuebinding: "2.sku"},
    {ID: "description",
     value: "disinteggrates",
     valuebinding: "2.description"}
     ]
    ]
  }

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:

<tr id="table-header">
  <th>Count</th><th>Name</th><th class="column-header">1</th><th>Median ave</th>
</tr>

In order to use this with the same component tree as before, we can make the following call to selfRender:

  var tree = {"header:": [1,2,3,4,5]};

  fluid.selfRender($("#table-header"), tree, 
        { cutpoints: [{selector: "th.column-header", id: "header:"}]
     });

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

  • No labels