This documentation is currently being moved to our new documentation site.

Please view or edit the documentation there, instead.

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

ChangeApplier API

This section explains and documents the various Javascript API calls for instantiating and working with ChangeAppliers. In practice, users will use the ChangeAppliers which are automatically constructed for every Model Component as its top-level member applier and will not construct their own. Furthermore, a good deal of the use made of ChangeAppliers will take the form of Declarative Configuration rather than literal JavaScript API calls - many more declarative uses are supported in Infusion 1.5 and even more will be supported in Infusion 2.0. This page presents both programmatic calls and their declarative equivalents where they exist.

On This Page

Registering interest in model changes using a ChangeApplier

Declarative style

The declarative style for registering interest in change events uses an entry in the modelListeners options area of a modelRelayComponent. These listeners are attached to the applier during the construction process of the entire component (and its surrounding tree) and so will therefore become notified as part of the initial transaction - they will therefore get to observe the model changing state from its primordial value of undefined to holding their initial resolved value. This is the recommended way of listening to model changes using the ChangeApplier system.

Each record in the modelListeners block has the format <modelPathReference>: <modelListener declaration>. The left and right hand sides of this definition will be explained in the subsequent sections:

Model path references

A <modelPathReference> has the form:

Syntax definition for <modelPathReference> - the key in modelListeners options block for a modelRelayComponent
SyntaxMeaningExamples
Simple StringReference to a model path in this component"modelPath" / "modelPath.*" / ""/ "*"
IoC referenceReference to a model path in another component"{otherComponent}.model.modelPath", "{otherComponent}.model.modelPath.*", "{otherComponent}.model", "{otherComponent}.model.*"

The 4 examples presented in the "Examples" column are parallel for the two cases - they respectively match changes occurring in the same parts of the target model, only in the first row they match into the model attached to this component (the same one in which the modelListeners record appears) and in the second row they match into the model attached to another component - one referenced by the Context Expression otherComponent.

Model listener declaration

A model listener declaration block has exactly the same form and meaning as any of the record types supported by Invokers and Listeners - including the one-string compact syntax documented with Invokers. The only difference is that an extra context name is available in this block by the name of change. This is bound to the particular change event which triggered this listener. This context behaves as an object with the following fields:

Members of the {change} object bound in a model listener declaration
MemberTypeDescription
{change}.valueAny typeThe new value which is now held at the model path matched by this model listener block
{change}.oldValueAny typeThe previous value which was held at the matched model path, before it was overwritten by the change being listened to
{change}.pathStringThe path at which this change occurred. In general this will be the same as the path registered as the modelPathReference for this block - however it may be one segment longer if a wildcard path was used (see next section)


Wildcards in model path references

The last path segment of a model path reference may or may not be "*". Whether it is "*" or not, the reference matches exactly the same set of changes - the only difference is in how they are reported. A path reference of "things" will match all changes occurring below this path segment, and report all those occurring within a single transaction as a single change. A path reference of "things.*" will match the same changes, but will report one change for each immediately nested path segment touched by the changes. For example, the following definition will log just one 

fluid.defaults("examples.pathExample1, {
    gradeNames: ["fluid.modelRelayComponent", "autoInit"],
    modelListeners: {
        things: {
            funcName: "console.log",
            args: ["{change}.value", "{change}.path"]
        }
    }
});
 
var that = examples.pathExample1();
that.applier.change("things", {a: 1, b: 2});
// this logs {a: 1, b: 2}, "things" to the console

However, the following example which just differs in the listener path (swapping "things" for "things.*") will log two changes:

 

fluid.defaults("examples.pathExample2, {
    gradeNames: ["fluid.modelRelayComponent", "autoInit"],
    modelListeners: {
        "things.*": {
            funcName: "console.log",
            args: ["{change}.value", "{change}.path"]
        }
    }
});
 
var that = examples.pathExample2();
that.applier.change("things", {a: 1, b: 2}); // logs 2 lines
// Line 1: 1, "things.a"
// Line 2: 2, "things.b"

The standard way to be notified of any changes to the model in a single notification is to use a model path reference consisting of the empty string "". Use of "*" will react to the same changes, but will report multiple notifications for compound modifications as in the above example.

