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

Renderer Components

On This Page

In the previous View Components currency converter example, you can notice two things:

  • The list of currencies is duplicated: It is present in the HTML as well as in the model.
  • Event listeners were attached to the UI elements to update the model when the controls were changed.

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 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 model components and 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.

The simplest way to specify a component tree is to create a function that returns the tree and specify the function name using the produceTree option (see the example below).

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.

The mapping between the component tree and the template is specified using cutpoints: key/value pairs mapping the ID of the component in the tree to a selector for the element in the template. When you use the renderer component grade, the Framework automatically generates a list of cutpoints from the selectors specified in the component options.

For more detailed information, see Cutpoints.

Useful functions and events

In addition to the standard view component features, the renderComponent attaches a refreshView function and an afterRender event.

refreshView:

The refreshView function is used to trigger/re-trigger the rendering of a component. If the "renderOnInit" option is set to "true", the component will automatically render without having to specifically call refreshView.

afterRender:

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:

fluid.defaults("tutorials.currencyConverter", {
    gradeNames: ["fluid.rendererComponent", "autoInit"],
    selectors: {
        amount: ".tut-currencyConverter-amount",
        currency: ".tut-currencyConverter-currency-selecter",
        result: ".tut-currencyConverter-result"
    },
    model: {
        rates: {
            names: ["euro", "yen", "yuan", "usd", "rupee"],
            values: ["0.712", "81.841", "6.609", "1.02", "45.789"]
        },
        currentSelection: "0.712",
        amount: 0,
        result: 0
    },
    events: {
        conversionUpdated: null
    }
});

As mentioned above, you also need to provide a renderer component tree. Create a public function that will accept the component object, that, and returns the tree.

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.

tutorials.currencyConverter.produceTree = function (that) {
    return {
        amount: "${amount}",
        currency: {
            optionnames: "${rates.names}",
            optionlist: "${rates.values}",
            selection: "${currentSelection}"
        },
        result: "${result}"
    };
};

Specify the name of this function using the produceTree option:

fluid.defaults("tutorials.currencyConverter", {
    gradeNames: ["fluid.rendererComponent", "autoInit"],
    selectors: {
        amount: ".tut-currencyConverter-amount",
        currency: ".tut-currencyConverter-currency-selecter",
        result: ".tut-currencyConverter-result"
    },
    model: {
        rates: {
            names: ["euro", "yen", "yuan", "usd", "rupee"],
            values: ["0.712", "81.841", "6.609", "1.02", "45.789"]
        },
        currentSelection: "0.712",
        amount: 0,
        result: 0
    },
    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, 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:

var bindEventHanders = function (that) {
    // When the model changes, update the resulting "converted" value
    that.applier.modelChanged.addListener("amount", function () {
        that.convert(that.model.amount);
    });
    that.applier.modelChanged.addListener("currentSelection", function () {
        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:

fluid.defaults("tutorials.currencyConverter", {
    gradeNames: ["fluid.rendererComponent", "autoInit"],
    selectors: {
        amount: ".tut-currencyConverter-amount",
        currency: ".tut-currencyConverter-currency-selecter",
        result: ".tut-currencyConverter-result"
    },
    model: {
        rates: {
            names: ["euro", "yen", "yuan", "usd", "rupee"],
            values: ["0.712", "81.841", "6.609", "1.02", "45.789"]
        },
        currentSelection: "0.712",
        amount: 0,
        result: 0
    },
    events: {
        conversionUpdated: null
    },
    produceTree: "tutorials.currencyConverter.produceTree",
    finalInitFunction: "tutorials.currencyConverter.finalInit",
    renderOnInit: true
});

var bindEventHanders = function (that) {
    // When the model changes, update the resulting "converted" value
    that.applier.modelChanged.addListener("amount", function () {
        that.convert(that.model.amount);
    });
    that.applier.modelChanged.addListener("currentSelection", function () {
        that.convert(that.model.amount);
    });
    that.applier.modelChanged.addListener("result", function () {
        that.refreshView();
    });
};

tutorials.currencyConverter.produceTree = function (that) {
    return {
        amount: "${amount}",
        currency: {
            optionnames: "${rates.names}",
            optionlist: "${rates.values}",
            selection: "${currentSelection}"
        },
        result: "${result}"
    };
};

tutorials.currencyConverter.finalInit = function (that) {

    // Add a method to the component object
    that.convert = function (amount) {
        var convertedAmount = parseInt(amount) * that.model.currentSelection;
        that.applier.requestChange("result", convertedAmount);
        that.events.conversionUpdated.fire(convertedAmount);
    };
    
    bindEventHanders(that);
};