ComponentJS Features  

Understand the major features of ComponentJS

The APIs of all JavaScript libraries share a common global namespace. A conflict can occur easily. ComponentJS by default exports its API into the global variable ComponentJS, but this can be configured or even changed afterwards. Usually you want to use the short cs (for "component system") as the global API identifier.

Simple Environment

When loading ComponentJS directly in the index.html of your HTML5 App use one of the following two approaches:

<!-- in simple environment -->
<script src="component.js"></script>
<script>ComponentJS.symbol("cs");</script>

curl.js Environment

For complex HTML5 Apps you usually load all assets with a loader like curl.js. Here, ComponentJS is loaded like this:

/* under curl.js environment */
curl([ "component.js" ], function () {
    ComponentJS.symbol("cs");
    curl([ "app.js", … ], function () {
        …
    })
})

YepNope Environment

Alternatively, for complex HTML5 Apps you can also load all assets with the YepNope loader. Here, ComponentJS is loaded like this:

/* under YepNope environment */
yepnope([{
    load:     [ "component.js" ],
    complete: function () { ComponentJS.symbol("cs"); }
}, {
    load:     [ "app.js", … ]
    complete: function () { … }
}]);

CommonJS Environment

In a CommonJS environment like Node.js (usually for running a UI headless in order to just test its presentation model layers) you load ComponentJS like this:

/* under CommonJS environment */
var cs = require("component.js").ComponentJS;
Object-Oriented Programming (OOP) usually dictates the use of classes to structure a program. As JavaScript uses a prototype-based object-orientation approach, there is no built-in class system, of course. Unfortunately, although JavaScript's prototype object approach is very flexible, the corresponding syntax and semantics is rather strange. You usually want a classical OO-style class system on the one hand and the flexibility of prototype objects by means of traits on the other hand. ComponentJS uses a flexible class system for itself internally and also exposes it in the global API for optional use by the application.

NOTICE: It is technically not required to use ComponentJS's class system for your own application in order to work with ComponentJS! For instance, when working with Ext JS you might want to use their Ext.define() mechanism. Hence, for working with ComponentJS, the classes you use for components can be created in arbitrary ways (not required to use $cs.clazz) as long as one can still instanciate its objects with the regular JavaScript new directive.

Class Definition

ComponentJS allows you to define namespaced classes (which syntax variant you choose is just a matter of style):

/* define class com.example.Foo (variant 1: explicit namespace) */
cs.ns("com.example"); com.example.Foo = cs.clazz({ … });

/* define class com.example.Foo (variant 2: ad-hoc namespace) */
cs.ns("com.example").Foo = cs.clazz({ … });

/* define class com.example.Foo (variant 3: implicit namespace) */
cs.clazz({ name: "com.example.Foo", … });

Object Instanciation

ComponentJS then allows you to instanciate the defined class with the regular new directive of JavaScript:

/* instanciate class foo.bar.quux */
var foo = new com.example.Foo();

Constants, Fields, Constructor and Methods

Usually, a class has a few fields, a constructor which initializes them and methods which operate on the fields:

com.example.Foo = cs.clazz({
    statics:  { IDX_FOO: 1, IDX_BAR: 2 },
    dynamics: { foo: null, bar: null, quux: null },
    cons: function (foo, bar, quux) {
        this.foo  = foo;
        this.bar  = bar;
        this.quux = quux;
    },
    protos: {
        doFooBarQuux: function (…) {
            …
        },
        …
    }
});

  • The statics hash contains the name/value pairs of all statically assigned fields. These have to be treated like constants as they are shared across all class instances.
  • The dynamics hash contains the name/value pairs of all dynamically assigned fields. These are deep-cloned and re-assigned for each class instance and hence are individual to each class instance. These are usually the private fields of a class.
  • The cons function is called just after the class instance is created. It receives the parameters passed to the new com.example.Foo(…) call. It is usually used to set dynamics fields based on the input parameters.
  • The protos hash contains the name/function pairs of all class methods. These are shared by all class instances and the instances of sub-classes by means of JavaScript's regular prototype objects.

Positional and Named Parameters

The cs.param() function is just a utility function which allows a method to conveniently support both positional and name-based parameters at the same time. For instance, the above constructor could use it:

com.example.Foo = cs.clazz({
    …
    cons: function () {
        var args = cs.params("cons", arguments, {
            foo:  { pos: 0, def: "",     valid: "string",  req: true },
            bar:  { pos: 1, def: true,   valid: "boolean", req: true },
            quux: { pos: 2, def: cs.nop, valid: "function",          }
        });
        this.foo  = args.foo;
        this.bar  = args.bar;
        this.quux = args.quux;
    },
    …
});

This then allows both forms of calling:

/* use positional parameters */
var foo = new com.example.Foo("foo", true, function () { … });

/* use named parameters */
var foo = new com.example.Foo({ foo: "foo", bar: true, quux: function () { … } });

Addtionally, it allows one to leave out optional parameters (from the right side of the parameter list for positional parameters only, of course) if they are not required or at least have default values:

/* use first positional parameters only */
var foo = new com.example.Foo("foo");

/* use any named parameters */
var foo = new com.example.Foo({ bar: true });

Inheritance

As known from usual OO-systems, classes can also inherit from super-classes:

/* define class */
com.example.Shape = cs.clazz({
    dynamics: { x: 0, y: 0 },
    cons: function (x, y) {
        this.x = x;
        this.y = y;
    }
});

/* define sub-class */
com.example.Circle = cs.clazz({
    extend: com.example.Shape,
    dynamics: { r: 0 },
    cons: function (x, y, r) {
        this.base(x, y);
        this.r = r;
    },
});

The extend class definition field references the single previously defined super class. With the this.base(…) call you can (and should) call the constructor (or any other overloaded method) from the super class. It is important to notice how calls to any method resolve and how calls to this.base() in any method of a class resolves.

  • When on class Foo and its instanciated object foo a method foo.bar() is called, the following happens:
    1. First, a direct property named bar on object foo is tried. This can exist on foo through (in priority order) a bar in either the dynamics definition of a mixin of Foo, or in the statics definition of a mixin of Foo, or in the dynamics definition of Foo, or in the statics definition of Foo.
    2. Second, an indirect prototype-based property named bar on object foo is tried. This can exist on foo through (in priority order) a bar in either the protos definition of Foo or in the protos definition of any extend of Foo.
  • When on class Foo and its instanciated object foo in any method foo.bar() the this.base() is called, the following happens:
    1. First, a call to the super functions in the mixin trait chain is attempted. The mixins are traversed in the reverse order of the trait specification in the mixin array, i.e., the last trait's mixins are tried first.
    2. Second, a call to the super functions in the extend inheritance class chain is attempted. First the directly extend class is attempted, then the extend class of this class, etc.

Traits/Mixins

In addition to the single-inheritance with extend, ComponentJS's class mechanism also support traits, i.e., classes which can be mixed in with the mixin class definition field:

/* define a trait */
com.example.Property = cs.trait({
    dynamics: { __prop: {} },
    protos: {
        property: function (name, value_new) {
            var value_old = this.__prop[name];
            if (typeof value_new !== "undefined")
                this.__prop[name] = value_new;
            return value_old;
        }
    }
});

/* define another trait */
com.example.Observable = cs.trait({
    …
});

/* define a class */
com.example.Circle = cs.clazz({
    extend: com.example.Shape,
    mixin: [ com.example.Property, com.example.Observable ],
    …
});

Traits are defined like classes but with two differences: they cannot have a class they extend and instead in addition to the cons method they can have a setup method. This setup method is called at the end of a class(!) instanciation in order to give each mixed in trait the chance to post-adjust the newly created object. This allows traits not only to statically mixin properties but to also dynamically post-adjust instanciated objects.

/*  silly method renaming trait  */
com.example.Foo2Bar = $cs.trait({
    setup: function () {
        if (this.hasOwnProperty("foo")) {
            this["bar"] = this["foo"];
            delete this["foo"];
        }
    }
});

/* define a class */
com.example.Foo = cs.clazz({
    mixin: [ com.example.Foo2Bar ],
    dynamics: {
        foo: 42 /* is automatically renamed to "bar" */
    }
    …
});

Components and Shadow Objects

ComponentJS manages a hierarchy of logical UI components. Each logical UI component actually consists of two closely coupled objects: a generic ComponentJS component object (based on an internal and not exposed ComponentJS class definition) and an application-supplied so-called shadow object (like com.example.ui.Panel). The generic component object serves as a proxy to the shadow object for driving all ComponentJS functionalities without making any constraints (usually like requiring you to sub-class a special "component" base class as other frameworks require) on the corresponding shadow object.

/* the simplest ComponentJS-style shadow object */
com.example.ui.Window = cs.clazz({});

/* the simplest JavaScript-style shadow object */
com.example.ui.Window = function () {};

The UI component hierarchy usually corresponds to the inherently hierarchical structure of UI dialogs. A UI dialog can be a whole UI screen or just a group of widgets. But it usually is never just a single widget (for the management of single widgets the UI toolkit is responsible for) or even the whole UI of the application (for this singleton-scenario to use a component system like ComponentJS is useless). Every UI component has a unique name, consisting of the slash-separated path of UI component names, from the implicitly existing root UI component to the addressed UI component (e.g. /example/ui/panel). When creating a UI component you can address it with a fully-qualified path or relatively to an already existing UI component.

/* UI component creation with fully-qualified paths */
cs.create("/example/ui/",               com.example.ui.Window);
cs.create("/example/ui/dialog1",        com.example.ui.Dialog1);
cs.create("/example/ui/dialog1/list",   com.example.ui.Dialog1List);
cs.create("/example/ui/dialog1/detail", com.example.ui.Dialog1Detail);
cs.create("/example/ui/dialog2",        com.example.ui.Dialog2);

