>

ko.routing

ko.routing is an advanced light router (7kb minimized) specifially designed for knockout.js It depends only on knockout.js and it may work either alone or as an optional module for the Data Moving SPA Framework. When used  with the Data Moving SPA Framework it substitutes the native virtual link based router, and provide a classical standard url based routing that gives a fine grained control over the search engine visibility of each link.

ko.routing with some simple examples may be downloaded here, but it is available also as a nuget package.

Discussions & bugs may be posted in this forum.

ko.routing main features

  • Free, open source (MIT license).
  • It is very light (7kb minimized) and depends just on knockout.js.
  • Like knockout.js it is AMD, and CommonJs compatible.
  • It allows a fine grained control over the search engine visibility of all SPA links. Depending, on various settings the action knockout binding inserts each url either into the href attribute that is visible to the search engines, or into a custom attribute that remains hidden to the search engines.
  • You may have several independent routers in the same page. Only the main router may be connected to the browser history, but all other routers have their private history that may be easily exploited by calling its methods, or by connecting to its knockout.js observables.
  • The format of all routes may be changed without affecting the remainder of the application. In fact, the action binding generates all urls from a set of parameters, thus adapting them to routing rules changes. The router, transforms urls into parameters values that are passed to user provided action functions, while the action binding performs the inverse transformation by creating the url that would yield a given set of parameters.
  • Routing rules may be nested. For instance, the 0 level routing rule might select the view, while level 1 might select further options of the view, or a detail view to be shown in a different area. If the user clicks a link that doesn't change the url section that selects the view, the view isn't re-loaded and mantains its state, while receiving the new options passed by the level 1 routing rules.
    In general the action function of a level n rule is invoked only if there was a change in the ulr part that is processed by the routing rule up to level n.
  • Routers understand relative links. More specifically, if the purpose of a link is just to change the selection made by the level n routing rules, all parameters extracted by the n-1 level before are not required in the action binding, since their values are inferred automatically. Thus, for instance, if we have a 2 level routing rule where the 0 level section extracts the view while the level 1  section extracts the id of a record to be shown, and if we would like to change the record shown, then we need an action binding that specifies just the new id: data-bind="action: {id: 21}", instead of a full set of parameters: data-bind="action: {module: 'people', 'view: 'customers_detail', id: 21}".


Basic usage

As a first step we specify a default action function to be invoked each time the current rule match a route:

default action

Where:

  • obj contains the parameters extracted by the routing rules. The action function invoked at kevel n of a routing rules is passed all parameters extracted up to level n.
  • level is the level of the routing rule that invoked the action function. Levels are 0 based.
  • hasChildren is true, if and only if the routing rule has children nested rules.

Then we may specify all routing rules:

routing rules

important: routing may be started on the initial page url, as soon as the Html page is loaded by calling router.history.refresh();

As a default the first router defined, is assumed to be the main router that is associated with the browser history.  Leaf routes are specified with the route function, while routes with children rules are specified with the prefix function that is closed with the end function. route and prefix, accepts exactly the same parameters:

  1. The string to match. The match string may contain parameters that are alfanumeric strings preceeded by :. Optional parameters are followed by ?. When an option parameter is immediately preceeded by /, the / must be omitted whenever the parameter value is omitted. Parameter names ends as soon as a character that is neither a letter or a number is encountered. Some parameter values may be passed also as query string. Thus for instance, the url #/home/index?id=1 matches the first routing rule, thus defining module="home", and view="index" that are taken by the rule default values, and id=1 that is taken from the query string. Anagously, #/people/customer?id=1 matches the last routing rules(the one immediately before the 0 level notFound), thus defining module="people" and view="customer" that are extracted by matching the rule parameters with the url, and id=1 that is extracted from the query string.
    Match string may contain alfanumeric strings plus the following characters: ?, /, ., -, #, !.
  2. The parameter default values. These values are taken whenever the parameter value isn't extracted by matching the current url with the match string.
  3. An optional action function. If this parameter is not provided the default action function is taken.

The notFound method only accepts the parameter defaults and the optional action function, since it matches all urls. It is used to catch all request with no match.

All rules are evaluated in the same order they were defined till a match is found. In the case of nested rules, the leading part of the url that matched the rule is removed, and the remainder of the string is passed to the nested routing rules. Thus, for instance after that #/nested/main/5 matched the 0 level rule "#/nested/:view", the parameter view=5 is extracted, the action function is invoked, and the string /5 is passed to the "/:par?" children rule. If  all n level rules fail the n level notFound rule is invoked if it is defined, otherwise no further action is performed.

Urls may be inserted either as values of href attributes of anchors, or as values of data-action attributes of any Html node.

The main router, that is connected with the browser history, accepts only urls coming from href attributes that starts with #, since all urls it processes must be visible to the search engines. Actually, for a correct interaction with search engines  these links should start with #!. GoogleBot is now able to run javascript, and to perform ajax calls, but all other search engines require a static html snapshot(see the previous link for more details).

In order to keep the code independent from the match string formats, urls are usually created starting from the parameter values with the action binding:

action binding

The action binding infers that the above parameter values are created by an url that matches the second 0 level rule, and then its first child rule. Then it creates the the adequate url, and assign it to the link href attribute.

If the action binding isn't in the scope of any local router, its uses the main router rules to compute the url, otherwise it uses the innermost router it is in the scope of. We may specify a different router by adding it to the _router parameter:

action binding with property

Where:

routing rules parameters

In case the link is associated with the main router the url is always assigned to the link href attribute, while in case of local router the urls is assigned to the data-action attribute if:

  • the local router was created with the fakeUrls option;
  • the parameters contain: _fakeUrl=true;
  • the html node is not an anchor;

An url may be computed from the parameters also programmatically, by calling either the router action method method:

action method

Or, for the main router, by calling:

main router action

Changing the main router

In order to change the main router one need first to reset the routing module by calling:

ko.routing.reset()

Then it is enough to create another router by calling new ko.routing.router(). Any attempt to define a new router without resetting the routing system will define a local router, also if the router is not explicitely declared to be local.