EXC | DEV | Documentation
This topic applies to the Front-End framework.
EXC attempts to simplify application development following many design patterns found under the principle of Inversion of Control (IOC). Inversion of Control attempts to solve software architecture problems related to the structure of an applications components (libraries, services, etc) and how we wire those things together and manage dependencies.
While you may read about IOC mainly in the context of decoupling dependencies (dependency inversion), IOC is actually a matter of strategies to abstract and decouple the control flow of an application.
In EXC we use dependency inversion, control flow composition, event-driven logic among other abstractions. Of course one important goal is to have an easy to use implementation that people can learn and master quickly. EXC's approach is far from what you find in other frameworks like Java Spring, etc, but more in the line to cujojs.
Decoupling is a big deal In all of this, but beyond the interoperability of different components (wiring together, hint!) it offers other advantages:
In EXC the wire is the library used to implement application composition and control. While not too original, the wire
comes from the notion of "wiring" our app together.
A wire can be used to do composition of components, the application or represent a logical flow.
EXC implements a simple DSL (domain-specific-language) to represent the compositions and abstractions supported by WIRE.
A DSL is always represented by a Javascript object literal or JSON object. The js object that represents a WIRE DSL is called a specification or "spec". A spec is a declarative object that can represent a lot of things.
A spec defines and make things happen by means of verbs. A verb is a special key or object structure that tells the WIRE to do something. A verb may load resources and libraries, create components, set some bindings for events and data, bootstrap an application and kick its execution, among others.
You will see verbs like $ref
, $emit
, $load
, these "verbs" give meaning to the plain JS object that a spec is. EXC defines a core set of verbs but other plugins or modules may add their own verbs.
A verb is an abstraction that allows to interact and execute operations provided by different components and libraries without and explicit interaction or connection. Verbs are used to decouple interaction of different layers in your application.
A verb like everything else in the spec is a Javascript object literal or JSON object with a key name that starts with a dollar sign "$".
A reference is a verb entry identified with a $ref
key (similar to the JSON REF) used to bind a value to a property inside an object.
A $ref
is meant to resolve a value (to lookup somewhere) and are no intended as basic variables. A reference may be used as an assigment (binding) or as a parameter to another verb.
The common use of $ref
is as a binding assigment:
{
aValue : { $ref: 'mycomponent.value'}
}
Values binded by a $ref
assigment will be live, that is its value is update in many scenarios.
A spec may reference a DOM element by using a $node:
resolver in a $ref
:
{
aNode : { $ref: '$node:.myselector'}
}
This entry will add a property aNode
to the object that points to the first DOM element matched by the selector.
We may also specify an at
key with a selector pointing to the parent element where the element is:
{
aNode : { $ref: '$node:.myselector', at:'div.parent'}
}
A component is an object representing a collection of functionality and logic that we can reference by its name. A component can also be an object of a library or module that you loaded see verbs $load and $require.
When the spec has an object with a $create
verb a property of type component is created. For example:
{
myComponent: {
$create: {
//crerate options go here....
}
}
}
In this case the name of the component is "myComponent" and myComponent
becomes a property of the current wire context.
The $create
key is a plain object with options to create our component. An empty $create
will use a plain object as a start.
We can use a factory function to return an object to be use as the basis of our component or a constructor function. The difference being that a constructor function is called with new
and a factory function is just invoked and expected to return an object.
{
myComponent: {
$create: {
factory: buildFancyObject, //call a factory function
}
}
}
{
myComponent: {
$create: {
constructor: fancyObject, //call a constructor function
}
}
}
Another option is to use a CommonJS module that exports a single object.
{
myComponent: {
$create: {
//loads a module and assigns its export as the base object
module: "./js/myFancyObject.js",
}
}
}
When using the option module
the component creation may be delayed until the module is loaded.
If you use a $load
verb to pre-load content then you can use the require
option to just get a reference to an existing export.
{
myComponent: {
$create: {
//assigns an existing export as the base object
require: "./js/myFancyObject.js",
}
}
}
Key value pairs other than the $create
verb as procesed as regular WIRE specs in doing so we can extend and combine functionality into a component with ease.
myComponent: {
$create: {
//Our create options ...
},
//add a handler for the "DoThis" message
//with a traditional callback function
onDoThis: function(msg){
console.log("@myComponent.doThis()");
console.log(msg);
console.log("This=%o", this); //this points to myComponent
//get the parent context of myComponent
console.log("This.parent=%o", this.parent);
}
},
A message handler may also point to a set of actions:
myComponent: {
//...
//add a handler for the "DoThis" message
//with a traditional callback function
onDoThis: [
{$emit: "doSomethingElse"},
{$emit: "doSomethingElse"},
]
},
myComponent: {
$create: {
//Our create options ...
},
//add an observable property
firstName: "Jose"
},
A key like firstName: "Jose"
in the component's spec will create a property in the component created. We can access a property from a component like traditional variable.
myComponent: {
$create: {
//Our create options ...
},
firstName: "Jose", //add property
onDoThis: function(msg){
console.log("First Name is %s", firstName);
}
},
Properties created in a spec are binded values that are also observable through the wire.
component1.wire.observeKey("firstName", aCallBack)
The keys done
and error
can specified a function or a spec to be executed when the component is created succesfully or if ther was an error. The done
option is particularly usefull to initiate or trigger a chain of operations.
{
myComponent: {
$create: {
module: "./js/module3.js",
//a wire with actions to execute if the component is created
done: [
{$emit:"createAppMenus"},
function(){ //a plain callback
console.log("doing something!");
//this is done, parent is myComponent
console.log("Name=%s", this.parent.name);
}
]
},
onCreateAppMenus: function(msg){
//some code here...
}
},
}
Verb: $emit Actionable: Yes Type: Verb-Key
The $emit
verb will emit a message in the application's service bus. The verb is
{$emit: "message_name"}
Other keys provided to an $emit
verb become values passed in the message's data.
{$emit: "setTitle", fname:"Jose", lname:"Cuevas" }
Somewhere in a message handler we use the message.data
to access the values:
onSetTitle : function(msg){
console.log( "Hello %s %s", msg.data.fname, msg.data.lname);
}
Verb: $do Actionable: Yes Type: Verb-Key
The $do
verb will let you specify an action or list of actions to execute.
{$do: [
{$emit: "message_name"},
function(){
console.log("executing a plain function...");
}
]}
The value of a $do
key is either a function or an array of actionable items.
Verb: $require Actionable: No Type: Verb-Key
Use the $require
verb to add an existing object, module or spec to a wire.
$require
WILL not load a module, the module must be already loaded with the$load
verb or other means. We encourage you to manage your dependencies using a builder or packing utility.
{
//asumes exports is the object representing the library
myLibrary: {$require: "./js/mylib.js"}
}
{
//specify with get which export to assign.
myLibrary: {$require: "./js/mylib.js", get:"UIAdapter"}
}
When the option get
is not used $require assumes that your export points to the library, unless:
exports.spec
will be loaded as an specification to create a wire.exports.component
will be created as a component.Verb: $load Actionable: Yes Type: Verb
The $load
verb is a top level key that you specify to fetch resources (css, js) and load modules.
You can specify two set of assets to process with include and require. Includes are assets to be loaded but we do not care the order or if the app is unable to load them. A require in the other hand must be loaded for the $load
to be successful, they are loaded sequentially and you may specify if the loading must wait for the current item before loading the next (this is useful for dependancies).
{
$load: {
include: [
//items to load...
],
require: [
//items that must be loaded...
]
}
}
An entry in include
or require
may by a string with a url or a plain object with the assets options.
When specifying the assets options the following keys are available:
Key | Example | Description |
---|---|---|
url | url: "./a/url/myfile.js" | A string with the url of the file. You must use the type key when the system may not infer if the resource is a js or css file. Required |
is_required | is_required: true | A boolean that indicates if this item is required to be loaded. Default is true. |
is_forced | is_forced: true | A boolean that indicates that the resource must be reloaded ignoring any cache. Default is false. |
type | type: "js" type: "css" type: "json" | A string indicating the resource type. |
use | use: "aGlobalObject" use: "anExportName" | Adds an object or module to the current wire or context. Depending on the js file loaded you can specify a string with the name of a global object loaded or the name of an exported (module.exports) object. |
wire | wire: true | Used to indicate that the object loaded with the use key must be treated as an specification to create a wire. |
Some examples:
{
$load: {
include: [
"./css/client_style.css"
],
require: [
"./js/basic.js",
{url:"./js/config.js", is_forced: true},
{url:"./js/corelib.js"},
{url:"./js/node_modules/mylib.js", use:"aLib"},
{url:"./js/spec.js", use:"aspec", wire:true}
]
}
}
A $load
may have a done
option with actionable items to run when the loading is completed and in the opposite side an error
option with actionable items will be execute when the loading fails:
{
$load: {
require: [
//items that must be loaded...
],
done: [
{$emit: "processConfig"}
],
error: [
{$emit: "appFailedToLoad"}
]
}
}
A paths
option can be added to config path aliases.
{
$load: {
require: [
{url:"myLib"},
{url:"modules/alib.js"}
],
paths: {
'myLib': './js/include1.js',
'modules': './js',
},
}
}
EXC implements a simple DSL (domain-specific-language) to represent the compositions and abstractions supported by WIRE.
A DSL is always represented by a Javascript object literal or JSON object. The js object that represents a WIRE DSL is called a specification or "spec".
TERM | SYNTAX | DESCRIPTION |
---|---|---|
PRIMITIVE | A literal string, number, boolean or date, regex. | |
OBJECT | A JS object other than an Array | |
ARRAY | A JS array object. | |
KEY | Any valid key that may appear on the left side of a key-value pair in a JS literal object or JSON. | |
VALUE | PRIMITIVE OBJECT ARRAY | Any valid value that may appear on the right side of a key-value pair in a JS literal object or JSON. |
PARAMS | PARAM1:VALUE1, ... | Set of key value pairs in a JS literal object or JSON |
VERB_NAME | $name "$name" | A key name that starts with the "$" sign. |
VERB | VERB_NAME : {...} | A JS literal object or JSON that represents a WIRE composition. It appears as a key-value pair, the key is always a VERB_NAME and the right side is the argument/parameters passed to said verb. |
VERB_KEY | {VERB_NAME: VALUE, PARAMS} | A JS literal object or JSON that represents a WIRE composition. A VERB_KEY may appear as the right side of a key-value pair or as an item in an array. Only one VERB_NAME may by present in the object, all other key-value pairs are arguments for the verb. |
PROPERTY | KEY : VALUE | A traditional key-value pair is converted to a property of the object created or the actual context. |
ACTION | VERB-KEY FUNCTION ACTIONS | An actionable value. |
ACTIONS | [...] | An array that may include: VERB-KEY , FUNCTION . |
MSG_HANDLER | onMessageName : FUNCTION onMessageName : ACTIONS | A key who's name starts with "on" followed by message name, becomes a message handler. |