Services

A myriad of services to make your life easier. PushState routing, exception handling, config merging, and less styles manager.

Dynamically manage page state with the havok router.

The router lets you manage the browser location for simple dynamic page loading, without having to use # hacks.

Example

These docs pages use the router. The first page you load will deliver a full html page. Any links you click after that will only load new content. It will not reload the top menu footer, css, or js. Moreover, if you go back to a page you have already visited, it will load very quickly because the havok documentation controller caches previously loaded content. And, all this is done with static pages, no logic on the server at all.

Try it out. Click on 'Getting Started' in the site, menu, and then click 'Services' to get back here.

How it works

Once a router is started, it will listen to every click event that bubbles up to document.body. If the click event is on a link (A tag) then it will be processed by the router.

The router will attempt to match the href attribute of the tag with one of it's configured routes. If a match is found, the controller on the configured route is called. If a match is not found, the click event is allowed to continue bubbling.

Router Configuration

The router is configured through a havok/di/di instance. See the DI docs for general di info.

The router can be configured with an array of routes. The routes will be evaluated in reverse order (fifo). The first matching route will be used. Each route has these properties:

name type description
regex string Used to test if the requested href matches with this route.
controller string The name of a controller instance that can be retrieved through the di container.
defaultMethod string The name of the method to call on the controller if there is no method match.
methods object An array of route to method name pairs.

Example

A router config:

di: {
    'havok/router/router': {
        params: {
            routes: [
                {
                    regex: /index.html/,
                    controller: 'my/index/controller',
                    defaultMethod: 'home'
                },
                {
                    regex: /login/,
                    controller: 'my/login/controller',
                    defaultMethod: 'login',
                    methods: [
                        {'logout': 'logout'}
                    ]
                },
                {
                    regex: /albums/,
                    controller: 'my/albums/controller',
                    defaultMethod: 'list',
                    methods: {
                        'new': 'create',
                        'update': 'update'
                    }
                }
            ]
        }
    }
}

The above router config will call the methods in the following way:

index.html -> myIndexController.home()
user -> myLoginController.user()
user/login -> myLoginController.login()
user/logout -> myLoginController.logout()
albums -> myAlbumController.list()
albums/list -> myAlbumController.list()
albums/new -> myAlbumController.new()
albums/new/newAlbumName -> myAlbumController.list('newAlbumName')
albums/update -> myAlbumController.update()
albums/update/oldAlbumName -> myAlbumController.list('oldAlbumName')

Starting the router

The router can be started like this:

require(['havok/get!havok/router/router'],
function (router){
    router.startup();
});

Alternately, the router can be started with the AMD plugin:

require(['havok/router/started!'],
function (){
    //do something
});

Note: if you are using the router, it is recommended you start it up when the page first loads.

Base Url

Router routes are evaluated relative to the Base Url. By default the base url is set when you first start the router to whatever the current page address is. However, this will not work if the first page you load on your site is not a the top level of the site's directory structure. In such cases, you will need to specify the Base Url in config:

di: {
    'havok/router/router': {
        params: {
            baseUrl: 'my/site',
            ...
        }
    }
}

You can retrieve the baseUrl in code using the baseUrl AMD plugin:

require(['havok/router/baseUrl!'],
function (baseUrl){
    //do something
});

Arguments

Arguments can be passed to a controller method through the route. Routes take the following form:

<controllerRegexMatch>/<method>/<arg1>/<arg2>/<arg3>

For example, this config and controller could be used to add numbers together:

The config:

di: {
    'havok/router/router': {
        params: {
            routes: [
                {
                    regex: /math/,
                    controller: 'my/math',
                    methods: {
                        'add': 'add'
                    }
                }
            ]
        }
    }
}

The controller:

