Documentation

Setup

The setup of Conditioner is a two step process.

Conditioner needs an AMD loader to function. RequireJS is a great choice so for these docs we'll stick with that. You can get it at http://requirejs.org, if you haven't played around with RequireJS before, follow the Getting Started guide for a quick meet n' greet.

Now you've got a feel for RequireJS it is time to setup Conditioner.

First it needs to know where to find the Conditioner framework. You'll need to add a map entry to the RequireJS configuration object pointing to the location of Conditioner. In the example below the file 'conditioner.js' is located in the 'lib' folder within the generic 'js' folder.

requirejs.config({
    map:{
        '*':{
            conditioner:'lib/conditioner'
        }
    }
});

Now that's been setup, you can request a conditioner reference and have it start loading modules by calling the init method.

require(['conditioner'],function(conditioner) {

    conditioner.init();

});

Since we've not yet configured our modules it will not load anything for now.

If you need to support IE8 or IE9 you might also need a set of polyfills, you can find the them in the shim folder of the GitHub repository.

Module format

Conditioner does not force you to write your AMD modules in a certain way. Only if you want to unload your modules or pass custom options does it expect certain properties and methods to be there.

The following two basic module examples both contain an unload method and an options property. If your module will always be active you can omit the unload method, also, if their are no options to be set, leave out the options property.

An outline of a very basic AMD module returning a class:

define(function(){

    // constructor
    var exports = function(element,options) {

    };

    // options (optional)
    exports.options = {
        'foo':'bar'
    };

    // unload (optional)
    exports.prototype.unload = function() {

    };

    return exports;

});

Outline of an AMD module returning an static object:

define(function(){

    return {

        // load method, required when no constructor available
        load:function(element,options) {

        },

        // options (optional)
        options:{
            'foo':'bar'
        },

        // unload (optional)
        unload:function() {

        }
    };

});

Now you know how to setup your modules, let's see how you can link them to the DOM and have Conditioner load them.

Configuring modules

Linking your AMD Modules to the DOM is done through data attributes. The following sections guide you through the configuration of a typical module.

Linking a module to the DOM

To have a module loaded at a specific location in the DOM you use the data-module attribute. The value of the data-module attribute points to the location of the module.

<a data-module="ui/Map"> ... </a>

You can also use a shortcut name, should the module path change you would only have to change it on one location.

conditioner.init({
    'modules':{
        'ui/Map':'IMap'
    }
});

The data-module attribute would now look like this:

<a data-module="IMap"> ... </a>

When the module is activated a reference to the node is passed as the first argument to the constructor or, in case of a static object, the load method.

define(function(){

    var exports = function(element,options) {

        // the element parameter contains a reference
        // to the '<a data-module="IMap"> ... </a>' node

    };

    return exports;

});

Setting module conditions

The data-conditions attribute allows you to control the conditions under which a module is loaded. This is actually what Conditioner is all about.

The example below tells Conditioner to only load the module if the media query (min-width:30em) is matched.

<a data-module="ui/Map" data-conditions="media:{(min-width:30em)}"> ... </a>

Conditions consist of monitors (media) with related tests (min-width:30em) and are formatted like this: <monitor_name>:{<test_value>}.

You can prepend a test with the was operator to have it remember it's state. For instance, the test element:{was visible} will not be invalidated once the element becomes invisible.

To make conditions more powerful it is possible to combine multiple monitors together forming complex conditions.

Link multiple monitors using the and or or operators:

foo:{bar} or foo:{bar} and foo:{bar}

Group monitors using brackets to override operator precedence:

foo:{bar} or (foo:{bar} and foo:{bar})

Set the not operator to negate a monitor or monitors group state:

not (foo:{bar} or foo:{bar}) and not foo:{bar}

Default Monitors

The following monitors are currenlty available. For those people who want to monitor more variables it is possible to write your own monitors using a special API.

Element
  • Visible

    Measures if the element is currently in scrolled into view.

    element:{visible}
  • Min-width

    The minimum required element width in pixels.

    element:{min-width:500}
  • Max-width

    The maximum required element width in pixels.

    element:{max-width:500}
  • Min-height

    The minimum required element height in pixels.

    element:{min-height:500}
  • Max-height

    The maximum required element height in pixels.

    element:{max-height:500}
Media
  • Query

    The media query to test, don't forget the parenthesis around the queries.

    media:{(min-width:500px)}
  • Supported

    Tests if media queries are supported.

    media:{supported}
