ERoot

Root interface of the library, available via global variable excellent.

You can make this interface also available via an alias name that can be set via attribute e-root or data-e-root on the HTML element:

<HTML e-root="app">
Source:
See:

Members

(readonly) modules :Object.<JSName, {}>

Namespace of all modules, registered with method addModule.

Type:
Source:
See:

(readonly) services :Object.<JSName, {}>

Namespace of all services, registered with method addService.

Type:
Source:
See:

(readonly) version :string

Library version, automatically injected during the build/compression process, and so available only with the compressed version of the library. But you should be using excellent.min.js always, because the library is distributed with the source maps.

Type:
  • string
Source:

Methods

addAlias(name, ctrlNames, cbopt)

Creates a simplified controller as a configurable alias.

Any controller that extends other controllers, using method EController.extend, is effectively an alias. And this method simplifies creation of such controllers, suitable when you only want a new alias for extended controller(s), and optionally configured.

This method is particularity useful during integration into an app, creating simpler aliases and configuring their extended controllers at the same time. You may decide to have a few such controllers, as a means of simplifying the app setup, i.e. instead of searching for a controller to change its configuration, you can resort to using a pre-configured alias, even if it is for a single controller:

app.addAlias('errorBoard', 'someModule.panels.centered', function(c) {
   // here we presume that controller 'someModule.panels.centered'
   // implements method setConfig(configObject):
   c.setConfig({bgColor: 'red', color: 'white'});
});

In cases when all you want is to create an alias for a single controller name, inside an app or a module, method getCtrlFunc may be more appropriate for this.

Parameters:
Name Type Attributes Description
name JSName

New controller/alias name. Trailing spaces are ignored.

ctrlNames CtrlName | Array.<CtrlName>

Either a single controller name, or an array of names, for which the new alias is created.

Trailing spaces are ignored.

cb function <optional>

Optional callback for re-configuring controllers.

It takes a dynamic list of parameters, matching the specified controllers.

Calling context this is set to the created alias controller.

Source:
See:
Examples
// Create a new controller aliasName as an alias for ['controller1', 'controller2'],
// so instead of e-bind="controller1, controller2" we can use e-bind="aliasName":

app.addAlias('aliasName', ['controller1', 'controller2']);
// Create a new controller-alias, and re-configure it at the same time:

app.addAlias('aliasName', ['controller1', 'controller2'], function(c1, c2) {
    // this = controller 'aliasName' object
    // c1 = controller 'controller1' object
    // c2 = controller 'controller2' object

    this.node.className = 'myClass';

    c1.someMethod(123);
    c2.someMethod('hello!');
});

addController(name, func) → {boolean}

Registers a new application-level controller.

A controller is either a function or ES6 class that implements it, paired with a unique name by which it is represented.

Typical implementation for any reusable controller is to be done inside a module, which registers itself (and all its controllers automatically) with addModule. It is only when you need some application-specific controllers that you would create them on the application level, and then you need to use this method, in order to register them.

If controller with such name already exists, then the method will do the following:

  • throw an error, if the controller is different from the original
  • nothing (and return false), if the controller passed in is the same

TIP: Reusable controllers should always reside inside modules.

And if the purpose of your controller is only to extend and/or configure other controller(s), then method addAlias offers a simpler syntax for adding such controllers.

Parameters:
Name Type Description
name JSName

Controller name. Trailing spaces are ignored.

func function | class

Either a function or ES6 class that implements the controller:

  • a function is called with controller's scope/instance as a single parameter, and as this context, to initialize the controller as required.
  • for an ES6 class, a new instance is created.
Source:
See:
Returns:

Indication of whether the controller was added:

  • true - a new controller has been added
  • false - a controller with the same name and implementation was added previously
Type
boolean
Examples
// ES5 syntax:
//
app.addController('ctrlName', function(ctrl) {
    // this = ctrl

    // Initializing your controller here:
    //
    // - setting up public properties and methods
    // - setting up event handlers, as needed
    // - changing DOM, if needed

    // Creating all event handlers, as needed:

    this.onInit = function() {
        // - can do ctrl.extend(...) here, to extend functionality
        // - can find controllers created through explicit binding
    };

    this.onReady = function() {
        // can find all controllers here, including the ones
        // created implicitly (through extension)
    };

    this.onDestroy = function() {
        // any clean-up, if needed
    };

    // Creating public properties + methods for
    // communication with other controllers:

    this.someProp = 123;

    this.someMethod = function() {
        // do something
    };
});
// ES6 class syntax:
//
class MyController extends EController {
    // If you want to use a constructor in your controller class,
    // you must pass Controller Context parameter into the parent,
    // or else there will be a construction-related error thrown:
    constructor(cc) {
        super(cc); // pass Controller Context into the parent class

        // here you can access and modify DOM
    }

    onInit() {
        // - can do ctrl.extend(...) here, to extend functionality
        // - can find controllers created through explicit binding
    }

    onReady() {
        // can find all controllers here, including the ones
        // created implicitly (through extension)
    }

