Router
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 });
Config Merger
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.
Store Manager
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.
Exception Handler
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.
Less
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.
Utils
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'; })