Window
  • Min-width

    The minimum required window width in pixels.

    window:{min-width:500}
  • Max-width

    The maximum required window width in pixels.

    window:{max-width:500}
  • Min-height

    The minimum required window height in pixels.

    window:{min-height:500}
  • Max-height

    The maximum required window height in pixels.

    window:{max-height:500}
Pointer
  • Near

    Measures if the pointer moves within a certain distance from the element.

    pointer:{near:200}
  • Fine

    Measures if the user is using a fine pointer device.

    pointer:{fine}
Connection
  • Any

    Measures if the user has any form of connectivity.

    connection:{any}

Passing options to a module

The data-options attribute allows you to set specific options to be passed to the Module when it's loaded.

There's two formats to choose from. A basic JSON string or a more readable format, the later is parsed manually so might be a bit slower than the JSON string depending on the amount of options being overriden.

<a data-module="ui/Map"
   data-options="map.zoom:10, map.type:terrain"> ... </a>
<a data-module="ui/Map"
   data-options='{"map":{"zoom":10, "type":"terrain"}'> ... </a>

You can also set page level options for multiple modules of a certain type on conditioner init.

conditioner.init({
    'modules':{
        'ui/Map':{
            'options':{
                'zoom':5
            }
        }
    }
});

Conditioner will merge module level options with page level options and in turn with node level options and pass the resulting options to the activated module.

In the (not too exciting) example above the resulting option object would be {"zoom":10}, since node level options take precedence over page level options zoom level 10 overwrites zoom level 5.

define(function(){

    var exports = function(element,options) {

        // the options parameter now contains
        // the following object {zoom:10}

    };

    exports.options = {
        zoom:15
    };

    return exports;

});

Controlling module support

conditioner.init({
    'modules':{
        'ui/Map':{
            'enabled':'addEventListener' in window
        }
    }
});

Altering load priorities

The data-priority attributes allows you to control the order in which a node is handled. Positive numbers give a node priority over other nodes, a negative number moves it to the back of the initialisation queue.

Suppose you have a certain module you want to start loading earlier but is located lower in the DOM tree you can now move it up the list.

<a data-module="ui/Map" data-priority="2"> ... </a>

JavaScript API

Conditioner consists of a group of publicly accessible JavaScript Classes. These Classes allow you to safely access modules once they've been parsed. The starting point to load modules is always the conditioner object of which there is only one available.

Conditioner