    onDestroy() {
        // any clean-up, if needed
    }
}

app.addController('ctrlName', MyController);

addModule(name, func) → {boolean}

Adds and initializes a new module, which is effectively an isolated namespace of controllers.

If the module with such name already exists, the method will do nothing, and return false.

Unlike application-level controllers, which register themselves by calling addController, all controllers inside a module are available automatically, just as the module is added.

Each added module is listed within the modules namespace.

Parameters:
Name Type Description
name JSName

Module name. Trailing spaces are ignored.

func function

Module initialization function, to be called with the module's scope as a single parameter, and as this context, to initialize the module as required.

Source:
See:
Returns:

Indication of whether the module was added:

  • true - the module has been successfully added
  • false - ignoring the module, as it was added previously
Type
boolean
Examples
app.addModule('moduleName', function(scope) {
    // this = scope

    // Creating functions-controllers on the scope:

    this.ctrl1 = function() {
        // controller implementation here;
    };

    // Can use sub-spaces of any depth:
    this.effects = {
        fadeIn: function() {
            // implement fadeIn controller here;
        },
        fadeOut: function() {
            // implement fadeOut controller here;
        };
    };
});
// Modules can also use ES6 classes as controllers:

class MyController extends EController {
    onInit() {
        this.node.innerHTML = 'Hello!';
    }
}

app.addModule('moduleName', function(scope) {
    // this = scope

    this.ctrl1 = MyController;
});

addService(name, func) → {boolean}

Adds and initializes a new service, which is simply an isolated namespace that contains generic reusable code, to be shared across components and/or the application.

If the service with such name already exists, the method will do nothing, and return false.

Each added service becomes globally available via the services namespace.

Parameters:
Name Type Description
name JSName

Service name. Trailing spaces are ignored.

func function

Service initialization function, to be called with the service's scope as a single parameter, and as this context, to initialize the service as required.

Source:
See:
Returns:

Indication of whether the service was added:

  • true - the service has been successfully added
  • false - ignoring the service, as it was added previously
Type
boolean
Example
app.addService('serviceName', function(scope) {
    // this = scope

    // Implement the service API on the scope here:

    this.getMessage = function() {
        // implement the method here
    };
});

analyze() → {EStatistics}

Pulls together and returns a snapshot of the current state of the library, as EStatistics object.

This method is to help with debugging your application, and for automatic tests.

Source:
See:
Returns:

Statistics Data.

Type
EStatistics

attach(e, names) → {EController|Array.<EController>}

Manually attaches/binds and initializes controller(s) to one specific DOM element, bypassing the automatic controller binding.

This method is to simplify the binding when elements are available only as DOM objects, and not as HTML.

Most practical use cases for this method are that of an integration process into an application. If however, you decide to call it from inside a controller, please note that while it will work during and after onInit event, it will throw an error, if called during the controller's construction, because it will cause nested binding execution, which this library does not support.

The method's primary use is as an integration tool, and a replacement for automatic binding, not an addition. However, if you try to combine it, note that while attaching to an element previously bound automatically, it will work correctly, as an extension (just like method EController.extend), but automatic binding will not work on elements with manually attached controllers, due to the conflict of controllers initialization in this case.

Similar to method EController.extend, it creates and returns a new controller(s), according to the parameters. And if you specify a controller name that's already bound to the element, that controller is returned instead, to be reused, because only a single controller type can be bound to any given element.

The method sets/updates attribute data-e-bind / e-bind according to the new bindings.

Parameters:
Name Type Description
e external:HTMLElement

Either a new DOM element or a ControlledElement, to bind with the specified controller(s).

names CtrlName | Array.<CtrlName>

Either a single controller name, or an array of names. Trailing spaces are ignored.

Source:
See:
Returns:
  • if you pass in a single controller name, it returns a single created controller.
  • if you pass in an array of names, it returns an array of created controllers.

The returned controller(s) have already finished processing event EController.onReady.

Type
EController | Array.<EController>
Examples
// This is how you can integrate a controller into an existing app,
// without using element-to-controller explicit bindings:

var e = document.getElementById('someId'); // find a DOM element
var c = app.attach(e, 'myController'); // attach a controller to it
c.someMethod(data); // data = parametrization data for the controller
// This example is only to show how ERoot.attach relates to EController.extend,
// but not how it is to be used, as using ERoot.attach like this is pointless.

app.addController('ctrlName', function(ctrl) {
    this.onInit = function() {

        // Specifically in this context, the result of
        // calling the following two lines is identical:

        var a = ctrl.extend(['ctrl1', 'ctrl2']);

        var b = app.attach(ctrl.node, ['ctrl1', 'ctrl2']);
    };
});

bind(processopt)

Searches for all elements in the document not yet bound, and binds them to controllers.

It will search for all elements in the document that contain attribute data-e-bind / e-bind, but without controllers yet, create and initialize controllers, as specified by the attribute, which is expected to contain valid names (comma-separated) of existing controllers.

