Creating Enactors

This article describes how to use the Infusion Preferences Framework to create Enactors, components that act in response to changes to a user's preferences.

Overview

In the Preferences Framework, "Enactors" are Infusion components that act upon changes to user preferences.

The configuration information used to define an enactor must include certain required information:

  • the fluid.prefs.enactor grade (provided by the Framework)
  • a Preference Map (see below)
  • a renderer proto-tree or produceTree function
  • selectors for rendering the controls, labels, etc
  • any other information required by the enactor

If the enactor will be modifying the view of the interface, you will also want to add the fluid.viewComponent grade as well as selectors.

PreferenceMap

Each enactor defines a "preference map," which map the information in the Primary Schema into your enactor. The preference map is used to copy the default preference value from the primary schema into the enactor's model. It can also be used to copy any other necessary information from the primary schema into the enactor, if relevant. The values can be mapped to any path in the Panels options, and then they can be accessed through those paths.

Format

{
    "preferenceMap": {
        "<key of preference from primary schema>": {
            "<path in enactor's options where value should be held>": "<key in primary schema where value held>"
            // ...
        }
        // ...
    }
}

Examples

fluid.defaults("fluid.prefs.enactor.textSize", {
    gradeNames: ["fluid.prefs.panel"],
    preferenceMap: {
        "fluid.prefs.textSize": {
            "model.value": "value"
        }
    }
    // ...
});
fluid.defaults("fluid.prefs.enactor.emphasizeLinks", {
    gradeNames: ["fluid.viewComponent", "fluid.prefs.enactor.styleElements"],
    preferenceMap: {
        "fluid.prefs.emphasizeLinks": {
            "model.value": "value"
        }
    }
    // ...
});
fluid.defaults("fluid.videoPlayer.panels.captionsSettings", {
    gradeNames: ["fluid.videoPlayer.panels.mediaSettings"],
    preferenceMap: {
        "fluid.videoPlayer.displayCaptions": {
            "model.show": "value"
        },
        "fluid.videoPlayer.captionLanguage": {
            "model.language": "value"
        }
    }
    // ...
});

Binding to Model Changes

The most important thing that an enactor does is listen for changes to its model and act when changes occur. The Preferences Framework automatically binds the enactor's model to the user's preferences through the Preferences Map (described above). This means that if the user's preference changes (for example, through the preferences editor), the enactor's model will automatically be updated and a modelChanged event will be fired. All the enactor has to do is listen for that modelChanged event and carry out whatever adjustments are necessary. (For more information about model changes, see ChangeApplier API; for more information about the events and listeners in general, see Infusion Event System).

modelChanged event listeners are bound in a special block of a component's defaults called modelListeners. The general format is shown below:

modelListeners: {
    <modelpath>: {
        funcName: <listener name>,
        args: [<argument list>]
    }
}

In the argument list of a model listener, the change object is the original change request, which can be used to access the new model value:

{
    path: [<model paths>],
    value: <new value>,
    oldValue: <old value>
}

In the following example, an enactor function acts upon a new magnification value:

gpii.pmt.enactors.magnification.magnify = function (that, newModel) {
    that.magnify(newModel.value);
};

fluid.defaults("gpii.pmt.enactors.magnification", {
    // ...
    modelListeners: {
        "magnificationFactor": {
            funcName: "gpii.pmt.enactors.magnification.magnify",
            args: ["{that}", "{change}.value"]
        }
    }
});

Acting on model changes

The actions that an enactor will take will be entirely dependent on what the enactor is for. It is up to the developer to create the necessary functions, etc. required.

Example: Line Spacing Enactor

fluid.defaults("fluid.prefs.enactor.lineSpace", {
    gradeNames: ["fluid.viewComponent", "fluid.prefs.enactor"],
    preferenceMap: {
        "fluid.prefs.lineSpace": {
            "model.value": "value"
        }
    },
    fontSizeMap: {},  // must be supplied by implementors
    invokers: {
        set: {
            funcName: "fluid.prefs.enactor.lineSpace.set",
            args: ["{arguments}.0", "{that}", "{that}.getLineHeightMultiplier"]
        },
        getTextSizeInPx: {
            funcName: "fluid.prefs.enactor.getTextSizeInPx",
            args: ["{that}.container", "{that}.options.fontSizeMap"]
        },
        getLineHeight: {
            funcName: "fluid.prefs.enactor.lineSpace.getLineHeight",
            args: "{that}.container"
        },
        getLineHeightMultiplier: {
            funcName: "fluid.prefs.enactor.lineSpace.getLineHeightMultiplier",
            args: [{expander: {func: "{that}.getLineHeight"}}, {expander: {func: "{that}.getTextSizeInPx"}}],
            dynamic: true
        }
    },
    modelListeners: {
        value: {
            funcName: "{that}.set",
            args: ["{change}.value"]
        }
    }
});

fluid.prefs.enactor.lineSpace.set = function (that, newValue) {
    that.set(newValue);
};

Example: Table of Contents Enactor

fluid.defaults("fluid.prefs.enactor.tableOfContents", {
    gradeNames: ["fluid.viewComponent", "fluid.prefs.enactor"],
    preferenceMap: {
        "fluid.prefs.tableOfContents": {
            "model.toc": "value"
        }
    },
    tocTemplate: null,  // must be supplied by implementors
    components: {
        tableOfContents: {
            type: "fluid.tableOfContents",
            container: "{fluid.prefs.enactor.tableOfContents}.container",
            createOnEvent: "onCreateTOCReady",
            options: {
                components: {
                    levels: {
                        type: "fluid.tableOfContents.levels",
                        options: {
                            resources: {
                                template: {
                                    forceCache: true,
                                    url: "{fluid.prefs.enactor.tableOfContents}.options.tocTemplate"
                                }
                            }
                        }
                    }
                },
                listeners: {
                    afterRender: "{fluid.prefs.enactor.tableOfContents}.events.afterTocRender"
                }
            }
        }
    },
    invokers: {
        applyToc: {
            funcName: "fluid.prefs.enactor.tableOfContents.applyToc",
            args: ["{arguments}.0", "{that}"]
        }
    },
    events: {
        onCreateTOCReady: null,
        afterTocRender: null,
        onLateRefreshRelay: null
    },
    modelListeners: {
        toc: {
            funcName: "{that}.applyToc",
            args: ["{change}.value"]
        }
    }
});

fluid.prefs.enactor.tableOfContents.applyToc = function (value, that) {
    if (value) {
        if (that.tableOfContents) {
            that.tableOfContents.show();
        } else {
            that.events.onCreateTOCReady.fire();
        }
    } else if (that.tableOfContents) {
        that.tableOfContents.hide();
    }
};