...
Div | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| |||||||||||||||
|
Div | |||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| |||||||||||||||||||
|
Div | |||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| |||||||||||||
|
...
Both of these things can be avoided by using a renderer component, which is a view component with the addition of the Infusion Renderer.
The Renderer will populate an HTML template with the contents of a data model. In the case of the currency converter, this means that the currency list can be specified only in the data model, and an empty HTML <select>
element will be populated with the data from the model, eliminating the duplication.
The Renderer can also bind the HTML to the data model, so that when users of the currency converter select a different currency or enter a different amount, the content of the data model will automatically be updated without any effort on your part.
Renderer Component Grade
To use the Renderer with your component, you need to use the rendererComponent grade. To Note that the direct use of the Infusion Renderer as described here will be withdrawn in the Infusion 2.0 release. Currently the renderer is in transition to being rewritten as a standard part of the IoC framework rather than requiring special JSON configuration in the form of renderer component trees.
The Renderer will populate an HTML template with the contents of a data model. In the case of the currency converter, this means that the currency list can be specified only in the data model, and an empty HTML <select>
element will be populated with the data from the model, eliminating the duplication.
The Renderer can also bind the HTML to the data model, so that when users of the currency converter select a different currency or enter a different amount, the content of the data model will automatically be updated without any effort on your part.
Renderer Component Grade
To use the Renderer with your component, you need to use the rendererComponent grade. To do this:
- specify a grade of
fluid.rendererComponent
, and - provide the following:
- a data model
- an HTML template
- a component tree
- selectors
Each of these is discussed below.
Data Model
A renderer component supports a model (just as Tutorial - Model Components and Tutorial - View Components do). The model of a renderer component contains any data that is to be rendered by the component.
Templates
A Renderer Template is an HTML file or snippet that provides guidance to the Renderer regarding how to render the data. The template can be provided in one of two ways:
- it can simply be found in the HTML document itself, or
- it can be loaded from a separate file.
Component Trees
A renderer component tree is an abstract representation of how the data is to be rendered. A renderer component (not to be confused with Infusion Components) is a JavaScript object that represent the contents and data binding function of a view, separate from any particular rendering of it.
...
For more detailed information, see Renderer Component Trees
Selectors and Cutpoints
The Infusion Renderer provides a clean separation between model and view; the HTML template and the component tree are relatively independent. For example, if your UI is going to allow users to select from a number of choices, your component tree will define the choices, but will be independent of whether the selection is rendered using checkboxes, a pull-down list, or some other method.
...
For more detailed information, see Cutpoints.
Useful functions and events
In addition to the standard view component features, the renderComponent attaches automatically synthesizes and attaches a refreshView function and an afterRender event.
...
The afterRender event will be fired as soon as the rendering process has finished. This is useful to know when a render component is ready or to attach new events and functions to pieces of the rendered DOM.
Example: Currency Converter
The currency converter example we've been evolving over the course of this tutorial can be implemented as a rendererComponent. As in the view component version, you need to specify selectors and a model, and can define events. In this case, however, declare the grade to be fluid.rendererComponent
:
...
In general, renderer component trees contain one entry per renderer component. One renderer component is typically one conceptual element in a user interface, for example: a text entry field, a set of radio buttons, a row in a table.
In a renderer component tree, the binding to the data model is specified using a special notation: the EL path into the data model is enclosed in "${...}"
. Some types of renderer components, such as a selection, have a particular format. For details see ProtoComponent Types. The preferred way of specifying the component tree to a renderer component is via the protoTree
top-level option.
Code Block | ||||
---|---|---|---|---|
| ||||
fluid.defaults("tutorials.currencyConverter.produceTree", ={ function (that) { gradeNames: ["fluid.rendererComponent", "autoInit"], return selectors: { amount: "${amount}.tut-currencyConverter-amount", currency: {".tut-currencyConverter-currency-selecter", result: ".tut-currencyConverter-result" optionnames: "${rates.names}", model: { optionlistrates: "${rates.values}",{ selectionnames: ["${currentSelection}euro", "yen", "yuan", "usd", "rupee"], }, resultvalues: "${result}" }; }; |
Specify the name of this function using the produceTree
option:
Code Block | ||
---|---|---|
javascript | javascript | fluid.defaults("tutorials.currencyConverter", { gradeNames: ["fluid.rendererComponent", "autoInit"],["0.712", "81.841", "6.609", "1.02", "45.789"] selectors: { }, amountcurrentSelection: "0.tut-currencyConverter-amount712", currencyamount: ".tut-currencyConverter-currency-selecter"0, result: ".tut-currencyConverter-result"0 }, modelprotoTree: { ratesamount: "${amount}", currency: { names: ["euro", "yen", "yuan", "usd", "rupee"] optionnames: "${rates.names}", valuesoptionlist: ["0.712${rates.values}", "81.841", "6.609", "1.02", "45.789"] }, currentSelectionselection: "0.712",${currentSelection}" amount: 0}, result: 0"${result}" }, events: { conversionUpdated: null }, produceTree: "tutorials.currencyConverter.produceTree" }); |
...
});
|
While it is not necessary to create event handlers to update the model when users change the controls (the renderer provides incoming data binding support automatically), you still need event handlers for:
- responding to changes in the model by updating the converted amount
- refreshing the display when the result is updated.
Attach event listener to the model itself, through the ChangeApplier, for these changes, rather than listening for changes in the interface:
...
We can clean up the implementation of our component from the Tutorial - Evented Components still further by writing these event handlers also in a declarative form. The display update can be handled by a modelListeners
entry of the kind we have seen before:
Code Block | ||
---|---|---|
| ||
modelListeners: { "result": { that.convert(that.model.amount); }); that.applier.modelChanged.addListener("currentSelection", function () {func: "{that}.refreshView" that.convert(that.model.amount);} }); that.applier.modelChanged.addListener("result", function () { that.refreshView(); }); }; |
Finally, you still need the finalInitFunction
to attach the convert()
method to the component object and to call bindEventHandlers()
.
One last option to set is renderOnInit
: This flag tells the Renderer whether or not to automatically render the interface one the component has finished initializing. If this flat is not true
, you must manually call that.refreshView()
yourself.
Putting it all together, you have the following:
Code Block | ||
---|---|---|
javascript | javascript | fluid.defaults("tutorials.currencyConverter", {
gradeNames: ["fluid.rendererComponent", "autoInit"],
selectors |
This directs the framework to automatically refresh the rendered view whenever the computed converted currency value is updated - this is done by binding a listener onto the renderer's automatically generated refreshView
method to trigger from any changes received to the model's result
field.
Arranging declaratively to perform the currency conversion requires a more interesting kind of definition. Any transformation that can be expressed as part of Infusion's Model Transformation system can be used to construct a Model Relay rule which can keep two component models (or two parts of the same component model) synchronised with each other's changes, where the synchronisation automatically takes account of a transformation rule. In this case we can recognise that the transformation performed by this component is one of the standard rules supplied with the framework, fluid.transforms.linearScale (if it weren't part of the standard set, it would be easy to use any suitable free function as the transforming rule). With the current Infusion version of 1.5, this requires upgrading the base grade of our component from fluid.rendererComponent
to fluid.rendererRelayComponent
. This requirement will go away in Infusion 2.0, along with the use of the "old renderer". The relay rule looks like this:
Code Block | ||
---|---|---|
| ||
modelRelay: { amounttarget: ".tut-currencyConverter-amountresult", currencysingleTransform: ".tut-currencyConverter-currency-selecter", { resulttype: ".tut-currencyConverter-result"fluid.transforms.linearScale", value: "{that}.model.amount", modelfactor: "{that}.model.currentSelection" rates: { names: ["euro", "yen", "yuan", "usd", "rupee } } } |
This rule states that the value held in the model field amount
will be multiplied by the value held in currentSelection
and the result placed in result
. Note that within the model transformation document itself we need to use fully-qualified IoC expressions of the form {that}.model.amount
etc. in order to avoid ambiguity with referring directly to strings. Outside the transformation rule we can use short-form references to the current component's model fields such as result
- although note that we would have had to have used the long forms here too if we had wanted to refer to a model held by a different component.
Putting it all together, you have the following:
Code Block | ||||
---|---|---|---|---|
| ||||
fluid.defaults("tutorials.currencyConverter", { gradeNames: ["fluid.rendererRelayComponent", "autoInit"], selectors: { values: ["0.712", "81.841", amount: "6.609", "1.02.tut-currencyConverter-amount", "45.789"] }currency: ".tut-currencyConverter-currency-selecter", currentSelectionresult: "0.712tut-currencyConverter-result", }, amount model: 0,{ resultrates: { 0 }, eventsnames: { ["euro", "yen", "yuan", "usd", "rupee"], conversionUpdated: null }, produceTreevalues: ["tutorials.currencyConverter.produceTree0.712", "81.841", finalInitFunction: "tutorials.currencyConverter.finalInit","6.609", "1.02", "45.789"] renderOnInit: true });, var bindEventHanders = function (that) {currentSelection: "0.712", // When the modelamount: changes0, update the resulting "converted" value that.applier.modelChanged.addListener("amount", function () {result: 0 }, that.convert(that.model.amount);protoTree: { }); that.applier.modelChanged.addListener("currentSelection", function () {amount: "${amount}", currency: that.convert(that.model.amount);{ }); that.applier.modelChanged.addListener("result", function () {optionnames: "${rates.names}", that.refreshView();optionlist: "${rates.values}", }); }; tutorials.currencyConverter.produceTree = function (that) {selection: "${currentSelection}" return { }, amountresult: "${amountresult}", }, currency modelListeners: { optionnames"result": "${rates.names}", optionlistfunc: "${ratesthat}.values}",refreshView" } }, selection modelRelay: "${currentSelection}" }target: "result", resultsingleTransform: "${result}"{ }; }; tutorials.currencyConverter.finalInit = function (that) {type: "fluid.transforms.linearScale", // Add a method to the component object value: "{that}.convertmodel.amount", = function (amount) { var convertedAmount = parseInt(amount) * that factor: "{that}.model.currentSelection; " that.applier.requestChange("result", convertedAmount); } that.events.conversionUpdated.fire(convertedAmount); }; } renderOnInit: true bindEventHanders(that}); }; |
Impressively we have succeeded in implementing all of this component design without a single line of user JavaScript code.
Div | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| |||||||||||||||
|