conkitty-route v0.12.0
conkitty-route 
Build a single page application using routing tree.
Introduction
We have some URL in browser address string and we want to render the corresponding DOM.
Reality makes things a bit harder sometimes. Somewhere in between knowing URL and rendering the DOM, we need to fetch some data. And we need to change the DOM and fetch a new data when the URI is changed or the HTML form is submitted.
conkitty-route gives a simple way to create the single page application.
Simple example
$CR
.add('/', {title: 'Welcome', render: 'WelcomeTemplate'})
.add('/about', {title: 'About', render: 'AboutTemplate'})
.add(null, {title: 'Not Found', render: 'NotFoundTemplate'})
.run();In this example we render the template named WelcomeTemplate for / URI and
the template named AboutTemplate for /about URI, setting the page title to
Welcome and About respectively. For other URIs we render the template
named NotFoundTemplate. By default,
Conkitty templates are used, but you can
customize the template caller and use conkitty-route with any template engine
you want.
More complex example
// Pending.About History API
conkitty-route utilizes History API of the modern browsers. For the most of
cases History API will be used automatically. conkitty-route adds a click
handler for the anchor tags and uses history.pushState() to actually change
browsers location. When the location is changed, only a corresponding part of
the page is rerendered. Of course, you can change the location manually, using
$CR.set() method.
URI patterns and parameters
Simple match
Pattern /path/to/something?arg1=value1&arg2=value2#somehash matches
any URI with a pathname equals to /path/to/something, any query string having
arg1 and arg2 arguments with exact value1 and value2 values,
any number of other query string arguments and a hash equals to somehash.
Pattern /path/to/something matches any URI with a pathname equals to
/path/to/something, any query string and any hash.
Capture parameters
/:path/:to/:something?arg1=:value1&arg2=:value2#:hash matches any URI
with three components in pathname, any query string with arg1 and arg2
arguments with any values and any hash. Matcher captures URI components by
corresponding names.
For example, the pattern above will match the following URI:
/hello/beautiful/world?arg1=it&arg2=is&arg3=pretty&arg1=amazing#indeed
and captured parameters will be:
{
path: 'hello',
to: 'beautiful',
something: 'world',
arg1: ['it', 'amazing'],
arg2: 'is'
}Another example. Pattern /test/:p/pattern will match the following URI (with
or without query string and hash): /test/this/pattern?some=arg#yo, with one
captured parameter:
{
p: 'this'
}Optional parameters
It is possible to specify an optional parameters.
Pattern /some/:?optional/params?arg1=:?a#:?h matches
/some/uri/params?arg1=val1#haha and /some/params. In the first case
parameters will be:
{
optional: 'uri',
a: 'val1',
h: 'haha'
}The last case will not have captured parameters.
Parameters constraints
It is possible to add constraints and default values to a parameter, as well as
transform parameter value. Frame params property aims to do that.
Frame
Frame summary
The Frame is the key element of the routing tree. The routing tree consists of Frames (unlimitedly nested). The Frame is a pair of a URI pattern and a settings object.
For example:
$CR.add('/frame1', {
render: 'template1',
frames: {
'/frame2': {
render: 'template2',
frames: {
'/frame3': {
render: 'template3'
},
'/frame4': {
render: 'template4'
}
}
}
}
});Here we have four nested Frames.
Patterns concatenation
URI patterns of nested Frames are concatenated. The example above will work
for /frame1, /frame1/frame2, /frame1/frame2/frame3 and
/frame1/frame2/frame4 in the browser address string.
Frame settings
Frame settings object example:
{
id: 'page',
data: '/api/data',
render: 'page-template'
}Frame settings is an object with the following keys. All of them are optional.
id
String
An optional user-defined unique Frame identifier. With this identifier you might obtain the Frame runtime object using $CR.get() method.
Frame runtime objects provide useful API.
Example:
$CR
.add('/:param1/test/:param2', {
id: 'page',
render: 'test-template'
})
.run();
alert($CR.get('page').makeURI({param1: 'val1', param2: 'val2'})); // /val1/test/val2title
StringFunction
The document title when the Frame is active. If the function is provided, it
will be called every time this Frame becomes active and should return the title.
The function receives a parameters object as the first argument, this will
point to this Frame runtime object.
Example:
$CR
.add('/?param=:param', {
id: 'test-id',
title: function(params) { return 'Test — ' + params.param + ' (' + this.id + ')'; },
render: 'test-template'
})
.run();
$CR.set('/?param=Hello'); // The title is `Test — Hello (test-id)` now.params
Object
When you want to add some constraints to a URI parameters or transform a URI
parameters values, use params setting. params setting value should be an
object, where the key is a parameter name and the value is one of the
following:
String, should be strictly equal to this string.RegExp, should match thisRegExp.Array, should be strictly equal to one of this array items.Function, the function will receive a value matched by a URI pattern and should return a processed value (orundefinedin case the value doesn't match).
Example:
$CR
.add('/?p1=:param1&p2=:param2&p3=:param3&p4=:param4', {
params: {
param1: /^(?:val1|val2)$/,
param2: ['val3', 'val4', 'val5'],
param3: 'val6',
param4: function(val) { return val + val; }
},
render: 'template1'
})
.run();
$CR.set('/?p1=val2&p2=val4&p3=val6&p4=piu');
// A parameters object for the `template1` will look like:
// {param1: 'val2', param2: 'val4', param3: 'val6', param4: 'piupiu'}.data
StringFunction$CR.$.data()$CR.$.static()Array of any of the previous
Data fetching is an essential part of any application. With this setting, you can tell what data you need to load for this Frame.
Let's check out the data setting value type meanings:
String
A reversed URI pattern for an AJAX request. A reversed URI pattern means that you use the same parametrized patterns which are used for the URI matching, but parameter references will be substituted with an actual values. Plus you can refer to a parent Frames parameters (parameters object is inherited from the parent Frame parameters object via prototypical inheritance).
For example:
$CR
.add('/:param1', {
render: 'template1',
frames: {
'/:param2?test=:val': {
data: '/api/:val/get-data?type=:param1&filter=:param2',
render: 'template2'
}
}
})
.run();
$CR.set('/hello/world?test=beautiful');In this example, when we go to /hello/world?test=beautiful, after template1
is rendered, before rendering template2, an AJAX request to
/api/beautiful/get-data?type=hello&filter=world will be performed.
Function
When you pass a function as the data setting value, this function will be
called when the Frame becomes active. It will receive a parameters object as
the first argument, this will point to the Frame runtime object.
The function should return a Promise or an actual data.
$CR.$.data()
It is possible to customize pretty much everything about the data fetching
process. Use $CR.$.data() helper to do that.
$CR.$.data() helper accepts a settings object, a full version of which looks
like:
{
uri: String | Function,
method: String | Function,
override: Function,
parse: Function,
transform: Function,
eq: Function
}Every property (except for uri) is optional.
When uri is a string, it is treated like a reversed URI pattern.
When uri is a function, it will be called with a parameters object as the
first argument, this will point to the Frame runtime object. The function
should return an URI for the request.
When override function is defined, it will be called with a parameters
object as the first argument, this will point to the Frame runtime object.
When no request to uri is needed, the function should return a non-undefined
value with the resulting data.
When parse function is defined, it will be called to parse a raw
XMLHttpRequest response. When parse is not defined, JSON.parse() is used.
parse function receives responseText as the first argument and
XMLHttpRequest object itself as the second argument, this will point to
the Frame runtime object.
When transform function is defined, it will be called to transform parsed
data into something else. The function will receive the parsed data as the first
argument, XMLHttRequest object as the second argument, this will point to
the Frame runtime object. The function should return the transformed data.
When eq function is defined, it will be used instead of the default equality
check function for the automatic refresh functionality. The function
will receive the new data as the first argument and the previous data as the
second argument, this will point to the Frame runtime object.
$CR.$.static()
When you want to attach some static data to the Frame, $CR.$.static helper
should be used.
...
data: $CR.$.static([{some: 'static'}, 'data']),
...Array of any of the previous
When you have several data sources, you can combine them all into an array. The Frame processing will continue after all these sources are fetched.
parent
StringFunctionNode
The parent setting represents the default parent DOM node for the render
setting of this Frame and its child Frames.
When the string is passed, it is used as CSS selector for find actual node.
When the function is passed, it will be called, this will point to this
Frame runtime object. The function should return DOM node.
When the Frame itself and all its parent Frames have no parent setting,
document.body is used.
render
Render objectStringFunction$CR.$.tpl()$CR.$.uri()Array of any of the previous
The render setting is one of the trickiest and powerful parts of the Frame.
There are five render stages:
beforestage happens when the Frame has become active.successstage happens when the data has been fetched or right afterbeforestage when the Frame has no data.errorstage happens when an error has occurred during the data-fetching.afterstage happens right aftersuccessorerrorstage.exceptstage happens when an exception has occurred during the stages above.
The DOM rendered on each stage replaces the DOM rendered on the previous stage (unless you specifically tell not to replace it).
Render object
Render object is an object with stage names as the keys and all other options
(String, Function, $CR.$.tpl(), $CR.$.uri(), Array of the previous)
as values.
When you use String, Function, $CR.$.tpl(), $CR.$.uri() or
Array directly as the render setting value, success stage is assumed.
Here is what the full version of Render object looks like:
{
before: AOO, // AOO — All other options.
'-before': AOO,
'+before': AOO,
success: AOO,
'-success': AOO,
'+success': AOO,
error: AOO,
'-error': AOO,
'+error': AOO,
after: AOO,
'-after': AOO,
'+after': AOO,
except: AOO,
'-except': AOO,
'+except': AOO
}- before the stage name means that this Frame wasn't active in previous
document location. + means that this Frame was active. Here is the example
to understand this:
$CR
.add('/:param', {
data: '/api/data',
render: {
'-before': 'spinner-template',
'+before': function() { document.body.style.opacity = 0.5; },
success: 'page-template',
error: 'error-template',
'+after': function() { document.body.style.opacity = 1; }
}
})
.run();
$CR.set('/hello');
// Wait for the data to load.
$CR.set('/world');In this example, after $CR.set('/hello'); the Frame becomes active for the
first time, there is no DOM for this frame yet. In this case -before handler
will work.
After $CR.set('/world'); the Frame remains active, but we have the previous
DOM already. If we will render the spinner, it will replace the previous DOM
and will cause the page to blink, so we just change the opacity instead.
String
The string will be interpreted as a template name, this name will be passed to
callTemplate callback of the $CR.run() method settings.
Function
The function will be called, this will point to this Frame, arguments are
depending on the stage (basically, the first argument is a data, the second
argument is a parameters object, the third argument is a form node, in case it
is a Form). The function should return a string (this string will be used as a
template name), a DOM node (this node, will be inserted into the document). The
function could also return false (to stop calling the next handlers for this
stage, see Array description below) and undefined (nothing will happen to
the document, sometimes you just need to call a function).
$CR.$.tpl(name, parent, replace)
To customize the template call result, $CR.$.tpl helper should be used.
...
render: $CR.$.tpl(
String | Function, // Template name, the same to the descriptions above.
String | Function | Node, // Personal parent for the result.
false // Do not replace the DOM from the previous stage, `true` by default.
),
...$CR.$.uri(uri, params, reload, replace)
Sometimes, a location change is needed as the result of the Frame processing. This is true for the forms mostly, but could be used everywhere.
uri is a string (reversed URI pattern) or a function (should return URI).
params is a parameters object or a function that returns a parameters object
(in case uri is a reversed URI pattern, ignored otherwise).
reload and replace have the same meaning
$CR.set() method has.
Array of any of the previous
Strings, Functions, $CR.$.tpl()s and $CR.$.uri()s can be combined into
an array, they will be handled one after another corresponding to the
descriptions above.
frames
Object
To mount root Frame, $CR.add() method should be used.
The frames setting is corresponding for mounting child frames:
$CR.add('/', {
frames: {
'/welcome': {
render: 'template1',
frames: {
'/my/love': {render: 'template2'}
}
},
'/about': {render: 'template3'}
}
});matcher
Function
By default, the Frame becomes active when the URI pattern matches the location.
If you need more complex logic to determine if the Frame is active, you can
use the matcher function. This function will be called if the URI pattern
matches the location, before Frame processing. The function will receive a
parameters object as the first argument, this will point to this Frame. The
function should return true if the Frame should become active, false
otherwise.
Example:
$CR
.add('/:param', {
matcher: function(params) { return Math.random() < 0.5; },
render: 'template1'
})
.add(null, {render: 'not-found'})
.run();
$CR.set('/piupiu'); // `template1` will be rendered one time out of two.break
BooleanFunction
The Frame matching process works the following way. The matcher checks the root Frames one after another. If there is a match in one of the root Frames, this root Frame becomes active with all matched child Frames.
The break setting allows you to stop the following child Frames matching
(something very similar to switch clause in JavaScript).
If the break setting is a function, this function will be called, this will
point to this Frame, the function should return true or false.
Example:
$CR
.add('/', {
render: 'template1',
frames: {
'/?param1=val1¶m2=val2': {render: 'template2'},
'/?param1=val1': {render: 'template3'},
'/?param2=val2': {render: 'template4'}
}
})
.run();
$CR.set('/?param1=val1¶m2=val2');In this example, when we open /?param1=val1¶m2=val2, all three child
Frames will become active.
But if we add break: true to the first child Frame:
$CR
.add('/', {
render: 'template1',
frames: {
'/?param1=val1¶m2=val2': {render: 'template2', break: true},
'/?param1=val1': {render: 'template3'},
'/?param2=val2': {render: 'template4'}
}
})
.run();
$CR.set('/?param1=val1¶m2=val2');Only the first child Frame will be processed now.
keep
Boolean
By default, when you call $CR.set() without
reload argument or when you click a link (conkitty-route catches clicks for
the anchor elements and uses $CR.set() internally), only the Frames that have
changed will be rerendered.
You can add keep: false setting to force the Frame to be rerendered every
time.
Example:
$CR
.add('/', {
render: 'template1',
frames: {
'/sub': {
data: '/api/data',
render: 'template2'
}
}
})
.run();
$CR.set('/sub'); // `/api/data` is loaded, `template2` is rendered.
$CR.set('/sub'); // Nothing happens.But if we add keep: false:
$CR
.add('/', {
render: 'template1',
frames: {
'/sub': {
data: '/api/data',
render: 'template2',
keep: false
}
}
})
.run();
$CR.set('/sub'); // `/api/data` is loaded, `template2` is rendered.
$CR.set('/sub'); // `/api/data` is loaded again, `template2` is rendered again.final
Boolean
By default, any Frame can become the final Frame (when the Frame matches the location and its child Frames don't match the location).
You can add final: false setting to deny this Frame to be the final one.
Example:
$CR
.add('/', {
render: 'template1',
frames: {
'/sub': {
render: 'template2'
}
}
})
.add(null, {render: 'not-found'})
.run();
$CR.set('/sub'); // `template1` and `template2` are rendered.
$CR.set('/'); // Just `template1`.But if we add final: false:
$CR
.add('/', {
final: false,
render: 'template1',
frames: {
'/sub': {
render: 'template2'
}
}
})
.add(null, {render: 'not-found'})
.run();
$CR.set('/sub'); // `template1` and `template2` are rendered.
$CR.set('/'); // `not-found` is rendered.wait
Boolean
By default, the success stage happens as soon as possible (right after the
data is fetched or right after the before stage when no data is needed).
You can delay success stage till all matched child Frames are ready for the
success stage. Just add wait: true. You can cancel the delay deeper by
adding wait: false.
Example:
$CR
.add('/', {
wait: true,
data: '/api/data1',
render: 'template1',
frames: {
'/sub': {
data: '/api/data2',
render: 'template2',
frames: {
'/sub2': {
wait: false,
data: '/api/data3',
render: 'template3'
}
}
}
}
})
.run();
$CR.set('/sub/sub2'); // `template1` and `template2` will be rendered only
// after `/api/data1` and `/api/data2` are loaded.
// `/sub2` Frame has `wait: false`, it will be rendered
// later, when `/api/data3` is loaded.reduce
Boolean
By default, each child Frame URI pattern takes its bite of the location.
Sometimes we want to take a piece of location just to have some status in a
parameters object. If you add reduce: false, the Frame will not take its
bite of the location from the child Frames and will not take a part in
$CR.makeURI() and
Frame.makeURI() functions.
Example:
$CR
.add('/', {
render: 'template1',
frames: {
'/:?item': { // Optional parameter, could be used to get the current item to highlight it.
reduce: false,
render: 'items',
frames: {
'/:item': {render: 'item-details'},
'/': {render: 'no-item-selected'}
}
}
}
})
.run();
$CR.set('/item'); // `template1`, `items` and `item-details` are rendered.
$CR.set('/'); // `template1`, `items` and `no-item-selected` are rendered.form
Form settings object
Array of Form settings objects
Use the form setting when the Frame has an HTML form that needs to be
processed. conkitty-route will intercept a submit event and process the
form according to Form settings.
Example:
$CR
.add('/:param', {
render: 'template1',
form: {
match: '.selector', // Optional matcher, if present, the form will
// be processed in case the form's DOM node
// matches the selector.
action: '/api/:param',
method: 'post',
render: 'template2'
}
})
.run();
$CR.set('/piu'); // `template1` will be rendered (it should have an HTML form).
// After the form is submitted, an AJAX request to `/api/piu`
// will be performed, and the DOM of this Frame will be
// replaced with `template2`.on
Object
You can use $CR.on() method to bind an event
handlers and you can bind an event handlers right from the Frame declaration.
Example:
$CR
.add('/', {
id: 'frame1',
render: 'template1',
on: {
before: function() { alert(this.id + ' before'); },
success: [function() { alert(this.id + ' success1'); }, function() { alert(this.id + ' success2'); }],
after: function() { alert(this.id + ' after'); }
}
});refresh
Number
Experimental. A timeout for a background refresh. After this timeout since
the previous data was fetched, conkitty-route will rerequest the Frame data
and silently rerender the Frame in case the data is changed. Only successful
attempts to fetch the data will be considered and only success and after
render stages will be called. You can customize the data equality check
function using $CR.$.data() helper.
Form
Form summary
The Form is a special kind of the Frame to handle HTML forms.
Form settings
title
The same to the Frame settings title, shows up when the form is
submitted.
action
StringFunction$CR.$.data()$CR.$.static()
When you don't provide the action attribute for the form DOM node, this
action setting is used.
When the action setting is a string, it's a reversed URI pattern (the same
to the Frame data setting).
When the action setting is a function, this function will be called, the
first argument is a serialized form data, the second argument is this Form
parent Frame runtime object, this will point to the DOM node of the form.
The function should return the data of the submission result.
$CR.$.data() and $CR.$.static() have the same meaning the Frame has.
method
String
When you don't provide the method attribute for the form DOM node, this
method setting is used.
check
Function
Provide this function if you need to validate the form before the submission. This function will be called for every field during the form submission or when you manually call Frame.checkForm() method.
The function receives the field DOM node as the first argument, the value as
the second argument and the whole form serialized data as the third argument.
this will point to the form Frame runtime object.
The function should return an error message in case the value is invalid,
false otherwise.
Example:
$CR
.add('/', {
render: 'template1',
form: {
action: '/api/form',
check: function(elem, val, data) {
return Math.random() < 0.5 ? 'The Fortune errors this field' : false;
},
render: 'template2'
}
});state
Function
The form field has three states: valid, invalid and sending. By default,
the fields get disabled when the form is in sending state.
You can provide a custom field state changer.
The function receives the field DOM node as the first argument, the state (one
of three states above) and an error message from the check function as the
third argument. this will point to the form Frame runtime object.
The function should return undefined when you don't want to cancel the
default action (disabling fields in sending state), something else otherwise.
Example:
$CR
.add('/', {
render: 'template1',
form: {
action: '/api/form',
check: function(elem, val, data) {
return Math.random() < 0.5 'The Fortune errors this field' : false;
},
state: function(elem, state, msg) {
switch (state) {
case 'valid': elem.className = ''; break;
case 'invalid': elem.className = 'invalid'; alert(msg); break;
case 'sending': elem.className = 'sending'; break;
}
},
render: 'template2'
}
});type
String
By default, the form data is sent in the urlencoded form format (with
application/x-www-form-urlencoded content type).
Set to text when you want to submit the form data as plain text (with
text/plain content type).
Set to json when you want to submit the form data as JSON (with
application/json content type).
submit
Function
Use this function to preprocess the data before the submission or to cancel the submission.
This function receives the form data as the first argument, the form Frame
runtime object as the second argument, cancel function as the third argument.
this will point to the DOM node of the form.
The function should return an actual data to send or call cancel function.
Example:
$CR
.add('/', {
render: 'template1',
form: {
action: '/api/form',
type: 'json',
submit: function(data, frame, cancel) {
// Use `cancel()` to cancel the submission.
return {values: data.map(function(item) { return item.value; })};
},
render: 'template2'
}
});xhr
Function
Use this function to set additional headers for the form XMLHttpRequest object.
This function receives XMLHttpRequest object as the first argument, the form
data as the second argument, the form Frame runtime object as the third
argument. this will point to the DOM node of the form.
render
The same to the Frame render. The result will be rendered instead
of the form parent Frame DOM.
API
$CR.add(uri, frame)
Mount a root Frame.
uri— an URI pattern.frame— a settings object.
$CR.run(defaults)
$CR.get(frameId)
Get the Frame runtime object.
frameId— an identifier.
$CR.set(uri [, reload, replace])
Set the current location.
uri— a new location.reload(optional) —conkitty-routedoesn't reload the Frame data and doesn't rerender the Frame, when no corresponding to the Frame URI part is changed. By passingtrueyou can force the Frame to be reloaded.replace(optional) — by default, each$CR.set()method call adds a record to the browser history. Passtrueto replace the current history position instead of creating the new one.
$CR.on(event, handler , frameId)
Bind an event handler.
event— an event name or space-separated set of event names.handler— an event handler.frameId(optional) — an identifier (when the handler should be called for the specific Frame only).
There are per-Frame events:
before— the Frame has become active.success— when the data is loaded (or right afterbeforewhen there is no data).error— when an error has occurred during loading the data.after— aftersuccessorerrorevent.leave— the Frame has stopped to be active.stop— the location has changed before the Frame data is loaded.except— an exception has occurred during processing the Frame.
There are meta events:
busy— when some data has started to load.idle— when all the datas have been loaded.
$CR.off(event, handler , frameId)
Unbind an event handler.
The arguments are the same to $CR.on() method
arguments.
$CR.makeURI(uri , params)
Build an URI from URI pattern an a paremeters object.
uri— an URI pattern.params— a parameters object.
Example:
$CR.makeURI('/hello/:world?arg=:ololo', {world: 'piu', ololo: ['11', '22']});
// '/hello/piu?arg=11&arg=22'$CR.params()
Get the current location query string params by their actual names.
Example:
$CR.set('/hello?world=beautiful&yes=truly&yes=indeed');
console.log($CR.params());
// {world: 'beautiful', yes: ['truly', 'indeed']}$CR.serializeForm(node , withFields)
Frame API
Frame.params()
Get the parameters object from the Frame runtime object.
Frame.data(index)
Frame.makeURI(params)
Frame.active(andPrev)
Frame.reload()
Frame.checkForm(node)
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago