ComponentJS Application Programming Interface (API) 1.6.2 ComponentJS API Management Change the API symbol in the global variable namespace under which ComponentJS is exposed. By default ComponentJS is exposed under the symbol name ComponentJS. It is a common convention to change the symbol to cs (for "component system/service") to have a convenient short-hand. * ComponentJS.symbol([name: String]): ComponentJS Change symbol of ComponentJS API to global variable name and return it. If name is not given, ComponentJS does not occupy any global namespace slot at all ? then it is required to store the return value and use ComponentJS directly through it. ComponentJS.symbol("cs") /* standard */ var cs = ComponentJS.symbol() /* alternative */ * ComponentJS.version = { major: Number, minor: Number, micro: Number, date: Number } Access the ComponentJS implementation version "major.minor.micro" and the corresponding release date (in format YYYYMMDD). if (ComponentJS.version.date < 20120101) throw new Error("need at least ComponentJS as of 20120101") Library Management ComponentJS can be extended through plugins, so it can initialize some of its internals only once all plugins were loaded and executed. * ComponentJS.bootstrap(): Void Bootstrap the ComponentJS library by initializing its internals. This has to be called at least before any calls to create(), but can be called after any calls to symbol(), debug() or ns(). cs.bootstrap() * ComponentJS.shutdown(): Void Shutdown the ComponentJS library by destroying its internals. This implicitly destroy the existing component tree, too. cs.shutdown() * ComponentJS.plugin(): String[] ComponentJS.plugin(name: String): Boolean ComponentJS.plugin(name: String, callback: Function): Void Return the names of all registered plugins, check for the registration of a particular plugin with name name or register a new plugin under name name with callback function callback. The callback function callback should have the signature "callback(_cs: ComponentJS_API_internal, $cs: ComponentJS_API_external, GLOBAL: Environment): Void" where _cs is the internal ComponentJS API (you have to check the source code of ComponentJS to know what you can do with it), $cs is the external ComponentJS API (the one described in this document) and GLOBAL is the global environment object (usually window in a browser, global in Node.js, etc). /* add a "foo()" method to all components */ ComponentJS.plugin("foo", function (_cs, $cs, GLOBAL) { var trait = $cs.trait({ protos: { foo: function () { ... } } }); _cs.latch("ComponentJS:bootstrap:comp:mixin", function (mixins) { mixins.push(trait); }); }); Debugging ComponentJS has special support for debugging its run-time processing, especially for visualizing the current component tree in real-time. * ComponentJS.debug(): Number ComponentJS.debug(level: Number): Void ComponentJS.debug(level: Number, message: String): Void Get current debug level, or configure the debugging through maximum debug-level level (0 disables all debug messages, 9 enables all debug messages) or log a particular message under debug-level level. cs.debug(0) if (cs.plugin("debugger")) { if (cs.debug_instrumented()) { cs.debug(9) cs.debug_window({ ... }) } } * ComponentJS.debug_instrumented(): Boolean Notice: requires ComponentJS plugin debugger to be loaded! Determine whether the current browser is "instrumented" for debugging, i.e., whether the browser's built-in debugger is activated (in detached mode only). Currently ComponentJS is able to determine Firefox's Firebug and Chrome's Inspector only. if (cs.debug_instrumented()) ... * ComponentJS.debug_window({ enable: Boolean, autoclose: Boolean, name: String, width: Number = 800, height: Number = 600, natural: Boolean = false }) ComponentJS.debug_window(enable: Boolean, autoclose: Boolean, name: String) Notice: requires ComponentJS plugin debugger to be loaded! On enable true/false open/close the extra browser window containing the ComponentJS debugger view for the ComponentJS application identified by name. If autoclose is true, optionally automatically close the debugger window with application window (which usually is inconvenient during debugging because on application reloads the ComponentJS debugger window is recreated with default width/height at default position instead of reused). Parameters width and height can be used to change the initial window size. Parameter natural controls whether the component tree is drawn with the root component at the bottom (true) or at the top (false). cs.debug_window({ enable: true, autoclose: false, name "My App" width: 800, height: 800, natural: true }) Code Structuring ComponentJS internally uses a few code structuring utility functions for implementing class method parameters and class attributes. Those utility functions are also exposed for consumption by application developers, but they are NOT(!) required for using ComponentJS. Especially, it is NOT(!) required that component backing objects are defined by cs.clazz! * ComponentJS.ns(path: String[, leaf: Object = {}]): Object Classes and traits should be structured through namespaces. A namespace is a JavaScript (hash) object, potentially itself part of a parent namespace object. The top-most implicit namespace object is window. A namespace has a dot-separated fully-qualified symbol path like foo.bar.quux. This method allows to create the fully-qualified path of nested objects through the dot-separated path of object names, optionally assign the right-most/leaf object to leave and finally return the right-most/leaf Object. cs.ns("my.app"); my.app.ui = cs.clazz({ ... }) /* standard */ cs.ns ("my.app").ui = cs.clazz({ ... }) /* alternative */ cs.ns("my.app.ui", cs.clazz({ ... }) /* alternative */ * ComponentJS.select(object: Object, path: String[, value: Object]): Object Dereference into (and this way subset) object according to the path specification and either return the dereferenced value or set a new value. Object has to be a hash or array object. The path argument has to follow the following grammar (which is a direct JavaScript dereferencing syntax): path ::= segment segment* segment ::= bybareword | bykey bybareword ::= "."? identifier bykey ::= "[" key "]" identifier ::= /[_a-zA-Z$][_a-zA-Z$0-9]*/ key ::= number | squote | dquote number ::= /[0-9]+/ dquote ::= /"(?:\\"|.)*?"/ squote ::= /'(?:\\'|.)*?'/ Setting the value to undefined effectively removes the dereferenced value. If the dereferenced parent object is a hash, this means the value is delete'ed from it. If the dereferenced parent object is an array, this means the value is splice'ed out of it. cs.select({ foo: { bar: { baz: [ 42, 7, "Quux" ] } } }, "foo['bar'].baz[2] ") ? "Quux" * ComponentJS.validate(object: Object, spec: String): Boolean Validate an arbitrary nested JavaScript object object against the specification spec. The specification spec has to be either a RegExp object for String validation, a validation function of signature "spec(Object): Boolean" or a string following the following grammar (which is a mixture of JSON-like structure and RegExp-like quantifiers): spec ::= not | alt | hash | array | any | primary | class | special not ::= "!" spec alt ::= "(" spec ("|" spec)* ")" hash ::= "{" (key arity? ":" spec ("," key arity? ":" spec)*)? "}" array ::= "[" (spec arity? ("," spec arity?)*)? "]" arity ::= "?" | "*" | "+" | "{" number "," (number | "oo") "}" number ::= /^[0-9]+$/ key ::= /^[_a-zA-Z$][_a-zA-Z$0-9]*$/ | "@" any ::= "any" primary ::= /^(?:null|undefined|boolean|number|string|function|object)$/ class ::= /^[A-Z][_a-zA-Z$0-9]*$/ special ::= /^(?:clazz|trait|component)$/ The special key "@" can be used to match an arbitrary hash element key. cs.validate({ foo: "Foo", bar: "Bar", baz: [ 42, 7, "Quux" ] }, "{ foo: string, bar: any, baz: [ number+, string* ], quux?: any }") * ComponentJS.params(name: String, args: Object[], spec: Object): Object Handle positional and named function parameters by processing a function's arguments array. Parameter name is the name of the function for use in exceptions in case of invalid parameters. Parameter args usually is the JavaScript arguments pseudo-array of a function. Parameter spec is the parameter specification: each key is the name of a parameter and the value has to be an Object with the following possible fields: pos for the optional position in case of positional usage, def for the default value (of not required and hence optional parameters), req to indicate whether the parameter is required and valid for type validation (either a string accepted by the validate() method, or a valid regular expression C object for validating a String against it or an arbitrary validation callback function of signature "valid(Object): Boolean". function config () { var params = $cs.params("config", arguments, { scope: { pos: 0, req: true, valid: "boolean" }, key: { pos: 1, req: true, valid: / ^[a-z][a-z0-9_]*$/ }, value: { pos: 2, def: undefined, valid: "object" }, force: { def: false, valid: "boolean" } }); var result = db_get (params.scope, params.key); if (typeof params.value !== "undefined") db_set (params.scope, params.key, params.value, params.force); return result; } var value = config("foo", "bar"); config("foo", "bar", "quux"); config({ scope: "foo", key: "bar", value: "quux", force: true }); * ComponentJS.attribute({ name: String, def: Object, valid: Object }): Function ComponentJS.attribute(name: String, def: Object, valid: Object): Function Create a cloneable attribute capturing getter/setter function with name name (for exception handling reasons only), the default value def and the value validation valid. var id = ComponentJS.attribute("id", "foo", /^[a-z][a-zA-Z0-9_]*/); id() == = "foo" id("bar") ? "foo" id() ? "bar" * ComponentJS.clazz({ [name: String,] [extend: Clazz,] [mixin: Array(Trait),] [cons: Function,] [dynamics: Object,] [protos: Object,] [statics: Object] }): Clazz Define a JavaScript Class, optionally stored under the absolute dot-separated object path name, optionally extending the base/super/parent Class extend and optionally mixing in the functionality of one or more Traits via mixin. The class can have a constructor function cons which is called once the Class is instantiated and which can further initialize the dynamic fields of the class. On each instantiation, all fields which are specified with dynamics are cloned and instantiated and all methods in protos are copied into the Class prototypes object. The statics content is copied into the Class itself only. In case of extend and/or mixin, both the cons and methods of protos can call this.base(...) for the base/super/ parent method. var foo = cs.clazz({ cons: function (bar) { this._bar = bar; }, protos: { bar: function (value_new) { var value_old = this._bar; if (typeof value_new !== "undefined") this._bar = value_new; return value_old; } [...] } }) 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: + 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. + 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: + First, a call to the super/base/parent 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. + Second, a call to the super/base/parent functions in the extend inheritance class chain is attempted. First, the directly extend class is attempted, then the extend class of this class, etc. NOTICE: As ComponentJS does not care at all how backing objects of components are defined, you can alternatively use an arbitrary solution for Class-based OO in JavaScript (e.g. TypeScript, JSClass, ExtendJS, DejaVu, Classy, jTypes, etc) or fallback to the also just fine regular Prototype-based OO in JavaScript: var foo = function (bar) { this._bar = bar; } foo.prototype.bar = function (value_new) { var value_old = this._bar; if (typeof value_new !=== "undefined") this._bar = value_new; return value_old; } [...] * ComponentJS.trait({ [name: String,] [mixin: Array(Trait),] [cons: Function, ] [setup: Function,] [dynamics: Object,] [protos: Object,] [statics: Object ] }): Trait Define a JavaScript Trait (a Class which can be mixed in), optionally stored under the absolute dot-separated object path name and optionally mixing in the functionality of one or more other Traits via mixin. The trait can have a constructor function cons which is called once the Class the Trait is mixed in is instantiated and which can further initialize the dynamic fields of the Class. On each instantiation, all fields which are specified with dynamics are cloned and instantiated and all methods in protos are copied into the Class prototypes object. The statics content is copied into the Class itself only. The optional setup function is called directly at the end of Class definition (not instantiation) and can further refine the defined Class. var foo = cs.trait({ protos: { bar: function () { [...] } } }) Component Creation Components are managed in hierarchical fashion within a component tree. The component tree can be traversed and its components can be created, looked up, state transitioned, communicated on and be destroyed. * ComponentJS.create(abs-tree-spec: String, class: Class[, ...]): Object ComponentJS.create(base: Component, rel-tree-spec: String, class: Class[, ...]): Object component.create(rel-tree-spec: String, class: Class[, ...]): Object Create one or more components. Their structure is specified by the absolute (abs-tree-spec) or relative (rel-tree-spec) tree specification which is string containing a set ({...}) of slash-separated (.../...) paths of component names. In other words, the specification has to follow the following grammar: abs-tree-spec ::= "/" rel-tree-spec rel-tree-spec ::= path | "{" path ("," path)* "}" path ::= rel-tree-spec | name ("/" name)* name ::= /^[^\/]+$/ For instance, the specification foo/{bar/baz,quux} is the tree consisting of the two maximum length paths: foo/bar/baz and foo/quux. For each name from left-to-right in the tree specification you have to give either a to be instantiated class constructor (Function) or an already instantiated object (Object). The create() method returns the last created component, i.e., the right-most component in the tree specification. cs.create("/{sv,ui/{one,two}}", my.sv, {}, my.ui.one, my.ui.two); cs.create (this, "model/view", model, view); cs(this).create("model/view", model, view); * ComponentJS.destroy(abs-path: String): Void component.destroy(): Void component.destroy(): Void Destroy the component uniquely identified by abs-path or the component on which this method is called upon. cs.destroy("/foo/bar") cs.destroy(comp, "foo/bar") cs("/foo/bar").destroy() Component Information Components carry a few distinct information. They can be accessed via the following getter/setter-style methods. * component.id(): String component.id(id: String): String Get current unique id of component or set new id on component and return the old id. Setting the id of a component should be not done by the application as it is done by ComponentJS internally on component creation time. cs(this).id() ? "0000000000000000000000000000001" * component.name(): String component.name(name: String): String Get current non-unique name of component or set new name on component and return the old name. Setting the name of a component should be not done by the application as it is done by ComponentJS internally on component creation time. cs("/foo/bar").name() === "bar" * component.obj(): Object Retrieve the backing Object object to the corresponding Component. cs(this).obj() === this * component.cfg(): Array(String) component.cfg(key: String): Object component.cfg(key: String, value: Object): Object component.cfg(key: String, undefined): Object Components can have key/value pairs attached for application configuration purposes. Four use cases exists for this method: 1. get array of all key strings, 2. get current configuration property identified by key, 3. set configuration property identified by key to new value value and return the old value, and 4. delete the configuration property identified by key. var value = cs("/foo/bar").cfg("quux") cs("/foo/bar").cfg("quux", value) cs ("/foo/bar").cfg("quux", undefined) Component Lookup Before performing certain operations on a component, it first have to be looked up in the component tree. As this is one of the most prominent functionalities of ComponentJS, it is directly exposed through the global API symbol. * ComponentJS(abs-path: string): Component ComponentJS(component: Component, rel-path: String): Component ComponentJS(object: Object, rel-path: String): Component ComponentJS(component: Component): Component ComponentJS(object: Object): Component Components can be looked up by absolute/relative paths from root/base components. A path is a string of slash-separated component names with four special names allowed: "." for current component name, ".." for parent component name, "*" for any component name and an empty name (C) for any component trees between current and following components. In any case, the result has to uniquely identify a single component. The following usages exist: 1. Lookup Component by absolute path path (this is usually never done explicitly, but occurs implicitly if the input parameter is already a Component). 2. Lookup Component by path path, relative to Component component. 3. Lookup Component by path path, relative to the Component corresponding to Object object. 4. Lookup Component object via backing object object. 5. Lookup Component object via the component itself (no-operation). The paths have to follow the following grammar: abs-path ::= "/" rel-path rel-path ::= name ("/" name)* name ::= "" | "*" | /^[^\/]+$/ cs("/foo/bar") /* absolute */ cs(comp, "model/view") /* relative to component */ cs(this, "model/view") /* relative to component via backing object */ cs("//bar") /* full-tree lookup */ cs(comp, "//bar") /* sub-tree lookup */ cs(this, "*/view") /* wildcard lookup */ cs(this, "..//view") /* parent sub-tree lookup */ * component.exists(): Boolean Check whether a (usually previously looked up) component (either a real existing on or the special pre-existing singleton component with name " ") really exists in the component tree. if (cs("//quux").exists()) ... if (cs("//quux").name() !== "") ... Component Tree Components are managed within a component tree. The following functions allow you to traverse this tree. * component.path(): Array(Component) component.path(separator: String): String Either retrieve as an array all Components from the current component up to and including the root component, or get the slash-separated component name path String from the root component down to and including the current component. cs("/foo/bar").path("/") ? "/foo/bar" cs("/foo/bar").path() ? [ cs("/foo/ bar"), cs("/foo"), cs("/") ] * component.parent(): Component Return the parent component of component, or null if component is the root or none component. cs(this).parent() === cs(this, "..") * component.children(): Array(Component) Return the array of child components of component. cs(this).children() * component.attach(parent: Component): Void Attach component as a child to the parent component. In case it is already attached to an old parent component, it automatically calls detach() before attaching to the new parent component. Internally used by ComponentJS on create(), but can be also used by application when moving a sub-tree within the component tree. /* migrate all children from our view1 onto our view2 */ var view1 = cs (this, "model/view1") var view2 = cs(this, "model/view2") view1.children ().forEach(function (child) { var state = child.state({ state: "created", sync: true }) child.detach() child.attach(view2) child.state(state) }) * component.detach(): Void Detach component as a child from its parent component. Internally used by ComponentJS on destroy(), but can be also used by application when moving components within the component tree. cs(this).detach() * component.walk_up(callback: Function, ctx: Object): Object Walk the component tree upwards from the current component (inclusive) to the root component (inclusive). The callback Function has to be of signature callback(depth: Number, component: Component, ctx: Object): Object and for each component it is called like "ctx = callback(depth++, comp, ctx)" where initially ctx=ctx, comp=component and depth=0 was set. var path = cs(this).walk_up("", function (depth, comp, ctx) { return "/" + comp.name() + ctx; }, "") * component.walk_down(callback: Function, ctx: Object): Object Walk the component tree downwards from the current component (inclusive) to all the transitive child components (inclusive). The callback Function has to be of signature callback(ctx: Object, component: Component, depth: Number, depth_first: Boolean): Object and for each component it is called twice(!): once like "ctx = callback(depth, comp, ctx, false)" when entering the component (before all children will be visited) and once like "ctx = callback(depth, comp, ctx, true)" when leaving a component (after all children were visited). Initially ctx=ctx, comp=component and depth=0 is set. var output = cs(this).walk_down( function (depth, comp, output, depth_first) { if (!depth_first) { for (var n = 0; n < depth; n++) output + = " "; output += "\"" + comp.name() + "\"\n"; } return output; }, "") States Components, during their life-cycle, are in various particular states. Components can be triggered to change their state. During those state transitions, enter and leave methods are called accordingly. * ComponentJS.transition(null) ComponentJS.transition(target: String, enter: String, leave: String, color: String, [source: String]) ComponentJS.transition({ target: String, enter: String, leave: String, color: String, [source: String] }) Clear all (if passed just a single null parameter) or add one state transition to target state target, either at the top of the transition stack or in the middle, above the source state source. When entering the target state, the optional component backing object method enter is called. When leaving the target state, the optional component backing object method leave is called. The color is a "#RRGGBB" string used for visualizing the state in the debugger view. The default state transition definitions are given as an example. cs.transition(null); cs.transition("created", "create", "destroy", "# cc3333"); cs.transition("configured", "setup", "teardown", "#eabc43"); cs.transition("prepared", "prepare", "cleanup", "#f2ec00"); cs.transition ("materialized", "render", "release", "#6699cc"); cs.transition("visible", "show", "hide", "#669933"); cs.transition("enabled", "enable", "disable", " #336600"); * component.state(): String component.state(state: String[, func: Function]): String component.state({ state: String, [func: Function = undefined,] [sync: Boolean = false,] [min: Boolean = undefined,] [max: Boolean = undefined] }) : String Determine the current state or request a transition to a new state of component. By default a state transition is performed asynchronously, but you can request a synchronous transition with sync. For asynchronous transitions you can await the transition finish with func. The old state is returned on state transitions. On each state transition, for each transitively involved component and each target or intermediate state, a non-capturing/non-bubbling event is internally published named "ComponentJS:state:state:enter" (after the higher state state was entered from the state below it) or "ComponentJS:state:state:leave" (after the higher state state was left towards the state below it). You can subscribe to those in order to react to state transitions from outside the component, too. By default if the current and requested state of component is just different, the current state is transitioned towards the requested state. Setting parameter min to true skips the transition if the current state is already higher or equal to the requested state. Setting parameter max to true skips the transition if the current state is already lower or equal to the requested state. cs("/ui").state("visible") * component.state_compare({ state: String }): Number component.state_compare(state: String): Number Compare the state of component with state. If component is in a lower state than state, a negative number is returned. If component is in same state than state, a zero is returned. If component is in a higher state than state, a positive number is returned. if (cs(this).state_compare("visible") < 0) ... * component.state_auto_increase(increase: Boolean): Boolean component.state_auto_increase(): Boolean Get or set component component to automatically transition to same higher/ increased state than its parent component. cs(this).state_auto_increase(true) * component.state_auto_decrease(decrease: Boolean): Boolean component.state_auto_decrease(): Boolean Get or set component component to automatically transition to same lower/ decreased state than its child components. Notice that this means that a child can drag down the parent component and this way implicitly also all of its other sibling child components. Hence, use with care! cs(this).state_auto_decrease(true) * component.guard({ method: String, level: Number }): Void component.guard(method: String, level: Number): Void Guard component component from calling the state enter/leave method method and this way prevent it from entering/leaving the corresponding state. The level can be increased and decreased. Initially it should be set to a positive number to activate the guard. Then it should be set to a negative number to (potentially) deactivate the guard. A usage with an initial call of +1 and then followed by a -1 is a boolean guard. An initial call of +N and then followed by N times a -1 call is a Semaphore-like guard which ensures that only after the Nth -1 call the guard is finally deactivated again. This is useful if you activate the guard in order to await N asynchronous operations. Then the guard should be deactivated once the last asynchronous operation is finished (independent which one of the N operations this is). A guard level of 0 resets the guard, independent what its current level is. var self = this; cs(self).guard("render", +2) $.get(url1, function (data) { self.data1 = data; cs(self).guard("render", -1) }); $.get(url2, function (data) { self.data2 = data; cs(self).guard("render", -1) }); * component.await({ state: String, func: Function, direction: String, spool: String }): Number component.await(state: String, func: Function): Number Await once that component component reaches state while the Component System goes in "upward" or "downward" direction during the state transition process. cs("/foo/bar/quux").await({ state: "visible", direction: "upward", func: function () { ... } }) * component.unawait({ id: Number }): Void component.unawait(id: Number): Void Destroy the state awaiting request identified by id, previously created by a call to await(). This is usually done implicitly through the spooling mechanism or once the await() callback was called. But it can be called manually in case one no longer wants to await the state. cs(this).unawait(id) Spools In ComponentJS there are at least 4 resource allocating operations which have corresponding deallocation operations: Model observe()/unobserve(), Socket plug ()/unplug(), Event subscribe()/unsubscribe(), Service and register()/unregister (). For correct run-time operation it is required that each allocation operation, performed in a state enter method, is properly reversed with the corresponding deallocation operation in the state leave method. As this is extremely cumbersome (especially because you have to store the identifiers returned by the allocation operations as you need them for the deallocation operation), ComponentJS provides convenient spool mechanism which all of the above allocation operations support and which also can be used by the application itself. * component.spool({ name: String, ctx: Object, func: Function, [args: Array (Object) = new Array()] }): Void component.spool(name: String, ctx: Object, func: Function, [args: Object, ...]): Void Remember action "func.apply(ctx, args)" on spool named name. The name parameter can be either just a plain spool-name "name" or a combination of (relative) component-path and spool-name "path:name". This allows one to spool on a component different from component (usually a relative path back to the component of the caller of the spool() operation). cs(this).spool({ name: "foo", ctx: this, func: function (num, str) { ... }, args: [ 42, "foo" ] }); * component.spooled({ name: String }): Number component.spooled(name: String): Number Return the number of actions which are spooled under spool named name. Usually done before calling unspool() as it would throw an exception if there are no spooled actions at all. if (cs(this).spooled("foo")) cs(this).unspool("foo") * component.unspool({ name: String }): Void component.unspool(name: String): Void Perform all actions previously spooled on spool name in reverse spooling order (those spooled last are executed first). release: function () { cs(this).unspool("materialized") } Markers An object can be "marked" with a set of names. ComponentJS internally does not use those markers at all, but the ComponentJS Debugger plugin at least uses markers named "service", "model", "view" and "controller" on components' backing object to render those components in different colors. * ComponentJS.mark(obj: Object, name: String): Void component.mark(name: String): Void Mark object obj with marker named name. An arbitrary number of markers can be added to an object. An an alternative and for convenience reasons, but only if the component classes are defined through ComponentJS' optional Class/Trait system, the traits cs.marker.{service,model,view,controller} can be mixed into. app.ui.panel.view = cs.clazz({ create: function () { cs(this).mark("view"); } ... }); app.ui.panel.view = cs.clazz({ mixin: [ cs.marker.view ] ... }); * ComponentJS.marked(obj: Object, name: String): Boolean component.marked(name: String): Boolean Checks whether object obj is marked with marker named name. This is usually interesting for ComponentJS plugin developers only. if (cs("/").marked("controller")) { ... } Properties Every component can have an arbitrary number of key/value based properties attached to it. The keys have to be of type String, the values can be of any type. A property is set on a target component but is resolved on both the target component and all parent components (up to and including the root component). This way properties feel like inherited and overrideable values which can be used for both storing component-local information and to communicate information to foreign components. * component.property({ name: String, [value: Object = undefined,] [def: Object = undefined,] [scope: String = undefined,] [bubbling: Boolean = true,] [targeting: Boolean = true,] [returnowner: Boolean = false] }): Object component.property(name: String, value: Object): Object component.property(name: String): Object Get or set property with name name and value value on component component. If bubbling is set to false a property get operation does not resolve on any parent components ("it does not bubble up to the root"). If targeting is set to false a property get operation does not resolve on the target component component (resolving starts on parent component). If returnowner is set to true instead of the property value, the owning component is returned. Finally, properties can be scoped with a child component name or even a descendant component name path: on each attempt to resolve the property, first the scoped variants are tried. This means, if a property was set with name "quux@bar" (or with name "quux" and an explicitly scope set to "bar") on component /foo, if you resolve the property with cs("/foo/ bar", "quux") you get the value, but if you resolve the property with cs("/ foo/baz", "quux") you do not get the value. This allows you to set the same property with different values for different child components. Additionally the scope can be a partial component path, too. If a property was set with name "quux@bar/baz" on component /foo, if you resolve the property with cs ("/foo/bar/baz", "quux") you get the value, but if you resolve the property with cs("/foo/bar/baz2", "quux") you do not get the value. This allows you for instance to skip so-called intermediate namespace-only components. Setting value to "null" removes the property. If no property name is found at all, def (by default the value undefined) is returned. cs(this).property("foo") Sockets Sockets are a special form of component Properties with callback functions as the values. They are intended to link Views of child/descendant components into the View of a parent/ancestor component. In contrast to regular Properties, Sockets are never resolved directly on the target component. Instead they always start to resolve on the parent component because the sockets on the target component are intended for its child/ancestor components and not for the target component itself. So, please remember to never plug a socket directly onto the target component! * component.socket({ [name: String = "default",] [scope: Object = null,] ctx: Object, plug: Function, unplug: Function [, spool: String] }): Number component.socket(ctx: Object, plug: Function, unplug: Function): Number Create a socket on component, named name and optionally scoped for the child component named scope, where plug() and unplug() calls on child/ ancestor components execute the supplied plug/unplug functions with ctx supplied as this, the object parameter of plug()/unplug() as first argument and component as the second argument. The socket() method returns an id which uniquely identifies the socket. Instead of having to manually release the socket later via unsocket() you can use the spool mechanism and spool the corresponding unsocket() operation via option spool. var ui = $(...); cs(this).socket({ ctx: ui, plug: function (el) { $ (this).append(el); }, unplug: function (el) { $(el).remove(); } }) * component.unsocket({ id: Number }): Void component.unsocket(id: Number): Void Destroy the socket identified by id, previously created by a call to socket (). This is usually done implicitly through the spooling mechanism. cs(this).unsocket(id) * component.link({ [name: String = "default",] [scope: Object = null,] target : Object, socket: String [, spool: String] }) component.link(target: Object, socket: String) Create a socket on component, named name and optionally scoped for the child component named scope, and pass-through the plug()/unplug() calls to the target component target and its socket named socket. Usually used by Controller components to link their default socket (for the View below itself) to a particular socket of a parent component (because a View should be reusable and hence is not allowed to know the particular socket intended for it). The link() method returns an id which uniquely identifies the linked socket. Instead of having to manually release the socket later via unlink() you can use the spool mechanism and spool the corresponding unlink () operation via option spool. cs(this).link({ name: "default", target: this, socket: "menu1" }) * component.unlink({ id: Number }): Void component.unlink(id: Number): Void Destroy the linked socket identified by id, previously created by a call to link(). This is usually done implicitly through the spooling mechanism. cs(this).unlink(id) * component.plug({ [name: String = "default",] object: Object, [spool: String ,] [targeting: Boolean] }): Number component.plug(object: Object): Number Plugs object into the socket named name provided by any parent/ancestor component of component. Optionally spool the corresponding unplug() operation on spool spool attached to component. Optionally (in case of targeting set to true) start the operation on component instead of its parent component. Returns an identifier for use with the corresponding unplug() operation. cs(this).plug({ object: ui, spool: "materialized" }) * component.unplug({ id: Number[, targeting: Boolean] }): Void component.unplug(id: Number): Void Unplugs the object previously plugged under id from the socket providing parent/ancestor component of component. Optionally (in case of targeting set to true) start the operation on component instead of its parent component. This is usually performed indirectly through the Spool mechanism. cs(this).unplug(id) Models When using Model/View/Controller roles for components, the Model component needs a so-called Presentation Model: an abstraction of presentation onto which both View and Controller components attach via Observer pattern. The Controller component for provisioning business information into the Model and triggering business services upon Model changes. The View component for displaying the Model information and storing events into it. * component.model(spec: Object): Object Define a model through the specification in spec. Each key is the name of a model element and the value has to be an Object with the following possible fields: value (Object) for the default value, valid (String/RegExp) for validating the values (based on the underlying validation language of the validate() method), autoreset (Boolean) for indicating that on each value write, the value should be automatically reset to the initial value, and store (Boolean) for indicating that the value should be persistently stored in the browser's localStorage. Multiple calls to the model() method on the same component incrementally add model elements. cs(this).model({ "param:realms": { value: [], valid: "[string*]" }, "data:realm": { value: "", valid: "string", store: true }, "data:username": { value: "", valid: "string", store: true }, "data:password": { value: "", valid: "string" }, "state:username": { value: "empty", valid: "string" }, "state:username-hint": { value: "", valid: "string" }, "state:password": { value: "empty", valid: "string" }, "state:password-hint": { value: "", valid: "string" }, "state:hashcode-col": { value: 0, valid: "number" }, "state:hashcode-txt": { value: "", valid: "string" }, "state:button-enabled": { value: false, valid: "boolean" }, "event:button-clicked": { value: false, valid: "boolean", autoreset: true } }) * component.value({ name: String, [op: String,] [value: Object,] [force: Boolean,] [injected: Boolean] }) component.value(name: String, [value: Object,] [force: Boolean]) Get the value of component's model element named name or set the value of component's model element named name to value. As each value change causes observers to be triggered, by default changing a value to the same value does not trigger anything. But if force is true even setting a model element to its current value triggers observers. Setting the option injected to true should be done by plugins only and prevents model value observers from rejecting the (already injected) value. var val = cs(this).value("foo") cs(this).value("foo", "bar") If you store arbitrary sub-structured values, you can make name a path full specification based on the language supported by the select() method: var val = cs(this).value("foo.bar[1].baz['the-quux']) cs(this).value ("foo.bar[1].baz['the-quux']", "bar") In addition to the basic get/set operations on scalar values, you can also use array and hash operations on collections by using the op option. Supported op values are "get", "set", ["splice",offset,remove], "delete", "push", "pop", "unshift" and "shift". The last four array operations are internally translated to the corresponding splice operation. The arguments to the splice operation are the same as for JavaScript's Array.prototype.splice: "offset" is the 0-based offset into the array to operate at and "remove" is the number of elements to remove at "offset" (before the value is added). The operations get/set/delete operate on collection elements while the operations splice/push/pop/unshift/shift operate on collections, hence you have to provide a path in name which is suitable for them. The operations get/set/delete can operate on both array and hash elements while splice/push/pop/unshift/shift can operate on array objects only. To illustrate the functionality see the following comparisons between the standard JavaScript variable access code and the ComponentJS model value access code. First, working with scalars: // val = foo.bar val = cs(this).value("foo.bar") val = cs(this).value({ name: "foo.bar", op: "get" }) // foo.bar = "quux" cs(this).value("foo.bar", "quux") cs(this).value({ name: "foo.bar", op: "set", value: "quux" }) Second, working with Arrays: // foo.bar = [] cs(this).value("foo.bar", []) cs(this).value({ name: "foo.bar", value: [] }) // len = foo.bar.length len = cs(this).value ("foo.bar").length // val = foo.bar[42] val = cs(this).value("foo.bar[42]") val = cs(this).value({ name: "foo.bar[42]", op: "get" }) // foo.bar[42] = "quux" cs(this).value("foo.bar[42]", "quux") cs(this).value({ name: "foo.bar[42]", op: "set", value: "quux" }) // foo.bar.splice(1, 0, "quux") cs(this).value({ name: "foo.bar", op: [ "splice", 1, 0 ], value: "quux" }) // foo.bar.push("foo") cs(this).value({ name: "foo.bar", op: "push", value: "foo" }) // val = foo.bar.pop() val = cs(this).value({ name: "foo.bar", op: "pop" }) // foo.bar.unshift("bar") cs(this).value({ name: "foo.bar", op: "unshift", value: "bar" }) // val = foo.bar.shift() val = cs(this).value({ name: "foo.bar", op: "shift" }) Third, working with hashes: // foo.bar = {} cs(this).value("foo.bar", {}) cs(this).value({ name: "foo.bar", value: {} }) // keys = Object.keys(foo.bar) keys = Object.keys (cs(this).value("foo.bar")) // val = foo.bar.baz // val = foo.bar["baz"] val = cs(this).value("foo.bar.baz") val = cs(this).value("foo.bar['baz']") val = cs(this).value({ name: "foo.bar.baz", op: "get" }) val = cs (this).value({ name: "foo.bar['baz']", op: "get" }) // foo.bar.baz = "quux" // foo.bar["baz"] = "quux" cs(this).value("foo.bar.baz", "quux") cs (this).value("foo.bar['baz']", "quux") cs(this).value({ name: "foo.bar.baz", op: "set", value: "quux" }) cs(this).value({ name: "foo.bar ['baz']", op: "set", value: "quux" }) // delete foo.bar.baz // delete foo.bar["baz"] cs(this).value({ name: "foo.bar.baz", op: "delete" }) cs (this).value({ name: "foo.bar['baz']", op: "delete" }) * component.touch({ name: String, }) component.touch(name: String) Touches the value of component's model element named name, without changing the value but with triggering all its "set" observers (its "changed" observers are not triggered). This can be useful for firing "set" observers manually. cs(this).touch("foo") * component.observe({ name: String, func: Function, [touch: Boolean = false,] [boot: Boolean = false,] [op: String = "set",] [spool: String = null,] [ noevent: Boolean = false] }): Number component.observe(name: String, func: Function): Number Observe the value of component's model element named name for op operations (by default "set" operations). For "get" operations, the callback function func has to be of signature func(ev: Event, value: Object): Void. For "set" (and "splice", "delete", "push", "pop", "unshift", "shift") and "changed" operations, the callback function func has to be of signature func(ev: Event, value-new: Object, value-old: Object, op: Object, path: String): Void. Both types of callbacks can override the value by using ev.result( value). The observe() method returns an id which uniquely identifies the observation. Instead of having to manually release the observation later via unobserve() you can use the spool mechanism and spool the corresponding unobserve() operation via spool. Option noevent (similar to the same option for subscribe()) prevents the passing of the event parameter ev to the callback function func in case you don't need it. Option touch causes observe() to execute touch() internally at the end of its observation registration operation for bootstrapping purposes. This indirectly causes the callback function func (and also all other observers) to execute. Option boot causes observe() to execute the callback function func once at the end of its observation registration operation for bootstrapping purposes. This explicitly causes the callback function func (and only func and no other observers) to excecute. id = cs(this).observe("state:username", function (ev, username) { ... }) * component.unobserve({ id: Number }): Void component.unobserve(id: Number): Void Release the observation identified by id, previously acquired by a call to observe(). This is usually done implicitly through the spooling mechanism. cs(this).unobserve(id) Events The Event mechanism is a central one in ComponentJS. Both Models and Services are internally based on the Events mechanism. An Event is an object published towards a target component. It is delivered in 4 phases: * In phase 1 (the "capturing" phase) the Event is delivered to all components on the path from the root component (inclusive) towards the target component (exclusive). * In phase 2 (the "targeting" phase) the Event is delivered to the target component. * In phase 3 (the "spreading" phase) the Event is delivered to all descendant components of the target component in a depth-first traversal order. * In phase 4 (the "bubbling" phase) the Event is delivered (again) to all components on the path from the target component (exclusive) to the root component (inclusive). Event objects are implicitly created by the publish() operation and they provide various getter/setter methods: * target() (Component): target component the event is send to * propagation() (Boolean): whether event propagation should continue * processing() (Boolean): whether final default event processing should be performed * dispatched() (Boolean): whether event was dispatched at least once to a subscriber * decline() (Boolean): whether event was declined by subscriber * state() (Boolean): state of dispatching: capturing, targeting, spreading or bubbling * result() (Object): optional result value event subscribers can provide * async() (Boolean): whether event is dispatched asynchronously + component.subscribe({ name: String, [spec: Object = {}], [ctx: Object = component,] func: Function, [args: Object[] = []], [capturing: Boolean = false], [spreading: Boolean = false], [bubbling: Boolean = true], [ noevent: Boolean = false], [exclusive: Boolean = false], [spool: String = null] }): Number component.subscribe(name: String, func: Function, [args: Object, ...]): Number Subscribe to event name (optionally sub-specified via spec) on component component and execute callback func as func(ev: Event , args: Object, ..., sargs: Object, ...) once the event is dispatched to component after it was published. By default an event is dispatched in the (mandatory) targeting and (optional) bubbling phases. o Option ctx allows you to give "this" a particular value for the callback func. Option args allows you to pass additional parameters to func (before those passed by publish(). o Option noevent does not pass the ev: Event parameter to func. o Setting option capturing to "true" indicates that the event should be also dispatched in the capturing phase. o Setting option spreading to "true" indicates that the event should be also dispatched in the spreading phase. o Setting option bubbling to "false" indicates that the event should not be dispatched in the bubbling phase. o Option exclusive can be set to "true" for an exclusive subscription, i.e., a subscription which prevents any subsequent subscriptions. The subscribe() method returns an id which uniquely identifies the subscription. Instead of having to manually release the subscription later via unsubscribe() you can use the spool mechanism and spool the corresponding unsubscribe() operation via option spool. cs(self).subscribe({ name: "data-loaded", spool: "prepared", func: function (ev, data, info) { ... } }) + component.unsubscribe({ id: Number }): Void component.unsubscribe(id: Number): Void Release the subscription identified by id, previously acquired by a call to subscribe(). This is usually done implicitly through the spooling mechanism. cs(this).unsubscribe(id) + component.publish({ name: String, [spec: Object = {},] [async: Boolean = false,] [capturing: Boolean = true,] [spreading: Boolean = false,] [ bubbling: Boolean = true,] [completed: Function,] [resultinit: Object = undefined,] [resultstep: Function,] [directresult: Boolean = false,] [ noresult: Boolean = false,] [firstonly: Boolean = false,] [silent: Boolean = false,] [args: Object[] = []] }): Object component.publish(name: String, args...: Object): Object Publishes an Event to component component named name and with optional arguments args. By default, the event is intended to be dispatched in the (mandatory) targeting and (optional) capturing and bubbling phases. The following options allow you to further control the event publishing process: o Option spec allows you to sub-specify/parametrize the event with arbitrary key/value pairs in case the name is too generic. This then has to be matched by the corresponding spec option of method subscribe(). o Option async allows the event processing to occur asynchronously. The default is synchronously. o Setting option capturing to "false" indicates that the event should not be intended to be dispatched in the capturing phase. The default is to be dispatched in the capturing phase. o Setting option spreading to "true" indicates that the event should also be intended to be dispatched in the spreading phase. The default is not to be dispatched in the spreading phase. o Setting option bubbling to "false" indicates that the event should not be intended to be dispatched in the bubbling phase. The default is to be dispatched in the bubbling phase. o Option completed executes the specified callback function once the event was dispatched to subscribers in all possible phases. This allows you to react at the end of async=true events. o Options resultinit and resultstep allow you to control how the results of subscribers should be aggregated. The resultinit provides the initial value to start the aggregation. The resultstep is a callback function with signature "(old: any, cur: any): any" which aggregates the initial/old value and the current value into a new value. The default for resultinit is "undefined" and the default for resultstep is "function (old, cur) { return cur }", meaning that just the last result will be kept. The result is delivered with the result() method of returned Event objects. o Option directresult forces publish() to directly return ev.result() instead of the Event object ev. o Option noresult forces publish() to directly return undefined instead of the Event object ev. This allows publish() to internally optimize the event handling in case no Event object is necessary at all. o Option firstonly automatically stops the event propagation/delivery (with ev.propagation(false)) once the first subscriber has accepted the value (meaning: has not called ev.decline() on the Event object). o Option silent can be used to disable the expensive creation of internal debugging messages related to the Event processing. This can be used to speed-optimize the event processing. o Option args can be used to pass arguments to the subscribers. The subscribers receive those arguments at the end of their parameter list. cs(this).publish("data-loaded", data, info) Services Services are loosely coupled method calls across components. The functionality provider does register() the service and the functionality consumer does call() the service. + component.register({ name: String, [ctx: Object = component,] func: Function, [args: Object[] = [],] [spool: String,] [capturing: Boolean = false,] [spreading: Boolean = false,] [bubbling: Boolean = true] }): Number component.register(name: String, func: Function): Number Register a service name on component with the implementing callback function func. The function returns an identifier for unregister(). The following options can be used to control the later service calls: o Option ctx can be used to set the this pointer for func. o Option args can be used to pass additional parameters to func (before the args of call()!). o Option spool can be used to spool the corresponding unregister() call. o Option capturing can be set to true to provide the service also in the "capturing" phase. o Option spreading can be set to true to provide the service also in the "spreading" phase. o Option bubbling can be set to false to not provide the service in the "bubbling" phase. var id = cs(this).register({ name: "load-entity", args: [ em ], func: function (em, clazz, id) { return em.findById(clazz, id); } }) + component.unregister({ id: Number }): Void component.unregister(id: Number): Void Release the registration identified by id, previously acquired by a call to register(). This is usually done implicitly through the spooling mechanism. cs(this).unregister(id) + component.callable({ name: String[, value: Boolean] }): Boolean component.callable(name: String[, value: Boolean]): Boolean Checks whether a registered service is callable/enabled or enable/ disable a registered service. On every change to the "callable" status of a service, an internal event named "ComponentJS:service:name :callable" is published with two arguments: the new and old boolean value. cs(this).subscribe("ComponentJS:service:load-person:callable", function (old, new) { if (new) { /* react on now callable service */ } }) cs (this).callable("load-person", false) cs(this).callable("load-person", true) + component.call({ name: String, [args: Object[] = [],] capturing: Boolean = false,] spreading: Boolean = false,] bubbling: Boolean = true ] }): Object component.call(name: String [, args...: Object]): Object Call service named name on component component, optionally passing it the arguments args (after the optional args of register()!). The following options can be used to control the service call: o Option capturing can be set to true to deliver the underlying service event also in the "capturing" phase. o Option spreading can be set to true to deliver the underlying service event also in the "spreading" phase. o Option bubbling can be set to false to not deliver the underlying service event in the "bubbling" phase. var person = cs("/sv").call("load-entity", "Person", 42) Test-Driving ComponentJS has optional support for test-driving an application, based on asynchronously executed use-cases with the help of its testdrive plugin. + ComponentJS.suite(): Void Notice: requires ComponentJS plugin testdrive to be loaded! Notice: requires external library jQuery to be loaded! Open the interactive dialog of use-cases which can be driven. Usually this is executed from within the application itself (in case the test-drive functionality is used for something like UI macros) or through an external bookmark. /* open test-drive suite from within application */ if (cs.plugin ("testdrive")) cs.suite(); + [new] ComponentJS.promise(): Promise [new] ComponentJS.promise(executor: Function): Void Notice: requires ComponentJS plugin testdrive to be loaded! Return a Promise/A+ based promise, internally backed by an embedded "Thenable" implementation. The alternative usage with an executor (of type "(fulfill: (value?: any) => Promise, reject: (value?: any) => Promise) => void" in TypeScript definition syntax) avoids a temporary variable. /* standard usage */ var promise = new $cs.promise(); doSomethingAsync( function onSuccess (msg) { promise.fulfill(msg); }, function onError (err) { promise.reject(err); } ); return promise.proxy; /* alternative usage (regular) */ return new $cs.promise(function (fulfill, reject) { doSomethingAsync( function onSuccess (msg) { fulfill(msg); }, function onError (err) { reject(err); } ); }).proxy; /* alternative usage (compact) */ return $cs.promise(function (fulfill, reject) { doSomethingAsync(fulfill, reject); }).proxy; + ComponentJS.usecase({ name: String, desc: String, [conf: Object,] func: Function }): Void ComponentJS.usecase(name: String, desc: String, func: Function): Void Notice: requires ComponentJS plugin testdrive to be loaded! Define a single use-case of unique name name, with description desc, default configuration conf and the use-case executing function func (of type "() => void" in TypeScript definition syntax). The name is used for driving the use-case with drive() and conf can be overwritten with drive(). The function func can either execute synchronously or asynchronously. In case of a synchronous execution, the return value of func does not matter. In case of an asynchronous execution, the return value of func has to be a Promise/A+ based promise (usually created with ensure(), await(), drive or poll implicitly, or with promise() explicitly). The callback function receives the actual configuration as the first parameter. if (cs.plugin("testdrive")) { cs.usecase("reset", "reset all login dialogs", function () { return cs.ensure("/ui/panel/model", "prepared").then(function (comp) { comp.value("event:reset", true); }); }); cs.usecase({ name: "login", desc: "fill out a login dialog", conf: { num: 1, realm: "foo", username: "bar", password: "baz!quux" }, func: function (conf) { return cs.ensure("//login" + conf.num + "/model", "prepared") .then(function (comp) { comp.value("data:realm", conf.realm); comp.value("data:username", conf.username); comp.value ("data:password", conf.password); comp.value("event:login-requested", true); }); } }); cs.usecase({ name: "awaitStatus", desc: "await the status to show a particular text", conf: { num: 1, realm: "foo", username: "bar", password: "baz!quux" }, func: function (conf) { var re = new RegExp("login from \".*login" + conf.num + "\" with realm \"" + conf.realm + "\", username \"" + conf.username + "\" and password \"" + conf.password + "\""); return cs.poll(function (fulfill, reject) { return $("div.status").text().match(re); }, function () { return cs.once($("div.status"), "mutation"); }); } }); cs.usecase("all", "fill out all login dialogs", function () { return cs.drive("reset") .then (function () { return cs.drive("login", { num: 2 }); }) .then(function () { return cs.drive("awaitStatus", { num: 2 }); }) }); } + ComponentJS.drive({ name: String, [conf: Object,] [timeout: Number] }): Promise ComponentJS.drive(name: String, [conf: Object,] [timeout: Number]): Promise Notice: requires ComponentJS plugin testdrive to be loaded! Drive a single use-case name, with optional configuration conf and a run-time timeout of timeout (by default 10*1000) milliseconds. The function returns a Promise/A+ promise which is either fulfilled (with dummy value true) or rejected with an error message. cs.drive("login", { num: 3 }, 2*1000).then(null, function (e) { alert ("failed to login"); }); + ComponentJS.ensure({ path: String, state: String, [min: Boolean = true, ] [max: Boolean = false,] [sync: Boolean = false] }): Promise ComponentJS.ensure(path: String, state: String): Promise Notice: requires ComponentJS plugin testdrive to be loaded! Ensure that a component under path reaches a particular state which is at least (in case of min = true), and/or at most (in case of max = true) a particular state, by synchronously (in case of sync = true) or asynchronously (by default), triggering a state change on the component under path. The state change is explicitly trigger by ensure() itself. The function returns a Promise/A+ promise which is either fulfilled with the component object corresponding to path or rejected with an error message. cs.ensure({ path: "//login1/model", state: "prepared", min: true }) .then(function (comp) { comp.value("data:username", "foo"); }); + ComponentJS.await({ path: String, state: String, [direction: String = "enter"] }): Promise ComponentJS.await(path: String, state: String, [direction: String = "enter"]): Promise Notice: requires ComponentJS plugin testdrive to be loaded! Awaits that a component under path reaches a particular state, either on enter (in case of direction = "enter") or leave (in case of direction = "enter"). The enter/leave methods of the component will be already called in both cases. The component under path is NOT required to already exist. It is allowed that it springs into existence later. There is NO state change trigger by await() itself. The function returns a Promise/A+ promise which is either fulfilled with the component object corresponding to path or rejected with an error message. cs.await({ path: "//login1/model", state: "prepared" }).then(function (comp) { comp.value("data:username", "foo"); }); + ComponentJS.poll({ check: Function, [wait: Function,] [max: Number = 600] }): Promise ComponentJS.poll(check: Function, [wait: Function,] [max: Number = 600] ): Promise Notice: requires ComponentJS plugin testdrive to be loaded! Polls for a situation to occur by checking its occurance condition with check (of type () = Boolean> in TypeScript definition syntax) and in case of a still false return waits through wait and repeat from the beginning with a new round with check. The wait function has to be of type "() => Promise" and usually delays processing (usually with setTimeout()) and then resolves. If you pass a Number instead of a Function to wait, a default implementation is used which waits the number of milliseconds. The default for wait is 100, i.e., it internally maps onto "function () { return cs.sleep(100); }". cs.usecase({ name: "awaitStatus", desc: "await the status to show a particular text", conf: { num: 1, realm: "foo", username: "bar", password: "baz!quux" }, func: function (conf) { var re = new RegExp ("login from \".*login" + conf.num + "\" with realm \"" + conf.realm + "\", username \"" + conf.username + "\" and password \"" + conf.password + "\""); return cs.poll(function (fulfill, reject) { return $("div.status").text().match(re); }, function () { return cs.once($("div.status"), "mutation"); }); } }); + ComponentJS.sleep({ ms: Number, }): Promise ComponentJS.sleep(ms: Number): Promise Notice: requires ComponentJS plugin testdrive to be loaded! Sleeps a certain amount of milliseconds (with setTimeout()) and then resolves the promise which is returned by this function. cs.sleep(100).then(function () { ... }) + ComponentJS.once({ selector: String/Object, events: String, [ subselector: String = null] }): Promise ComponentJS.once(selector: String/Object, events: String, [subselector: String = null]): Promise Notice: requires ComponentJS plugin testdrive to be loaded! Notice: requires external library jQuery to be loaded! Awaits once(!) with jQuery's one() method for a DOM event to occur and then resolves the promise (with the jQuery event object as the value) this function returns. The three parameters selector, events and subselector are directly passed through to jQuery by mapping onto the internal call "jQuery(selector).one(events, subselector, ...)". Additionally, in case events = "mutation" the function internally uses the HTML5 MutationObserver functionality to await a DOM mutation (in this case subselector is ignored). cs.once("ul.list", "click", "li").then(function (ev) { ... }) Vue Data Binding ComponentJS has optional support for bidirectional data binding with the help of the vue plugin and the underlying Vue rendering engine. The vue plugin provides the following distinct features: + It provides a new ComponentJS API method vue() for rendering a view mask into a Vue instance (see below). + It provides a new ComponentJS API method unvue() which accepts a Vue instance (as previously created by the vue() method) and unplugs and destroys it (see below). + For all generated Vue instances, it generates for all ComponentJS model values (from the current component, usually a view component, up to the root component) Vue computed getter/setter fields (name) which perform true bi-directional data-binding. They can be used like e.g. v-bind: attr="name" in the view mask. The ComponentJS model values (by convention) named eventXXX are treated specially: Vue methods of the same name are generated for them, which can be used like e.g. v-on:click="eventXXX" (for passing a Vue Event instance, which is then converted into true) or v-on:click="eventXXX(arg)" (for receiving an arbitrary single argument arg) in the view mask. The names of ComponentJS model values are converted into valid Vue symbols by replacing all [^a-zA-Z0-9_$] characters with "_" characters. Hence it is recommended to use camel-case ComponentJS model values like paramFooBar, commandFooBar, stateFooBar, dataFooBar, and eventFooBar only. + For all generated Vue instances, it generates trampoline methods for all methods named utilXXX in the backing object of the ComponentJS (view) component. They can be used like v-if="utilXXX(...)" in the view masks for complex checks or calculations, if really necessary. + For all generated Vue instances, it creates ComponentJS sockets for all DOM elements in the Vue template which are tagged as sockets. For this, all HTML elements with data-socket="[name][@scope]" lead to a ComponentJS socket with name and scope. The default name is default. The default scope is no scope at all. The ctx parameter of ComponentJS socket() is the DOM element itself. + It intercepts the ComponentJS socket() method and makes its plug and unplug parameters optional instead of required. The provided fallback plug and unplug functions now accept Vue (and jQuery) instances as returned by the ComponentJS API method vue(). This plugin requires Vue, but jQuery is not directly required. The extra ComponentJS API methods are: + ComponentJS.vue({ options: Object, [spool: String] }): Object ComponentJS.vue(options: Object, [spool: String]): Object Notice: requires ComponentJS plugin vue to be loaded! Notice: requires external library VueJS to be loaded! This method accepts a required Vue options parameter (also accepted as a first positional parameter) and an optional ComponentJS spool parameter (also accepted as a second positional parameter). The options parameter is required to have either a Vue template field (with the view mask as either a string, a DOM fragment or a jQuery DOM fragment) or a Vue render field (with a compiled Vue render function). The result is a rendered but unmounted Vue model instance which can be later plugged into a ComponentJS socket with plug() and/or further manipulated (with jQuery) through its Vue $el field. + ComponentJS.unvue({ vm: Object }): Void ComponentJS.unvue(vm: Object): Void Notice: requires ComponentJS plugin vue to be loaded! Notice: requires external library VueJS to be loaded! Unplug and destroy the Vue model instance. Usually this is not called directly, as one should use the spool parameter of vue() to automatically unplug and destroy the Vue model instance. API Management + symbol + version Library Management + bootstrap + shutdown + plugin Debugging + debug + debug_instrumented + debug_window Code Structuring + ns + select + validate + params + attribute + clazz + trait Component Creation + create + destroy Component Information + id + name + obj + cfg Component Lookup + ComponentJS + exists Component Tree + path + parent + children + attach + detach + walk_up + walk_down States + transition + state + state_compare + state_auto_increase + state_auto_decrease + guard + await + unawait Spools + spool + spooled + unspool Markers + mark + marked Properties + property Sockets + socket + unsocket + link + unlink + plug + unplug Models + model + value + touch + observe + unobserve Events + subscribe + unsubscribe + publish Services + register + unregister + callable + call Test-Driving + suite + promise + usecase + drive + ensure + await + poll + sleep + once Vue Data Binding + vue + unvue ComponentJS Application Programming Interface (API)