The Infusion Inversion of Control (IoC) system includes a simple method for distributing options from any components to
any others in the component tree. This system uses a special block named distributeOptions
which is supported in the
options of every Infusion component. The key addressing scheme needed to make this system work — the means for
determining where the target of the distribution is — is dubbed "IoCSS" because it uses a syntax and idiom very
similar to that used in CSS selectors.
distributeOptions and IoCSS: Downward-matching CSS-like context selectors for options forwarding
The distributeOptions
option is a top-level block supported by every Infusion component. It specifies how options
should be distributed to components elsewhere in the tree. A typical, conservative usage would have a component
distribute only downwards to its own subcomponents, but it is also possible to use the considerable power of
distributeOptions
to distribute to any component anywhere in the (global) component tree - naturally such power
should be used with extreme discretion since it could very easily reduce a design to chaos. However, in many situations,
for example when writing powerful authoring tools, this kind of power is completely indispensable.
Background for distributeOptions
As component trees become larger, it will often happen that a high-level component will need to specify options for a
component further down the component tree. Without distributeOptions
, component configuration can become very, what we
might call, "pointy":
// without developer's use of IoCSS, user writes
fluid.uiOptions(".my-uio-container", {
components: {
templateLoader: {
options: {
templatePrefix: "../../myTemplates"
}
}
}
});
The example above shows a simplified version of a situation with Infusion's "UI Options"
component. In practice, the user of the component would have to write an even more deeply nested piece of configuration
than this, if the developer had not made use of the distributeOptions
directive in the component's options block,
together with the use of "IoCSS" expressions to distribute the user's options to the right place in the component tree,
as shown in the following example:
// developer writes:
fluid.defaults("fluid.uiOptions", {
distributeOptions: {
source: "{that}.options.templatePrefix",
target: "{that templateLoader}.options.templatePrefix"
}
});
// user writes:
fluid.uiOptions(".my-uio-container", {
templatePrefix: "../../myTemplates"
});
In the distributeOptions
block above, the context {that templateLoader}
is an IoCSS expression which designates one
or more of the child components of UI Options that are to receive the user's option. The syntax and meaning of these
expressions is defined below.
As well as converting the exposed options structure of a component into a more compact form, distributeOptions
is also
a powerful tool for maintaining API stability for a component or family of components. Since the binding of IoCSS
selectors such as that templateLoader
onto child components is flexible, the component tree could be refactored in
quite an aggressive way without requiring changes in either the user's configuration, or even the distributeOptions
block itself. If the refactoring was even more thorough (involving wholesale removal of the target component, or a
change in its important grades), the developer could still maintain stability of the external user API just by changing
the distributeOptions
block.
In terms of a standard discussion on Design Patterns, this use of distributeOptions
could be seen as an automated and declarative scheme for achieving the ends
of the Facade Design Pattern, without the need
for either user or developer code.
However, facade formation isn't the only possible function of an options distribution. They are also sufficiently
powerful to encompass most uses of the so-called Aspect-oriented
programming (AOP) by the use of the target root context /
to match (and "advise", in AOP terminology) all components in a design matching a particular specification. Unlike
traditional AOP techniques, however, these options distributions do not put ultimate and final power in the hands of the
wielder - since the component holding the distribution itself can be either advised itself, or destroyed completely -
thus withdrawing the "advice" from the system.
distributeOptions format
The distributeOptions
option is a top-level block supported by every Infusion component, holding an array of records,
hash of records, or single record containing the following properties:
Name | Description |
---|---|
target |
(Required) An IoC expression describing the location in the component tree where the options are to be set. The "context" part of this expression will usually consist of an IoCSS selector (see below for format). However, it may also in specialised cases consist of a standard "upwards" IoC context expression, indicating that options are to be distributed to a parent component (this is only meaningful in the case the parent has not finished instantiating). |
record |
(Mutually exclusive with source ) A record of options to set at the target. This will be interpreted literally - however it will be expanded according to standard options expansion rules. General configuration will be expanded in the context of the component holding distributeOptions - however, configuration for listeners and invokers will be expanded at the target component. |
source |
(Mutually exclusive with record ) An IoC expression into the options structure of the source component, referencing what to copy to the target. |
removeSource |
(Only possible if source is used) true/false: If true, the source options block is removed from its original site in the options structure when it is forwarded to the target . |
exclusions |
(Only possible if source is used) A list of EL paths into the source material which should not be forwarded. Whether or not removeSource is used, these will be retained in their original position in the source component's options. |
priority |
(Optional) a Priority specification specifying how the priority of this distribution is to be resolved with respect to any other options distributions which are targeted at the same component in the tree as this one. Typically one will write after:otherNamespace to indicate that this distribution should be stronger than (i.e. should merge options on top of) another distribution whose namespace is otherNamespace , or before:otherNamespace to indicate that this distribution should be weaker than (have option merged on top of by) a distribution whose namespace is otherNamespace . |
namespace |
(Optional, recommended) A namespace to identify this options distribution amongst others in an extended design. This can be used by the priority field of other distributions in order to defer to it or be deferred to by it. (The framework should also ensure to uniquify distributions with respect to namespace at the target - not currently implemented) |
In the case that a hash of these records is provided, the keys of the structure will be interpreted as the namespace
of the distribution.
IoCSS Selectors
An IoCSS selector appears in some special kinds of references in the framework, most notably in the target
element of
a distributeOptions
block. Informally, it can be distinguished from a standard IoC reference since
it contains some whitespace between its various matching segments, and a standard IoC reference just consists of a
single context name without whitespace. Some representative examples of IoCSS selectors are:
{that sessionManager}
- matches any component which matches the contextsessionManager
below the current component{that > ownSub}
- matches any component directly a child of the current one matching a contextownSub
(perhaps by being a member with that name){testEnvironment flowManager preferencesServer}
- firstly looks upward to find atestEnvironment
component somewhere above the current one, and then matches apreferencesServer
that is nested within aflowManager
{/ fluid.viewComponent}
- matches anyfluid.viewComponent
anywhere in the component tree
Each block separated by whitespace matches a component by one of the following component selector rules:
Form | Description |
---|---|
* |
matches any component - universal selector |
/ |
matches the global root component of the entire component tree - may only be used in the head position of an IoCSS selector |
E or &E |
matches any component holding a context name of E - the & character may be omitted for the first context in a group. There is special support for the context that as with standard IoC context matching |
#myid |
matches the component with id equal to myid (of no use to developers since component ids cannot be predicted) |
Mediating each neighbouring pair of component rules is a descendent rule, of which we currently support 2:
Form | Description |
---|---|
E F |
Matches any F component that is a descendant of an E component |
E > F |
Matches any F component that is a direct child of an E component |
If you are familiar with CSS selectors you can compare this set of 6 simple syntax rules with the wide range of CSS selector rules listed in the article's table.
Simple Example of distributeOptions: record for grade distribution
This is the form taken by the most commonly seen and straightforward use of distributeOptions
- the contribution of an
additional grade to one or more subcomponents of the current component:
fluid.defaults("kettle.use.session.io", {
gradeNames: ["fluid.component"],
distributeOptions: {
record: "kettle.sessionManager.io",
target: "{that sessionManager}.options.gradeNames"
}
});
The sessionManager
, wherever it is in the set of child components, will be granted the additional grade
kettle.sessionManager.io
.
Complex Example: record
fluid.defaults("fluid.moduleLayoutHandler", {
gradeNames: ["fluid.layoutHandler"],
// ...
distributeOptions: {
// unusual: not an IoCSS selector - upward-matching selector distributes options back to parent before instantiation ends
target: "{reorderer}.options",
record: {
selectors: {
movables: {
expander: {
func: "{that}.makeComputeModules",
args: [false]
}
},
dropTargets: {
expander: {
func: "{that}.makeComputeModules",
args: [false]
}
},
selectables: {
expander: {
func: "{that}.makeComputeModules",
args: [true]
}
}
}
}
}
});
Example: source
fluid.defaults("fluid.tests.uploader", {
gradeNames: ["fluid.component"],
components: {
uploaderContext: {
type: "fluid.progressiveCheckerForComponent",
options: {componentName: "fluid.tests.uploader"}
},
uploaderImpl: {
type: "fluid.tests.uploaderImpl"
}
},
distributeOptions: [{
target: "{that > uploaderImpl}.options", // Target a directly nested component matching the context "uploaderImpl"
source: "{that}.options", // Distribute ALL of our options there, except exclusions:
exclusions: ["components.uploaderContext", "components.uploaderImpl"] // options targeted directly at these subcomponents are left undisturbed in place
}],
progressiveCheckerOptions: {
checks: [{
feature: "{fluid.test}",
contextName: "fluid.uploader.html5"
}]
}
});
Example: broadcast via root to all components
// A `mixin grade` to contribute two listeners to every viewComponent in the system
fluid.defaults("fluid.debug.listeningView", {
listeners: {
onCreate: {
funcName: "fluid.debug.viewMapper.registerView",
args: ["{fluid.debug.viewMapper}", "{that}", "add"]
},
onDestroy: {
funcName: "fluid.debug.viewMapper.registerView",
args: ["{fluid.debug.viewMapper}", "{that}", "remove"]
}
}
});
fluid.defaults("fluid.debug.viewMapper", {
gradeNames: ["fluid.component", "fluid.resolveRoot"],
// Distribute the grade `fluid.debug.listeningView` to every viewComponent in the system
distributeOptions: {
record: "fluid.debug.listeningView",
target: "{/ fluid.viewComponent}.options.gradeNames" // <- use of IoCSS root context "/"
}
});