Call [init](#conditioner-init) on the conditioner object to start loading the referenced modules in the HTML document. Once this is done the conditioner will return the nodes it found as an Array and will initialize them automatically once they are ready.

Each node is wrapped in a NodeController which contains one or more ModuleControllers.

Event Description

init([options]) → Array

Parameter Type Description
options Object

Options to override.

Call this method to start parsing the document for modules. Conditioner will initialize all found modules and return an Array containing the newly found nodes.

require(['conditioner'],function(conditioner){

    conditioner.init();

});

setOptions(options)

Parameter Type Description
options Object

Options to override.

Allows defining page level Module options, shortcuts to modules, and overrides for conditioners inner workings.

Passing the default options object.

require(['conditioner'],function(conditioner){

    conditioner.setOptions({

        // Page level module options
        modules:{},

        // Path overrides
        paths:{
            monitors:'./monitors/'
        },

        // Attribute overrides
        attr:{
            options:'data-options',
            module:'data-module',
            conditions:'data-conditions',
            priority:'data-priority',
            initialized:'data-initialized',
            processed:'data-processed',
            loading:'data-loading'
        },

        // AMD loader overrides
        loader:{
            require:function(paths,callback){
                require(paths,callback)
            },
            config:function(path,options){
                var config = {};
                config[path] = options;
                requirejs.config({
                    config:config
                });
            },
            toUrl:function(path){
                return requirejs.toUrl(path)
            }
        }
    });

});

parse(context) → Array

Parameter Type Description
context Element

Context to find modules in.

Finds and loads all Modules defined on child elements of the supplied context. Returns an Array of found Nodes.

load(element, controllers) → NodeController | null

Parameter Type Description
element Element

Element to bind the controllers to.

controllers Array,ModuleController

ModuleController configurations.

Creates a NodeController based on the passed element and set of controllers.

require(['conditioner'],function(conditioner){

    // find a suitable element
    var foo = document.getElementById('foo');

    // load Clock module to foo element
    conditioner.load(foo,[
        {
            path: 'ui/Clock',
            conditions: 'media:{(min-width:30em)}',
            options: {
                time:false
            }
        }
    ]);

});

sync(arguments) → SyncedControllerGroup

Parameter Type Description
arguments ModuleController,NodeController

List of ModuleControllers or NodeControllers to synchronize.

Wraps the supplied controllers in a SyncedControllerGroup which will fire a load event when all of the supplied modules have loaded.

require(['conditioner','Observer'],function(conditioner,Observer){

    // Find period element on the page
    var periodElement = document.querySelector('.peroid');

    // Initialize all datepicker modules
    // within the period element
    var datePickerNodes = conditioner.parse(periodElement);

    // Synchronize load events, we only want to work
    // with these modules if they are all loaded
    var syncGroup = conditioner.sync(datePickerNodes);

    // Wait for load event to fire
    Observer.subscribe(syncGroup,'load',function(nodes){

        // All modules now loaded

    });

    // Also listen for unload event
    Observer.subscribe(syncGroup,'unload',function(nodes){

        // One of the modules has unloaded

    });

});

getNode(arguments) → NodeController | null

Parameter Type Description
arguments ...

See description.

Returns the first NodeController matching the given selector within the passed context

  • getNode(element) return the NodeController bound to this element
  • getNode(selector) return the first NodeController found with given selector
  • getNode(selector,context) return the first NodeController found with selector within given context

getNodes([selector], [context]) → Array

Parameter Type Description
selector String

Selector to match the nodes to.

context Element

Context to search in.

Returns all NodeControllers matching the given selector with the passed context

destroy(arguments) → Boolean

Parameter Type Description
arguments NodeController,String,Array

Destroy a single node controller, matched elements or an Array of NodeControllers.

Destroy matched NodeControllers based on the supplied parameters.

getModule(arguments) → ModuleController | null

Parameter Type Description
arguments ...

See description.

Returns the first ModuleController matching the supplied query.

  • getModule(element) get module on the given element
  • getModule(element, path) get module with path on the given element
  • getModule(path) get first module with given path
  • getModule(path, filter) get first module with path in document scope
  • getModule(path, context) get module with path, search within conetxt subtree
  • getModule(path, filter, context) get module with path, search within matched elements in context

getModules(arguments) → Array | null

Parameter Type Description
arguments ...

See description.

Returns all ModuleControllers matching the given path within the supplied context.

  • getModules(element) get modules on the given element
  • getModules(element, path) get modules with path on the given element
  • getModules(path) get modules with given path
  • getModules(path, filter) get modules with path in document scope
  • getModules(path, context) get modules with path, search within element subtree
  • getModules(path, filter, context) get modules with path, search within matched elements in context

matchesCondition(condition, [element]) → Promise

Parameter Type Description
condition String

Expression to test.

element Element

Element to run the test on.

Test an expression, only returns once via promise with a true or false state.

require(['conditioner'],function(conditioner){

    // Test if supplied condition is valid
    conditioner.matchesCondition('window:{min-width:500}').then(function(state){

        // State equals true if window has a
        // minimum width of 500 pixels.

    });

});

addConditionMonitor(condition, [element], [callback]) → Number

Parameter Type Description
condition String

Expression to test.

element Element,Function

Optional element to run the test on.

callback Function

Callback method.

Monitor an expression, bind a callback method to be executed when something changes.

require(['conditioner'],function(conditioner){

    // Test if supplied condition is valid
    conditioner.addConditionMonitor('window:{min-width:500}', function(state) {

        // State equals true if window a
        // has minimum width of 500 pixels.

        // If the window is resized this method
        // is called with the new state.

    });

});

removeConditionMonitor(id)

Parameter Type Description
id Number

Condition monitor id to remove.

Stop monitoring an expression.

require(['conditioner'],function(conditioner){

    // Remove condition monitor with supplied id
    conditioner.removeConditionMonitor(id);

});

ModuleController

The ModuleController loads and unloads the contained Module based on the conditions received. It propagates events from the contained Module so you can safely subscribe to them.

Event Description

getModulePath() → String

Parameter Type Description

Returns the module path

isModuleAvailable() → Boolean

Parameter Type Description

Returns true if the module is currently waiting for load

isModuleActive() → Boolean

Parameter Type Description

Returns true if module is currently active and loaded

wrapsModuleWithPath(path)

Parameter Type Description
path String

Path of module to test for.

Checks if it wraps a module with the supplied path

execute(method, [params])

Parameter Type Description
method String

Method name.

params Array

Array containing the method parameters.

Executes a methods on the wrapped module.

NodeController

For each element found having a data-module attribute an object of type NodeController is made. The node object can then be queried for the ModuleControllers it contains.

Event Description

getElement() → Element

Parameter Type Description

Returns the element linked to this node

matchesSelector(selector, [context])

Parameter Type Description
selector String

CSS selector to match element to.

context Element

Context to search in.

Tests if the element contained in the NodeController object matches the supplied CSS selector.

areAllModulesActive() → Boolean

Parameter Type Description

Returns true if all ModuleControllers are active

getActiveModules() → Array

Parameter Type Description

Returns an array containing all active ModuleControllers

getModule([path]) → ModuleController | null

Parameter Type Description
path String

The module id to search for.

Returns the first ModuleController matching the given path

getModules([path]) → Array

Parameter Type Description
path String

The module id to search for.

Returns an Array of ModuleControllers matching the given path

execute(method, [params]) → Array

Parameter Type Description
method String

Method name.

params Array

Array containing the method parameters.

Safely tries to executes a method on the currently active Module. Always returns an object containing a status code and a response data property.

Observer

Event Description

subscribe(obj, type, fn)

Parameter Type Description
obj Object

Object to subscribe to.

type String

Event type to listen for.

fn Function

Function to call when event published.

Subscribe to an event

Observer.subscribe(foo,'load',function bar(){

    // bar function is called when the foo object
    // publishes the load event

});

unsubscribe(obj, type, fn)

Parameter Type Description
obj Object

Object to unsubscribe from.

type String

Event type to match.

fn Function

Function to match.

Unsubscribe from further notifications

// Remove the bar function from foo object.
Observer.unsubscribe(foo,'load',bar);

publishAsync(obj, type, [data])

Parameter Type Description
obj Object

Object to fire the event on.

type String

Event type to fire.

data Object

Data carrier.

Publishes an async event. This means other waiting (synchronous) code is executed first before the event is published.

// Publishes a load event on the foo object. But does it async.
Observer.publishAsync(foo,'load');

publish(obj, type, [data])

Parameter Type Description
obj Object

Object to fire the event on.

type String

Event type to fire.

data Object

Data carrier.

Publish an event

// Publishes a load event on the foo object.
Observer.publish(foo,'load');

inform(informant, receiver)

Parameter Type Description
informant Object

Object to set as origin. Events from this object will also be published on receiver.

receiver Object

Object to set as target.

Setup propagation target for events so they can bubble up the object tree.

// When foo publishes its load event baz will republish it.
Observer.inform(foo,baz);

SyncedControllerGroup

Event Description
load

Fires a load event when all controllers have indicated they have loaded and we have not loaded yet

unload

Fires an unload event once we are in loaded state and one of the controllers unloads

destroy()

Parameter Type Description

Destroy sync group, stops listening and cleans up

areAllModulesActive() → Boolean

Parameter Type Description

Returns true if all modules have loaded

Custom Monitors

A really powerful feature of Conditioner is the fact that you can add your own monitors.

These monitors can then be used and combined in the conditioner expression engine.

Each monitor should be contained in it's own module, if monitors are not minimized and added to the main package they will be loaded on the fly.

{
    /**
     * Which events on which object trigger
     * the monitor to run it's tests.
     */
    trigger: {
        'resize':window,
        'scroll':window
    },

    /**
     * Possible values this monitor can test for.
     * foo relates to the name of the test.
     * monitor:{foo:bar}
     *
     * The data object contains the expected value and the element.
     * {element:<node>,expected:<value>}
     */
    test:{
        foo:function(data) {
            return data.expected === 'bar';
        }
    }
}

Optional methods and overrides.

Data

Properties to store for this monitor. If you want a monitor instance to remember something, you can add it to the data object. The element monitor contains some example code for implementing additional data properties.

data:{
    foo:'bar'
}
Trigger

Trigger can also be a function, useful for when you want to bind more exotic events.

When you're done with binding events or measuring things call the bubble method. For implementation examples see the media or the pointer monitor.

trigger = function(bubble){

    // do your thing here

    bubble();

};
Parse

Conditioner automatically parses the parameters supplied to a monitor (these are the values between the curly braces). It splits them on the comma and then splits the resulting blocks on semicolon.

If your test parameters are formatted differently, for instance using an underscore like so monitor:{foo_bar}, you can supply a custom parser.

The parse method should return an array of test objects. Each object in the form of an object {test:'name',value:'expected'}. An example of a custom parser can be found in the media monitor.

parse:function(value){

    var parts = value.split('_');

    return {
        test:parts[0],
        expected:parts[1]
    }
}
Unload

If an unload method is present, conditioner will make a single monitor for each node. Use the unload method to clean up after a node is destroyed. View the media monitor for an example implementation.

Conditioner.js