/* UI component creation with relative paths */
var ui = cs.create("/example/ui",       com.example.ui.Window);
ui.create("dialog1",                    com.example.ui.Dialog1);
ui.create("dialog1/list",               com.example.ui.Dialog1List);
ui.create("dialog1/detail",             com.example.ui.Dialog1Detail);
ui.create("dialog2",                    com.example.ui.Dialog2);

There are three distinct ways to create a component and its corresponding shadow object: you can implicitly let ComponentJS instanciate the shadow object (assuming no parameters need to be passed to the shadow object's constructor), you can on-the-fly explicitly instanciate the shadow object yourself (usually when you need to pass parameters to its constructor) and in case of special requirements you can even first create a UI component without a corresponding shadow object and then attach it yourself manually.

/* implicit shadow object instanciation */
cs.create("/example/ui/panel", com.example.ui.Panel);

/* explicit shadow object instanciation */
cs.create("/example/ui/panel", new com.example.ui.Panel("foo"));

/* explicit shadow object instanciation and delayed attachment */
var comp = cs.create("/example/ui/panel", null);
…
var obj = new com.example.ui.Panel("foo");
comp.obj(obj);

At any time you can easily go from the component to its shadow object and vice versa:

/* get the shadow object through a component */
var obj = comp.obj();

/* get the component through a shadow object */
var comp = cs(obj);

But you are safe to always call cs() on either the shadow-object or even the component itself (in case you are unsure what you have at hand, e.g., in case of a this pointer in a callback):

/* protected from double-wrapping */
if (comp !== cs(comp))
    alert("will not happen");

Component Lookup

As you can see, ComponentJS's global API symbol (by default ComponentJS but as explained above in practice usually mapped to cs) is actually a function (which directly maps onto cs.lookup(), but one never specifies this explicitly in practice), a method to lookup a component in various ways:

/* lookup component by absolute path */
var comp = cs("/example/ui/panel");

/* lookup component by relative path */
var ui = cs("/example/ui");
var panel = cs(ui, "panel");
var dialog1 = cs(panel, "../dialog1");

/* check for component existance */
cs("/")                      /* === cs("<root>") */
cs("/").exists()             /* === true */
cs("/not/exising")           /* === cs("<none>") */
cs("/not/exising").exists()  /* === false */

But usually you never assign the result of a lookup to a variable. Instead you use it to call the ComponentJS functionalities on it, similar to the API jQuery provides for calling functionalities on DOM elements. For instance, if you want, you can even combine lookup with relative component creation:

/* create UI component under path /example/ui/panel */
cs("/example/ui").create("panel", com.example.ui.Panel);

Component Tree Traversal

You can traverse the component hierarchy tree of ComponentJS yourself, in case you need it:

/* resolve parent component */
cs("/foo/bar").parent()  /* === cs("/foo") */
cs("/foo/bar/..")        /* === cs("/foo") */
cs(cs("/foo/bar"), "..") /* === cs("/foo") */

/* resolve path of all parent components */
cs("/foo/bar/quux").path()     /* === [ cs("/"), cs("/foo"), cs("/foo/bar") ] */
cs("/foo/bar/quux").path("/")  /* === "/foo/bar/quux" */

/* walk to all parent components (recursively)  */
var output = cs("/foo/bar/quux").walk_up(function (depth, comp, output) {
    output += depth + ":" + comp.name() + " ";
    return output;
}, "");
/* output === "0:quux 1:bar 2:foo 3:<root> " */

/* resolve all direct child components */
cs("/foo").create("bar");
cs("/foo").create("quux");
var childs = cs("/foo").children();
/* childs === [ cs("/foo/bar"), cs("/foo/quux") ] */

/* walk to all transitive child components (recursively)  */
cs.create("/ui/foo")
cs.create("/ui/foo/bar")
cs.create("/ui/foo/bar/baz")
cs.create("/ui/foo/quux")
var output = cs("/ui").walk_down(function (depth, comp, output, depth_first) {
    if (depth_first)
        output += depth + ":" + comp.name() + " ";
    return output;
}, "");
/* output === "3:baz 2:bar 2:quux 1:foo 0:ui " */
var output = cs("/ui").walk_down(function (depth, comp, output, depth_first) {
    if (!depth_first)
        output += depth + ":" + comp.name() + " ";
    return output;
}, "");
/* output === "0:ui 1:foo 2:bar 3:baz 2:quux " */

Component Properties

The first set of interesting ComponentJS functionalities on components is that they have hierarchical properties, i.e., key/value pairs attached to a component and on lookup-failure they are searched (with "first wins" strategy) from the current component up to the root component.

/* set a few properities */
cs("/").property("foo", "val1");
cs("/example").property("bar", "val2");
cs("/example/ui/panel").property("quux", "val3");

/* get properties on the leaf component */
var foo = cs("/example/ui/panel").property("foo");  /* === "val1" */
var foo = cs("/example/ui/panel").property("bar");  /* === "val2" */
var foo = cs("/example/ui/panel").property("quux"); /* === "val3" */

/* get properties anywhere */
var foo = cs("/example/ui").property("foo");  /* === "val1" */
var foo = cs("/example/ui").property("bar");  /* === "val2" */
var foo = cs("/example/ui").property("quux"); /* === null   */

Sometimes a property on a parent component should resolve to different values when looked-up on different child components. This can be achieved by scoping the property name to the name of the child component.

/* set a few properities */
cs("/example/ui/panel").property("foo",         "val-for-any");
cs("/example/ui/panel").property("foo@dialog2", "val-for-dialog2");
cs("/example/ui/panel").property("foo@dialog3", "val-for-dialog3");

/* get properties */
var foo = cs("/example/ui/panel/dialog1").property("foo");  /* === "val-for-any" */
var foo = cs("/example/ui/panel/dialog2").property("foo");  /* === "val-for-dialog2" */
var foo = cs("/example/ui/panel/dialog3").property("foo");  /* === "val-for-dialog3" */

State Transitions

One of the core feature of ComponentJS is its extraordinary powerful hierarchy-aware state transition management of UI components. At any time, each UI component in ComponentJS is in a particular state of a pre-defined life-cycle. A life-cycle consists of one or more stacked states. You can transition a component "/foo" to state "bar" by calling cs("/foo").state("bar"). State transitions are fully aware of the UI component hierarchy and the defined life-cycle, which is directly reflected in the two possible state changing szenarios:

  • Transition UI component to a higher state:

    For each state between the current state (exclusive) of the component and the higher target state (inclusive) in the life-cycle stack, the following procedure is performed:

    1. A mandatory transition of the parent component to the higher state is performed. This transitively triggers transitions of all parent components up to the root component to the higher state if necessary.
    2. A transition of the current component to the higher state is performed by optionally calling its corresponding state "enter" method.
    3. Optionally transition each child component to the higher state if the child component is explicitly configured for automatic state increase (see State Auto Increase below). This can also transitively trigger transitions of more sub-child components up to the leaf components to the higher state if explicitly configured.

  • Transition component to a lower state:

    For each state between the current state (exclusive) of the component and the lower target state (inclusive) in the life-cycle stack, the following procedure is performed:

    1. A mandatory transition of each child component to the lower state is recursively performed. This transitively triggers transitions of all sub-childs components up to the leaf components to the lower state if necessary.
    2. A transition of the current component to the lower state is performed by optionally calling its corresponding state "leave" method.
    3. Optionally transition the parent component to the lower state if the parent component is explicitly configured for automatic state decrease (see State Auto Decrease below). This can also transitively trigger transitions of more parent components up to the root component to the lower state if explicitly configured.

The above rules fully honor the following important invariant which you should remember, please:
Golden Rule: At any time, each component can only be in either the same or any lower state than its parent component!

State Automatic Increase/Decrease

The points (3) above talk about an optional transition of child components in case of a transition to a higher state and an optional transition of parent components in case of a transition to a lower state. Both are convenience features which have the following two distinct two use-cases:

  • State Auto Increase (regular case)
    When the state of a component is increased, it is mandatory that all parent components are at least at the same or higher state, of course (see Golden Rule above). But often, increasing the state of a particular distinct dialog usually means that all child components of at least this particular dialog should also be increased. For this you either have to call .state_auto_increase() on all childs components beforehand or in case you want to increase all child components transitively more conveniently set the property ComponentJS:state-auto-increase on the dialog component itself. In other words: "state-auto-increase" on a component means the component automatically increases with its parent component.
  • State Auto Decrease (esoteric case)
    When the state of a component is decreased, it is mandatory that all child components are at least at the same or lower state, of course (see Golden Rule above). But in some rare cases, decreasing the state of a particular dialog should mean that the parent component of at least this particular dialog should also be decreased. For this you either have to call .state_auto_decrease() on the parent component beforehand or in case you want to decrease all parent components transitively more conveniently set the property ComponentJS:state-auto-decrease on the parent components itself. In other words: "state-auto-decrease" on a component means the component automatically decreases with its child components.

Component Life-Cycle

As a generic solution, ComponentJS does not enfore a particular life-cycle and corresponding states. Instead, it just pre-defines the following life-cycle and states, but allows you to also re-define it from scratch. The pre-define life-cycle is a reasonable one for UI environments where a "UI dialog" or even a complex "UI widget" is implemented as a ComponentJS UI component:

/* remove all pre-defined states */
cs.transition(null);

/* configure: state name,     enter method, leave method */
cs.transition("created",      "create",     "destroy");
cs.transition("prepared",     "prepare",    "cleanup");
cs.transition("materialized", "render",     "release");
cs.transition("visible",      "show",       "hide"   );

A more detailed description of each state (the examples use some silly jQuery-based UI rendering, for illustration purposes only):

  • State created:
    This state means the component is "created and attached to component tree". The state is entered/left by optional create and destroy methods on the target component. In create the component usually creates sub-components (if existing). In destroy the component usually destroys sub-components (if existing).

    com.example.ui.foo = cs.clazz({
        protos: {
            create: function () {
                cs.create("subcomp1", com.example.ui.subcomp1);
                cs.create("subcomp2", com.example.ui.subcomp2);
            },
            destroy: function () {
                cs.destroy("subcomp1");
                cs.destroy("subcomp2");
            },
            …
        }
    });
    
  • State prepared:
    This state means the component is "prepared and ready for rendering". The state is entered/left by optional prepare and cleanup methods on the target component. In prepare the component usually loads its presentation model. In cleanup the component usually releases the presentation model.

    com.example.ui.foo = cs.clazz({
        dynamics: {
            model: null
        },
        protos: {
            prepare: function () {
                /* fetch content from backend service */
                var self = this;
                $.get("http://backend.example.com/…", function (data) {
                    self.model = data;
                });
            },
            cleanup: function () {
                /* cleanup content */
                this.model = null;
            },
            …
        }
    });
    
  • State materialized:
    This state means the component is "rendered onto the DOM tree". The state is entered/left by optional render and release methods on the target component. In render the component usually renders its view (perhaps still hidden). In release the component usually drops its view.

    com.example.ui.foo = cs.clazz({
        dynamics: {
            model: null
        },
        protos: {
            render: function () {
                var self = this;
                /*  add dialog to DOM */
                $(…).html(
                    '<div style="display: none;">' +
                    '    <div class="content">' + this.model  + '</div>' +
                    '    <div class="overlay" style="display: none; z-index: 100;"></div>' +
                    '</div>'
                ).bind("click", function (ev) {
                    self.click(this, ev);
                });
            },
            release: function () {
                /*  remove the dialog from DOM */
                $(…).html("").unbind("click");
            },
            click: function (el, ev) {
                …
            },
            …
        }
    });
    
  • State visible:
    This state means the component is "visible to the user". The state is entered/left by optional show and hide methods on the target component. In show the component usually shows its (already rendered) view. In hide the component usually hides its (already rendered) view.

    com.example.ui.foo = cs.clazz({
        protos: {
            show: function () {
                /* show dialog */
                $(…).css("display", "block");
            },
            hide: function () {
                /* hide dialog */
                $(…).css("display", "none");
            },
            …
        }
    });
    

Transition Guards

In case the particular state "enter" or "leave" methods of a component explicitly return the boolean value false, the current transitioning process is immediately stopped and suspended for resuming later. This allows methods to act as transition guards for entering or leaving a state. This is usually needed in case a resource is allocated/gathered asynchronously in a lower state and the transition to a higher state has to be prevented until the resource allocation was finished.

com.example.ui.dialog = cs.clazz({
    dynamics: {
        model: null
    },
    protos: {
        prepare: function () {
            /* fetch content from backend service */
            var self = this
            $.get("http://backend.example.com/…", function (data) {
                self.model = data;
            });
        },
        render: function () {
            if (this.model === null)
                return false;
            …
        },
        release: function () {
            if (this.model === null)
                return false;
            …
        },
        cleanup: function () {
            /* cleanup content */
            this.model = null;
        },
        …
    }
});

This allows the guard indicators to be arbitrary complex and calculated on the fly, but this direct transition guards approach works only as long as (1) the guard indicator (above it is the field this.model) is directly accessible (usually within the same component) and (2) the transition request is explicitly re-triggered by yourself (as ComponentJS cannot know when your guard indicator has switched state). Because of these nasty drawbacks, instead of driving the guards yourself, you usually use the ComponentJS guard mechanism:

com.example.ui.dialog = cs.clazz({
    dynamics: {
        model: null
    },
    protos: {
        prepare: function () {
            /* fetch content from backend service */
            var self = this;
            cs(self).guard("render", +1);
            $.get("http://backend.example.com/…", function (data) {
                self.model = data;
                cs(self).guard("render", -1);
            });
        },
        render: function () {
            …
        },
        release: function () {
            …
        },
        cleanup: function () {
            /* cleanup content */
            this.model = null;
        },
        …
    }
});

This prevents the component from entering the higher state through "render", asynchronously fetches the resource and once finished and then allows the previously requested state transition to finally proceed. The advantage is that guards easily can be set even on child components and that on guard deactivation ComponentJS has a chance to resume the pending state transitions.

Spooling Deallocation Actions

Everything you "allocate" in an "enter" method you HAVE TO "deallocate" in the corresponding "leave" method. Only this way states can be transitioned up and down arbitrarily and repeatedly. This means in particular:

  • For every event subscribe call there has to be a corresponding unsubscribe call.
  • For every service register call there has to be a corresponding unregister call.
  • For every hook latch call there has to be a corresponding unlatch call.
  • For every model observe call there has to be a corresponding unobserve call.
  • For every socket plug call there has to be a corresponding unplug call.
So, the general pattern is:

com.example.ui.dialog = cs.clazz({
    dynamics: {
        subscription: null,
        registration: null,
        hooking:      null,
        observation:  null,
        plugging:     null
    },
    protos: {
        prepare: function () {
            this.subscription  = cs(this).subscribe({ … });
            this.registration  = cs(this).register ({ … });
            this.hooking       = cs(this).latch    ({ … });
            this.observation   = cs(this).observe  ({ … });
            this.plugging      = cs(this).plug     ({ … });
        },
        cleanup: function () {
            cs(this).unplug     (this.plugging);
            cs(this).unobserve  (this.observation);
            cs(this).unlatch    (this.hooking);
            cs(this).unregister (this.registration);
            cs(this).unsubscribe(this.subscription);
        }
    }
});
For lots of such "allocations" this can be nasty, especially because you have to explicitly remember the resulting ids in the component. As this pattern is such common, you can use the spool parameter of subscribe, register, latch, observe and plug. This spools the corresponding "deallocation" operation under the supplied name for later all-at-once "deallication" via unspool. To better understand the generic spooling mechanism, here is how you could use it manually:

com.example.ui.dialog = cs.clazz({
    protos: {
        prepare: function () {
            var subscription  = cs(this).subscribe({ … });
            var registration  = cs(this).register ({ … });
            var hooking       = cs(this).latch    ({ … });
            var observation   = cs(this).observe  ({ … });
            var plugging      = cs(this).plug     ({ … });
            cs(this).spool("prepared", cs(this), "unsubscribe", subscription);
            cs(this).spool("prepared", cs(this), "unregister",  registration);
            cs(this).spool("prepared", cs(this), "unlatch",     hooking);
            cs(this).spool("prepared", cs(this), "unobserve",   observation);
            cs(this).spool("prepared", cs(this), "unplug",      plugging);
        },
        cleanup: function () {
            cs(this).unspool("prepared");
        }
    }
});

This already avoids the explicit storing of the ids and bundles "allocation" and "deallocation" together. During unspool the actions are executed in reverse spooling order. With the common spool parameter this further can be reduced to:

com.example.ui.dialog = cs.clazz({
    protos: {
        prepare: function () {
            cs(this).subscribe({ …, spool: "prepared" });
            cs(this).register ({ …, spool: "prepared" });
            cs(this).latch    ({ …, spool: "prepared" });
            cs(this).observe  ({ …, spool: "prepared" });
            cs(this).plug     ({ …, spool: "prepared" });
        },
        cleanup: function () {
            cs(this).unspool("prepared");
        }
    }
});

Finally, ComponentJS automatically unspools actions on every state enter/leave which were spooled under the internal name ComponentJS:state:name:enter and ComponentJS:state:name:leave This way, if wished, the above can be even further reduced to:

com.example.ui.dialog = cs.clazz({
    protos: {
        prepare: function () {
            cs(this).subscribe({ …, spool: "ComponentJS:state:prepared:leave" });
            cs(this).register ({ …, spool: "ComponentJS:state:prepared:leave" });
            cs(this).latch    ({ …, spool: "ComponentJS:state:prepared:leave" });
            cs(this).observe  ({ …, spool: "ComponentJS:state:prepared:leave" });
            cs(this).plug     ({ …, spool: "ComponentJS:state:prepared:leave" });
        }
    }
});

Sockets

A standard use case for UI component properties (see above) is to provide a socket/plug functionality in order to let child-components to link into the UI rendering of the parent component without having to know and hence hard-code the particular UI rendering location of the parent component (usually because of UI component reusability reasons) in the child component.

ComponentJS for provides its Socket functionality on top of the Property functionality. This means that Sockets resolve like regular component properties, but some additional functionality is provided, too.

com.example.ui.dialog1 = cs.clazz({
    protos: {
        create: function () {
            cs(this).create("dialog2", com.example.ui.dialog2);
        },
        render: function () {
            $("…").html(
                '<div>' +
                '    <div class="content">…</div>' +
                '    <div class="childs"></div>'
                '</div>' +
            );
            cs(this).socket($("… .childs"),
                function (el) { $(this).append(el); },
                function (el) { $(el).remove(); }
            );
        },
        …
    }
});

com.example.ui.dialog2 = cs.clazz({
    dynamics: {
        id: null
    },
    protos: {
        render: function () {
            var ui = $('<div>…</div>');
            this.id = cs(this).plug(ui);
        },
        release: function () {
            cs(this).unplug(this.id);
        },
        …
    }
});

Sometimes the parent UI component wants to provide multiple sockets to its clients. Then one at least has to hard-code names between parent and childs, of course. But the components are still de-coupled, because there is still no reason to explictly pass the particular parent UI widget object to each child.

com.example.ui.dialog1 = cs.clazz({
    protos: {
        create: function () {
            cs(this).create("dialog2", com.example.ui.dialog2);
            cs(this).create("dialog3", com.example.ui.dialog3);
        },
        render: function () {
            $("…").html(
                '<div>' +
                '    <div class="content">…</div>' +
                '    <div class="childs1"></div>'
                '    <div class="childs2"></div>'
                '</div>' +
            );
            cs(this).socket({
                name:   "childs1",
                ctx:    $("… .childs1"),
                plug:   function (el) { $(this).append(el); },
                unplug: function (el) { $(el).remove(); }
            });
            cs(this).socket({
                name:   "childs2",
                ctx:    $("… .childs2"),
                plug:   function (el) { $(this).append(el); },
                unplug: function (el) { $(el).remove(); }
            });
        },
        …
    }
});

com.example.ui.dialog2 = cs.clazz({
    dynamics: {
        id: null
    },
    protos: {
        render: function () {
            var ui = $('<div>…</div>');
            this.id = cs(this).plug({ name: "childs1", object: ui });
        },
        release: function () {
            cs(this).unplug(this.id);
        },
        …
    }
});

com.example.ui.dialog3 = cs.clazz({
    dynamics: {
        id: null
    },
    protos: {
        render: function () {
            var ui = $('<div>…</div>');
            this.id = cs(this).plug({ name: "childs2", object: ui });
        },
        release: function () {
            cs(this).unplug(this.id);
        },
        …
    }
});

In rare cases the parent UI component even might need to give a particular child UI component a different socket — without having to tell the particular child the different socket. This can be achieved by scoping the socket to the child UI component:

com.example.ui.dialog1 = cs.clazz({
    protos: {
        create: function () {
            cs(this).create("view",   com.example.ui.View);
            cs(this).create("detail", com.example.ui.Details);
        },
        render: function () {
            $("…").html(
                '<div>' +
                '    <div class="content">…</div>' +
                '    <div class="view"></div>'
                '    <div class="detail"></div>'
                '</div>' +
            );
            cs(this).socket({
                ctx:    $("… .view"),
                plug:   function (el) { $(this).append(el); },
                unplug: function (el) { $(el).remove(); }
            });
            cs(this).socket({
                scope:  "detail",
                ctx:    $("… .detail"),
                plug:   function (el) { $(this).append(el); },
                unplug: function (el) { $(el).remove(); }
            });
        },
        …
    }
});

com.example.ui.Details = cs.clazz({
    dynamics: {
        id: null
    },
    protos: {
        render: function () {
            var ui = $('<div>…</div>');
            this.id = cs(this).plug(ui); /* plugs into "detail" above */
        },
        release: function () {
            cs(this).unplug(this.id); /* unplugs from "detail" above */
        },
        …
    }
});

Events & Services

Beside the hierarchical state transitions, the event mechanism is one of ComponentJS's most flexible and powerful features. Actually, the companion Service mechanism is internally directly based on the Event mechanism, too.

The event mechanism is a classical publish/subscribe architecture where zero or more subscribers can up-front subscribe on UI components to the occurrence of events and once an event is published they can receive the event.

The event mechanism is usually used in combination with UI toolkit events: a component receives an UI event from the UI toolkit through an "action binding" mechanism, translates this "local" (per component) UI event into a "global" (per application) UI event and further publishes this to related UI components. Those receiving UI components usually catch the "global" UI event and translate it back to a "local" UI toolkit action on their own UI dialog.

A Service on the other hand is a call function "registered" on a component and which can be "called". The trick of both the Event and Service functionality is that it leverages the UI component hierarchy by resolving Event "subscribers" and Service "registers" from the target component up to the root component.

As an example, imagine a dialog "panel" with three sub-dialogs: "list" is a scrolling list of items, "preview" is a content preview of the currently (in the "list" dialog) selected item and "detail" is a dialog showing meta-information of the currently selected item. The important point is that the "preview" and "detail" dialogs are both reusable dialogs and do not have to know anything about "list".

com.example.ui.panel = cs.clazz({
    protos: {
        create: function () {
            cs(this).create("list",    com.example.ui.list);
            cs(this).create("preview", com.example.ui.preview);
            cs(this).create("detail",  com.example.ui.detail);
        },
        render: function () {
            $("…").html(
                '<div>' +
                '    <div class="list">…</div>' +
                '    <div class="preview"></div>'
                '    <div class="detail"></div>'
                '</div>' +
            );
            cs(this).socket({
                scope:  "list",
                ctx:    $("… .list"),
                plug:   function (el) { $(this).append(el); },
                unplug: function (el) { $(el).remove(); }
            });
            cs(this).socket({
                scope:  "preview",
                ctx:    $("… .preview"),
                plug:   function (el) { $(this).append(el); },
                unplug: function (el) { $(el).remove(); }
            });
            cs(this).socket({
                scope:  "detail",
                ctx:    $("… .detail"),
                plug:   function (el) { $(this).append(el); },
                unplug: function (el) { $(el).remove(); }
            });
            cs(this).subscribe({
                name: "list-selection",
                func: function (ev, item) {
                    cs("preview", this).call("show-item", item);
                    cs("detail",  this).call("show-item", item);
                }
            });
        },
        …
    }
});

com.example.ui.list = cs.clazz({
    protos: {
        render: function () {
            var self = this;
            var ui = $('<div>…</div>');
            cs(self).plug(ui);
            $("div", ui).click(function (ev) {
                var item = … /* determine id of clicked item */
                cs(self).publish("list-selection", item);
            });
        },
        …
    }
});

com.example.ui.preview = cs.clazz({
    dynamics: {
        view: null
    },
    protos: {
        create: function () {
            cs(this).register("show-item", this.show_item);
        },
        render: function () {
            var view = $('<div>…</div>');
            cs(this).plug(view);
        },
        show_item: function (item) {
            var preview = make_preview_of(item);
            $("div", this.view).html(preview);
        },
        …
    }
});

com.example.ui.detail = cs.clazz({
    …
});

This shows just the basic functionality of subscribe and publish. When called with named parameters it unleashes its additional functionalities: subscription arguments, event capturing and bubbling, exclusive subscribers, asynchronous delivery, etc. The mechanism is actually such powerful that even the Service and Hook mechanisms (see below) are directly driving on top of the Event mechanism.

Models

The following example shows a particular "Menu1" component reusing a generic Menu model/view pair of components. Notice how both the controller and the view attach to the model in the middle. This effectively is an implemenation of the Model-View-Controller/Component-Tree (MVC/CT) pattern from Ralf S. Engelschall.

/*  controller (non-reusable)  */
com.example.ui.menu1 = cs.clazz({
    mixin: [ cs.marker.controller ],
    protos: {
        create: function () {
            /*  instanciate the reusable menu model and view sub-components  */
            cs(this).create(
                "model", com.example.ui.menu.model,
                "view",  com.example.ui.menu.view
            );
        },
        prepare: function () {
            var self = this

            /*  route default socket of menu into specific
                "menu1" socket of our panel component  */
            cs(self).link(self, "menu1")

            /*  provision view/presentation model via business model  */
            cs(self).register({
                name: "menu1-set-items",
                spool: "prepared",
                func: function (items, active) {
                    cs(self, "model").value("data:itemList",    items);
                    cs(self, "model").value("state:activeItem", active);
                }
            })

            /*  act on model actions  */
            cs(self, "menu").observe({
                name:  "event:selectedItem",
                spool: "prepared",
                func: function (ev, pos) {
                    cs(self).publish("menu1-active-item", pos)
                }
            })
        },
        cleanup: function () {
            cs(this).unspool("prepared")
        }
    }
});

/*  model (reusable)  */
com.example.ui.menu.model = cs.clazz({
    mixin: [ cs.marker.model ],
    protos: {
        create: function () {
            /*  allow model and view to automatically increase state  */
            cs(this).property("ComponentJS:state-auto-increase", true);

            /*  define the presentation model  */
            cs(this).model({
                "data:itemList":      { value: [], valid: "[string*]"               },
                "state:activeItem":   { value: -1, valid: "number"                  },
                "event:selectedItem": { value: -1, valid: "number", autoreset: true }
            });
        }
    }
});

/*  view (reusable)  */
com.example.ui.menu.view = cs.clazz({
    mixin: [ cs.marker.view ],
    protos: {
        render: function () {
            var self = this;

            /*  generate outer DOM item list element  */
            var dom = $("<ul></ul>");
            cs(self).plug({ object: dom, spool: "materialized" });

            /*  generate inner DOM item elements  */
            cs(self).observe({
                name: "data:itemList",
                spool: "materialized",
                touchonce: true,
                func: function (ev, items) {
                    var active = cs(self).value("state:activeItem");
                    for (var i = 0; i < items.length; i++) {
                        var clazz = "item" + (i === active ? " active" : "");
                        html += "<li class=\"" + clazz + "\"" +
                           " data-pos=\"" + i + "\" >" + items[i] + "</li>";
                    }
                    $(dom).html($html);
                }
            })

            /*  fetch items from presentation model  */
            cs(self).observe({
                name: "state:activeItem",
                spool: "materialized",
                touchonce: true,
                func: function (ev, active) {
                    $("li.active", dom).removeClass("active");
                    $("li", dom).eq(active).addClass("active");
                }
            });

            /*  attach to the selection event and convert the
                technical DOM event into a logical model event */
            $("li", dom).click(function (ev) {
                var pos = parseInt($(ev.target).data("pos"));
                cs(self).value("event:selectedItem", pos);
            });
        },
        release: function () {
            cs(this).unspool("materialized");
        }
    }
});