define(['dojo/_base/declare'],
function(declare){
    return declare([], {
        add: function(a, b){
            console.debug(a + b);
        }
    )
})

The markup:

<a href="math/add/1/6">Add 1 and 6</a>

If the link were clicked, the number 7 would be printed in the console.

Exit methods

Exit methods can be defined for controllers. They are methods which will be called when a route is being left. Eg:

di: {
    'havok/router/router': {
        params: {
            routes: [
                {
                    regex: /math/,
                    controller: 'my/math',
                    defaultMethod: {
                        enter: 'add',
                        exit: 'cleanup'
                    }
                    methods: {
                        'subtract': {
                            enter: 'subtract',
                            exit: 'anotherMethod'
                        }
                    }
                }
            ]
        }
    }
}

Scripting a page change

The router can be triggered in code using the go function:

require(['havok/router/started!'],
function (router){
    router.go('my/route');
});

Forward and Back

The router can go forward and back in history by passing an integer to go. Eg:

require(['havok/router/started!'],
function (router){
    router.go(-1); //go back one
    router.go(-3); //go back three
    router.go(1); //go forward one
});

Create a merged dojo config from many different modules.

When you require dojo/_base/config you get back the dojo configuration object. Modules requiring configuration often use their own extensions to this object. Some havok modules, notably havok/di and havok/less use this pattern.

The havok/config modules allow you to build up the config object from multiple sources, and allows one config to be overridden by another - particularly useful for development or testing environments.

Use the merge key to specify the config modules to be merged. Eg:

dojoConfig = {
    isDebug: true,
    popup: true,
    async: true,
    merge: [
        'MyNamespace/Config1',
        'MyNamespace/Config2'
    ]
}

Merging

The config modules can be loaded and merged using the merge function of havok/config/manager.

The merge() function will return a Deferred object which will resolve when the config merge is complete.

require(['havok/config/manager'], function(configManager){
    configManager.merge().then(function(){
        //config merge complete
    })
}

ready!

Alternatively, config modules can be merged using the havok/config/ready! AMD plugin. The plugin will not return until configs are merged.

require(['havok/config/ready!'], function(){
    //Do something
}

Dojo builds

If you use the havok build tool, it will merge all configs during the build, so that config merging does not happen in production.

Container for managing data stores

The store manager allows you to configure the data stores you want to use with di, and then retrieve whole stores and individual records simply.

Config

To configure the store manager use di, and populate the stores object. Eg:

di: {
    'havok/store/stores': {
        gets: {
            store1: 'my/store/one',
            store2: 'my/store/two'
        },
        proxies: {
            store3: {
                base: 'my/store/three',
                proxyMethods: [
                    'get',
                    'query'
                ]
            }
        }
    }
}

Retrieve a store

To get a store, use getStore. Note that a Deferred may be returned if the individual store is proxied.

require(['havok/store/manager'], function(storeManager){
    var store1 = storeManager.getStore('store1');
}

Retrieve a record

To get a single record from a store, use get and pass a reference with the store name and record id. Eg:

require(['havok/store/manager'], function(storeManager){
    var record = storeManager.get('store1/id1');
}

Support

The store manager is supported by many havok that can use data stores.

Throw, handle, view, and log js exceptions.

The exception handler will catch and process all js exceptions. It allows you to define exception severity and render exceptions to the console, a server, or to the user interface.

Using

To turn on the exception handler use the havok/exception/started!. It is suggested that this be done just after havok has first loaded. Eg:

require(['havok/exception/started!'], function(){
    //do something
})

How it works

The exception handler listens to window.onerror. Any exceptions that are not handled by application code are caught and handled by the exception handler. Eg:

<button class="btn" onclick="console.debug(obj());">Not Defined</button>

Note: Open your console to see the exception rendered there.

Severity

Each exception has a severity. The possible severity codes are:

NOTICE   : 1,
WARNING  : 2,
ERROR    : 3

The higher the number, the more serious the exception.

By default, all exceptions have a severity of ERROR: 3.

Application Exception

The severity of an error can be cusomised by throwing an instance of havok/exception/Application.

<script>
    require(['dojo/on', 'dojo/dom', 'havok/exception/severity', 'havok/exception/Application'],
         function(on, dom, severity, Application){
              on(dom.byId('exception2'), 'click', function(){
                  throw new Application('Example Exception', {severity: severity.WARNING});
              })
         }
     )
</script>
<button class="btn" id="exception2">Throw warning</button>

Custom Exceptions

To create your own custom exceptions, extend havok/exception/Base. Eg:

define([
    'dojo/_base/lang',
    'dojo/errors/create',
    '../../exception/Base'
],
function(
    lang,
    create,
    BaseException
){
    return create(
        "MyCustomException",
        function(message, options){
            lang.mixin(this, options);
        },
        BaseException
    )
});

Renderers

The handler is configured through the di container with an array of renderers. Each renderer must have a minSeverity property and a render function. The handler checks the minSeverity property, and if the exception is more severe, the render function is called.

Console

Renders and exception the the console, including extra info like stack trace.

By default the console renderer is registered with the handler and set to render all exceptions.

UI

Renders an exception as a modal for the the user to aknowledge. Eg:

Store

Renders an exception to the configured store.

Click 'store render' to create an exception that is rendered to a store. Then click 'show store contents' to see the contents of the store.

Store items:


              

Tip: use a json rest store to send exceptions back to a server for logging.

Bundle less with widgets for better OOP design

Use the havok/less!. AMD plugin to define and load the less files you need.

define([
    'havok/less!my/less/file.less'
],
function (){
    //my module code
});

Config

The less plugin takes an array of less files. The files are loaded and parsed client side, and the resulting css is inserted into the page. A rank can be used to control the order they are added to the page.

Less files can be added to the plugin in two ways. Firstly through dojoConfig. Eg:

dojoConfig = {
    ...
    less: {
        "my/less/mixins.less": {defs: true},
        "my/less/file.less": {rank: 2},
        "another/less/file.less": {rank: 2}
    }
}

Secondly, less files can be added through the plugin. Less loaded this way will get a rank of 2 by default. eg:

define([
    'havok/less!my/less/file.less
],
function (){
    //my module code
});

To set the defs or rank when using the plugin, pass a json object:

define([
    'havok/less!my/less/file.less!{rank: 4}
],
function (){
    //my module code
});

Defs and Rank

The defs and rank control the order of less compilation an injection into the page. Any less marked with defs: true is considered to contain less definitions. Use this for less files that define variables and mixins.

Several style tags will be appended to the body of the page where compiled less will be injected. Less marked with rank: 0 will be added to the first style tag. Less marked with rank: 1 will be added to the second style tag, and so on.

Builds

Dynamically requiring less and compiling it client side is great for development - no intermediate steps, just edit your code and reload the browser. However, it is slow and inefficent for production. It is strongly recommended that you use the havok build tools to compile all less to css before deployment.

If you use the build tools, the following files will be created for each build layer:

  • myLayer.less
  • myLayer.uncompressed.css
  • myLayer.css

When deploying an a layer, link the myLayer.css in your document body to completely bypass client side less compilation and greatly speed up load times.

Small helper functions

array

havok/array extends dojo/_base/array to add one new function: substract(a, b). Array subtract will remove any elemnts in both a and b from a. Eg:

require(['havok/array', function(array){
    var removeFrom = [1, 2, 2, 3, 4, 5];
    var removeValues = [2, 3];

    var result = array.subtract(removeFrom, removeValues);
    //result is [1, 4, 5]
})

is

havok/is constains comparison functions that will return a boolean value.

function description
isInt(value) Is the value an integer?
isFloat(value) Is the value a floating point number?
isDeferred(value) Is the value an instance of dojo/Deferred
isStatic(value) Does the value contain any functions at all?

lang

havok/lang extends dojo/_base/lang with one new function. mixinDeep will mix objects together deep into their object tree.

require(['havok/lang', function(lang){
    var dest = {
        item: {
            a: 1,
            b: {bb: 1}
        }
    };
    var source = {
        item: {
            a: 0,
            b: {dd: 5},
            c: 3
        }
    }

    var result = lang.mixinDeep(dest, source);

    //result = {
    //    item: {
    //        a: 0,
    //        b: {bb: 1, dd:5},
    //        c: 3
    //    }
    //}
})

string

havok/string extends dojo/string with one new function. ucFirst will make the first character in a string uppercase.

require(['havok/string', function(string){

    var result = string.ucFirst('abc');

    //result = 'Abc';
})
Loading more havok...