Normally, a controller creates new controlled elements within its children, and then uses EController.bind method. It is only when you create a new controlled element that's not a child element, then you would use this global binding. For a random-element binding see bindFor.

Note that when integrating your controllers into an application, if you are dealing with DOM objects rather than HTML, then you can alternatively make use of method attach, to inject and initialize controllers for one specific DOM element.

You should try to avoid use of synchronous bindings, if possible. The binding engine implements the logic of minimizing the number of checks against the DOM, but it works/scales best when requests are asynchronous.

Parameters:
Name Type Attributes Default Description
process boolean | function <optional>
false

Determines how to process the binding:

  • any falsy value (default): the binding will be done asynchronously;
  • a function: binding is asynchronous, calling the function when finished;
  • any truthy value, except a function type: forces synchronous binding.
Source:
See:

bindFor(e, processopt)

Searches and initializes new bindings inside the specified element.

Typically, you would trigger local bindings via EController.bind. But when you know the element that contains new bindings, and do not want to create a controller for it, or use the global bind, you can use this method instead.

Parameters:
Name Type Attributes Default Description
e external:HTMLElement

DOM element with new bindings among its children - elements with attribute data-e-bind / e-bind set to valid names (comma-separated) of existing controllers.

process boolean | function <optional>
false

Determines how to process the binding:

  • any falsy value (default): the binding will be done asynchronously;
  • a function: binding is asynchronous, calling the function when finished;
  • any truthy value, except a function type: forces synchronous binding.
Source:
See:
Example
var e = document.getElementById('someId');
e.innerHTML = '<div e-bind="ctrl1, ctrl2"></div>';
app.bindFor(e); // bind child elements to controllers

find(name) → {Array.<EController>}

Searches for all initialized controllers in the entire application, based on the controller name, including the extended controllers.

The search is based solely on the internal map of controllers, without involving DOM, and provides instant results. Because of this, it will always significantly outperform method EController.find, even though the latter searches only among child elements, but it uses DOM.

It will find explicitly created controllers, if called during or after event EController.onInit, and implicitly created controllers (extended via method EController.extend), if called during or after event EController.onReady. And it will find everything, if called during or after global event ERoot.onReady.

Parameters:
Name Type Description
name CtrlName

Controller name to search by. Trailing spaces are ignored.

Source:
See:
Returns:

List of found initialized controllers.

Type
Array.<EController>

findOne(name) → {EController}

Implements a safe-check search for a single initialized controller, in the entire application, based on the controller name.

The method will throw an error, if multiple or no controllers found.

It will find explicitly created controllers, if called during or after event EController.onInit, and implicitly created controllers (extended via method EController.extend), if called during or after event EController.onReady. And it will find everything, if called during or after global event ERoot.onReady.

Parameters:
Name Type Description
name CtrlName

Controller name to search by. Trailing spaces are ignored.

Source:
See:
Returns:

A single controller with the matching name.

Type
EController

getCtrlFunc(name, noErroropt) → {function|class|null}

Resolves a full controller name into the corresponding controller (function/class).

This lets you create controllers on the app level or inside modules that directly alias an existing controller, without extending it (methods EController.extend and ERoot.addAlias).

// Example of declaring a controller inside a module,
// as an alias for a controller from another module:

scope.print = e.getCtrlFunc('printModule.default.showUI');

// e = excellent root object, scope = module's scope object
Parameters:
Name Type Attributes Default Description
name CtrlName

Full controller name. Trailing spaces are ignored.

noError boolean <optional>
false

By default, the method throws an error whenever it fails to resolve the specified name into a valid controller. Passing in noError = true forces it to return null when the module or controller are not found. This however will not suppress errors related to passing in an invalid controller name.

Example of where you might want to use it - provide an alternative controller when the desired one could not be resolved for some reasons, like when inclusion of a certain module into the app is optional. This way you can also check whether the containing module is included or not.

Source:
See:
Returns:

Initialization controller (function/class).

It can return null only when the function fails because the module or controller were not found, and noError was passed in as a truthy value.

Type
function | class | null
Example
// Adding a controller-alias, just to shorten another controller's name:

app.addController('shortName', app.getCtrlFunc('module1.very.long.name'));

reset()

Performs instant hard reset of the entire library state, including the root interface object.

It is only to help with some automatic tests that may require fresh state of the library.

NOTE: If an alternative root name was set with e-root / data-e-root, its value is reset, but the name itself is not removed from the global scope, because the library can pick it up only once, on the initial run.

Source:
See:

Events

onReady

Called only once, after initializing controllers in the app for the first time.

It represents the state of the application when it is ready to find all controllers and communicate with them. This includes controllers created through extension, i.e. all controllers have finished processing onReady event at this point.

Type:
  • function
Source:
See:
Example
app.onReady = function() {
  // All explicit and extended controllers now can be located;

  // Let's find our main app controller, and ask it to do something:
  app.findOne('appCtrl').doSomething();
};