It is not currently possible to supply more than one wildcard segment per path reference, or to supply the wildcard at any position in the string other than as the last path segment.

Programmatic style

The programmatic style for registering interest in model changes uses an API exposed by the ChangeApplier on its member modelChanged that is very similar to that exposed by a standard Infusion Event - the difference is that the addListener method accepts an extra 1st argument, pathSpec - this holds the same model path reference documented in the previous section on declarative binding:

applier.modelChanged.addListener(pathSpec, listener, namespace)
applier.modelChanged.removeListener(listener)

This style of listening to changes is not recommended. Since such a listener can only be attached once a component and its applier have been fully constructed, it will miss observation of the initial transaction as well as any other model changes that have occurred up to the point where it is registered. This implies that the state of the model that it sees cannot be fully predicted without a knowledge of the entire surrounding of the component tree. 

The listener is notified after the change (or set of coordinated changes) has already been applied to the model - it is too late to affect this process and so this event is not preventable. The signature for these listeners is

function listener(value, oldValue, pathSegs, changeRequests)

Parameter

Description

value

The new (current) model value held at the path for which this listener registered interest

oldValue

A "snapshot" of the previous model value held at that path

pathSegsAn array of String path segments holding the path at which value and oldValue are/were held

changeRequests

A single ChangeRequest object or an array of them (see below) which were responsible for this change (may be empty)

Users will in many cases only be interested in the first argument in this signature.

Firing a change using a ChangeApplier

Declarative style

The declarative style for firing model changes involve a kind of IoC record that is supported in various places in component configuration, in particular as part of the definition of both Invokers and Listeners of an IoC-configured component. This style of record is recognised by its use of the special member changePath which determines which path in which component model will receive the change.

Change record for firing changes by declarative binding
MemberTypeDescription 
changePath<model path reference> (String)The reference to the model path in a model somewhere in the component tree where the change is to be triggered. This has the same syntax as the model path references documented above for declarative listening, only wildcard forms are not supported. Four examples: "modelPath" / "" / "{otherComponent}.model.modelPath" / "{otherComponent}.model" 
valueAny typeThe value which should be stored at the path referenced by changePath. If this contains compound objects (built with {}), these will be merged into the existing values in the model. If this contains arrays (built with []) these will overwrite existing values at that path. 
typeString (optional)If this holds the value DELETE, this change will remove the value held at changePath. In this case, value should not be supplied. This is the recommended way of removing material from a model - it has the effect of the delete primitive of the JavaScript language. Sending changes holding a value of null or undefined does not have the same effect, as per the JavaScript language spec. 

Example of declarative triggering of changes

In the below example, we construct an invoker that will set the entire model of the current component to whatever value is supplied as its first argument - this is achieved by giving its record a changePath of "" and binding its value to {arguments}.0:

fluid.defaults("examples.changeExample, {
    gradeNames: ["fluid.modelRelayComponent", "autoInit"],
    model: "initialValue",
    invokers: {
        changer: {
            changePath: "",
            value: "{arguments}.0"
        }
    }
});
 
var that = examples.changeExample();
that.changer("finalValue");
console.log(that.model); // "finalValue"

Programmatic style

There are two calls which can be used to fire a change request - one informal, using immediate arguments, and a more formal method which constructs a concrete changeRequest object.

applier.change(path, value, type)

Parameter

Type

Description

path

string

An EL path into the model where the change is to occur.

value

Any type

An object which is to be added into the model

type

(optional) "ADD" or "DELETE"

A key string indicating whether this is an ADD request (the default) or a DELETE request (a request to unlink a part of the model)

The semantics and values are exactly the same as described in the section on declarative triggering above - with the difference that IoC references may not be supplied for path.

applier.fireChangeRequest(changeRequest)

where a changeRequest is an object holding the above named parameters in named fields - e.g. \path: "modelPath", value: "newValue"} change and fireChangeRequest reach exactly the same implementation - the only difference is in the packaging of the arguments. For change they are spread out as a sequence of 3 arguments, whereas for fireChangeRequest, they are packaged up as named fields (path, value and type) of a plain JavaScript object. Such an object is called a "ChangeRequest" and is a convenient package for these requests to pass around in in an event pipeline within the framework.

The programmatic style for firing changes is not strongly discouraged, as the programmatic style for listening to changes is - since it does not run into the same lifecycle issues that programmatic listeners do. However, the declarative style for triggering changes should be used wherever it can, and the framework will support more and more schemes for declarative triggering of changes moving up to Infusion 2.0.

Example of two styles of declarative model listener registration

Users can freely define very fine or coarse-grained listeners for changes in a model using the ChangeApplier. Here are some examples using the new declarative model listener registration syntax in Infusion 1.5:


fluid.defaults("my.component", {
    gradeNames: ["fluid.modelComponent", "fluid.eventedComponent", "autoInit"],
    
    invokers: {
        printChange: {
            "this": "console",
            method: "log",
            args: ["{arguments}.0"]
        }
    },

    model: {
        cats: {
            hugo: {
                name: "Hugo",
                colours: ["white", "brown spots"]
            },
            clovis: {
                name: "THE CATTT!",
                colours: ["white", "black spots", "black moustache"]
            }
        }
    },

    modelListeners: {
        // Will fire individual change events whenever any part of "cats.hugo" changes.
        // {change}.value will correspond to each changed path within "hugo".
        "cats.hugo.*" {
            funcName: "{that}.printChange",
            args: [{change}.value]
        },

        // Will fire a single composite change event whenever any part of "cats.clovis" changes.
        // {change}.value will contain the new state of the "clovis" object.
        "cats.clovis": {
            funcName: "{that}.printChange",
            args: [{change}.value]
        }
    }
}); 

// Example usage.
var c = my.component();
c.applier.change("cats.hugo", {
    name: "Hugonaut",
    colours: ["hard to tell"]
});
> "Hugonaut"
> ["hard to tell"]

c.applier.change("cats.clovis.name", "THER CATTT!");
> {name: "THER CATTT!", colours: ["white", "black spots", "black moustache"]}

Low-level ChangeApplier APIs

These are not recommended for typical users of the framework, and are not stable.

Instantiating a ChangeApplier

Instantiating a ChangeApplier manually is possible but is not recommended for general use. In particular, manual instantiation of a ChangeApplier will not allow access to the powerful Model Relay framework features, which require the ChangeApplier to be properly situated in an IoC component tree. However, it's possible to imagine some cases where authors of "superframeworks" might find this facility useful. In Infusion 1.5 there are several variants for construction of ChangeAppliers since the implementation is in transition from the old non-relay-aware applier to the new model relay implementation.

var applier = fluid.makeChangeApplier(model, options); // currently produces an "old-style" applier

    OR
var applier = fluid.makeHolderChangeApplier(holder, options); // currently produces an "old-style" applier
    OR
var applier = fluid.makeNewChangeApplier(holder, options); // produces a new "relay-aware" applier

In every case the options argument is optional. Where the parameter model appears it directly represents the model to be managed by the applier. Where parameter holder appears, it represents instead an object which is expected to be a container for the model where the model itself is held in this object at the member named model. In framework use this argument will actually hold the owning component itself, although the applier makes no use of any properties of this object other than the one held at model.

Under the old ChangeApplier semantics, the model handle was closed over and remained constant for the lifetime of the applier. The framework contained utilities such a fluid.model.copyModel which assisted the user in working with a model with changing contents whilst keeping its base reference constant. Under the new ChangeApplier semantics, the model base reference is not stable and in fact starts by holding the value undefined as every model component initialises.

Operating transactions manually

Similarly to manual construction of a ChangeApplier, this is not recommended for normal users of the framework. It may be appropriate for advanced users of the framework who are building higher-level frameworks layered over it.

A transaction can be opened using the initiate() method of the applier function which returns a transaction object:

var myApplier = fluid.makeChangeApplier(myModel);
var myTransaction = myApplier.initiate();

The transaction object exposes an API which agrees with the ChangeApplier's own API for triggering changes, which can be used to trigger changes within the transaction:

myTransaction.fireChangeRequest(requestSpec1);
myTransaction.fireChangeRequest(requestSpec2);
...

The transaction can be completed using the commit() function of the transaction object:

myTransaction.commit();

A single modelChanged event will be fired on completion of the commit, regardless of the number of change requests.

  • No labels