summaryrefslogtreecommitdiffstats
path: root/install/ui/src
diff options
context:
space:
mode:
authorPetr Vobornik <pvoborni@redhat.com>2014-05-16 18:22:56 +0200
committerPetr Vobornik <pvoborni@redhat.com>2014-06-23 15:13:13 +0200
commit86898065b5e1d60168e2daff050853729b34f1ce (patch)
tree0c2b07612110ab2cde6f61881593a9c00bcadfe9 /install/ui/src
parent27836cba9d865b1c912a65d0cd04562194f9e93f (diff)
downloadfreeipa-86898065b5e1d60168e2daff050853729b34f1ce.tar.gz
freeipa-86898065b5e1d60168e2daff050853729b34f1ce.tar.xz
freeipa-86898065b5e1d60168e2daff050853729b34f1ce.zip
webui: generic routing
Router is not able to create hash from facet state for custom routes/facets. This patch refactors router methods into providers. It allows to create additional route handlers, navigators and hash creators. These providers are mapped to facets and therefore it's possible to create router hash for any facet without any logic in the facet itself. Reviewed-By: Endi Sukma Dewata <edewata@redhat.com>
Diffstat (limited to 'install/ui/src')
-rw-r--r--install/ui/src/freeipa/Application_controller.js18
-rw-r--r--install/ui/src/freeipa/navigation.js31
-rw-r--r--install/ui/src/freeipa/navigation/Router.js243
-rw-r--r--install/ui/src/freeipa/navigation/routing.js505
4 files changed, 538 insertions, 259 deletions
diff --git a/install/ui/src/freeipa/Application_controller.js b/install/ui/src/freeipa/Application_controller.js
index 5a0b67b80..9ed0bc6e9 100644
--- a/install/ui/src/freeipa/Application_controller.js
+++ b/install/ui/src/freeipa/Application_controller.js
@@ -35,10 +35,11 @@ define([
'./reg',
'./navigation/Menu',
'./navigation/Router',
+ './navigation/routing',
'./navigation/menu_spec'
],
function(declare, lang, array, Deferred, on, topic, query, dom_class, auth,
- JSON, App_widget, FacetContainer, IPA, reg, Menu, Router, menu_spec) {
+ JSON, App_widget, FacetContainer, IPA, reg, Menu, Router, routing, menu_spec) {
/**
* Application controller
@@ -78,6 +79,7 @@ define([
init: function() {
this.menu = new Menu();
this.router = new Router();
+ routing.init(this.router);
var body_node = query('body')[0];
this.app_widget = new App_widget();
@@ -181,7 +183,7 @@ define([
if (IPA.is_selfservice) {
this.on_profile();
} else {
- this.router.navigate_to_entity_facet('user', 'search');
+ routing.navigate(routing.default_path);
}
},
@@ -219,7 +221,7 @@ define([
},
on_profile: function() {
- this.router.navigate_to_entity_facet('user', 'details', [IPA.whoami.uid[0]]);
+ routing.navigate(['entity', 'user', 'details', [IPA.whoami.uid[0]]]);
},
on_logout: function(event) {
@@ -287,8 +289,7 @@ define([
on_facet_state_changed: function(event) {
if (event.facet === this.current_facet) {
- var hash = this.router.create_hash(event.facet, event.state);
- this.router.update_hash(hash, true);
+ routing.update_hash(event.facet, event.state);
}
},
@@ -405,14 +406,15 @@ define([
if (!child) {
if(menu_item.entity) {
// entity pages
- this.router.navigate_to_entity_facet(
+ routing.navigate([
+ 'entity',
menu_item.entity,
menu_item.facet,
menu_item.pkeys,
- menu_item.args);
+ menu_item.args]);
} else if (menu_item.facet) {
// concrete facets
- this.router.navigate_to_facet(menu_item.facet, menu_item.args);
+ routing.navigate(['generic', menu_item.facet, menu_item.args]);
} else {
// categories, select first posible child, it may be the last
var children = this.menu.query({parent: menu_item.name });
diff --git a/install/ui/src/freeipa/navigation.js b/install/ui/src/freeipa/navigation.js
index 7cf309f15..105e867cb 100644
--- a/install/ui/src/freeipa/navigation.js
+++ b/install/ui/src/freeipa/navigation.js
@@ -21,16 +21,11 @@
define([
'dojo/_base/lang',
- './app_container',
- './ipa'
+ './navigation/routing'
],
- function(lang, app_container, IPA) {
+ function(lang, routing) {
- var get_router = function() {
- return app_container.app.router;
- };
-
var navigation = {
/**
* Navigation tells application to show certain facet.
@@ -88,7 +83,6 @@ define([
*/
show: function(arg1, arg2, arg3) {
- var nav = get_router();
var params = {};
this.set_params(params, arg1);
@@ -98,19 +92,20 @@ define([
var facet = params.facet;
if (typeof facet === 'string') {
- nav.navigate_to_facet(facet, params.args);
+ return routing.navigate(['generic', facet, params.args]);
}
if (!facet) throw 'Argument exception: missing facet';
if (facet && facet.entity) {
- return nav.navigate_to_entity_facet(
+ return routing.navigate([
+ 'entity',
facet.entity.name,
facet.name,
params.pkeys,
- params.args);
+ params.args]);
} else {
- return nav.navigate_to_facet(facet.name, params.args);
+ return routing.navigate(['generic', facet.name, params.args]);
}
},
@@ -128,14 +123,12 @@ define([
* @param {Object|facet.facet|string|Function} arg3
*/
show_entity: function(entity_name, arg1, arg2, arg3) {
- var nav = get_router();
var params = {};
-
this.set_params(params, arg1);
this.set_params(params, arg2);
this.set_params(params, arg3);
- return nav.navigate_to_entity_facet(entity_name, params.facet,
- params.pkeys, params.args);
+ return routing.navigate(['entity', entity_name, params.facet,
+ params.pkeys, params.args]);
},
/**
@@ -150,8 +143,7 @@ define([
* notification purposes
*/
show_generic: function(hash, facet) {
- var nav = get_router();
- nav.navigate_to_hash(hash, facet);
+ routing.router.navigate_to_hash(hash, facet);
},
/**
@@ -159,8 +151,7 @@ define([
* @method show_default
*/
show_default: function() {
- // TODO: make configurable
- return this.show_entity('user', 'search');
+ routing.navigate(routing.default_path);
}
};
return navigation;
diff --git a/install/ui/src/freeipa/navigation/Router.js b/install/ui/src/freeipa/navigation/Router.js
index a3b2a6791..a65c60fd3 100644
--- a/install/ui/src/freeipa/navigation/Router.js
+++ b/install/ui/src/freeipa/navigation/Router.js
@@ -20,14 +20,10 @@
define(['dojo/_base/declare',
'dojo/_base/lang',
- 'dojo/_base/array',
'dojo/Evented',
- 'dojo/io-query',
- 'dojo/router',
- '../ipa',
- '../reg'
+ 'dojo/router'
],
- function(declare, lang, array, Evented, ioquery, router, IPA, reg) {
+ function(declare, lang, Evented, router) {
/**
* Router
@@ -56,27 +52,6 @@ define(['dojo/_base/declare',
route_prefix: '',
/**
- * Variations of entity routes
- * @property {Array.<string>}
- */
- entity_routes: [
- '/e/:entity/:facet/:pkeys/*args',
- '/e/:entity/:facet//*args',
- '/e/:entity/:facet/:pkeys',
- '/e/:entity/:facet',
- '/e/:entity'
- ],
-
- /**
- * Variations of simple page routes
- * @property {Array.<string>}
- */
- page_routes: [
- '/p/:page/*args',
- '/p/:page'
- ],
-
- /**
* Used during facet changing. Set it to true in 'facet-change'
* event handler to stop the change.
* @property {boolean}
@@ -100,145 +75,22 @@ define(['dojo/_base/declare',
* @param {Function} handler to be associated with the route(s)
*/
register_route: function(route, handler) {
- // TODO: add multiple routes for one handler
- route = this.route_prefix + route;
- this.route_handlers.push(router.register(route, lang.hitch(this, handler)));
- },
-
- /**
- * Initializates router
- * - registers handlers
- */
- init_router: function() {
-
- // entity pages
- array.forEach(this.entity_routes, function(route) {
- this.register_route(route, this.entity_route_handler);
- }, this);
-
- // special pages
- array.forEach(this.page_routes, function(route) {
- this.register_route(route, this.page_route_handler);
- }, this);
- },
-
- /**
- * Handler for entity routes
- * Shouldn't be invoked directly.
- * @param {Object} event route event args
- */
- entity_route_handler: function(event) {
-
- if (this.check_clear_ignore()) return;
-
- var entity_name = event.params.entity;
- var facet_name = event.params.facet;
- var pkeys, args;
- try {
- pkeys = this._decode_pkeys(event.params.pkeys || '');
- args = ioquery.queryToObject(event.params.args || '');
- } catch (e) {
- this._error('URI error', 'route', event.params);
- return;
- }
- args.pkeys = pkeys;
-
- // set new facet state
- var entity = reg.entity.get(entity_name);
- if (!entity) {
- this._error('Unknown entity', 'route', event.params);
- return;
- }
- var facet = entity.get_facet(facet_name);
- if (!facet) {
- this._error('Unknown facet', 'route', event.params);
- return;
- }
- facet.reset_state(args);
-
- this.show_facet(facet);
- },
-
- /**
- * General facet route handler
- * Shouldn't be invoked directly.
- * @param {Object} event route event args
- */
- page_route_handler: function(event) {
-
- if (this.check_clear_ignore()) return;
-
- var facet_name = event.params.page;
- var args;
- try {
- args = ioquery.queryToObject(event.params.args || '');
- } catch (e) {
- this._error('URI error', 'route', event.params);
- return;
- }
-
- // set new facet state
- var facet = reg.facet.get(facet_name);
- if (!facet) {
- this._error('Unknown facet', 'route', event.params);
- return;
- }
- facet.reset_state(args);
-
- this.show_facet(facet);
- },
-
- /**
- * Used for switching to entitie's facets. Current target facet
- * state is used as params (pkeys, args) when none of pkeys and args
- * are used (useful for switching to previous page with keeping the context).
- */
- navigate_to_entity_facet: function(entity_name, facet_name, pkeys, args) {
-
- var entity = reg.entity.get(entity_name);
- if (!entity) {
- this._error('Unknown entity', 'navigation', { entity: entity_name});
- return false;
- }
-
- var facet = entity.get_facet(facet_name);
- if (!facet) {
- this._error('Unknown facet', 'navigation', { facet: facet_name});
- return false;
- }
-
- // Use current state if none supplied
- if (!pkeys && !args) {
- args = facet.get_state();
- }
- args = args || {};
-
- // Facets may be nested and require more pkeys than supplied.
- args.pkeys = facet.get_pkeys(pkeys);
- var hash = this._create_entity_facet_hash(facet, args);
- return this.navigate_to_hash(hash, facet);
- },
-
- /**
- * Navigate to other facet.
- */
- navigate_to_facet: function(facet_name, args) {
-
- var facet = reg.facet.get(facet_name);
- if (!facet) {
- this._error('Unknown facet', 'navigation', { facet: facet_name});
- return false;
+ if (route instanceof Array) {
+ for (var i=0, l=route.length; i<l; i++) {
+ this.register_route(route[i], handler);
+ }
+ } else {
+ var r = this.route_prefix + route;
+ this.route_handlers.push(router.register(r, lang.hitch(this, handler)));
}
- if (!args) args = facet.get_state();
- var hash = this._create_facet_hash(facet, args);
- return this.navigate_to_hash(hash, facet);
},
/**
- * Low level function.
+ * Navigate to given hash
*
- * Public usage should be limited reinitializing canceled navigations.
+ * @fires facet-change
+ * @fires facet-change-canceled
*/
navigate_to_hash: function(hash, facet) {
@@ -273,48 +125,6 @@ define(['dojo/_base/declare',
},
/**
- * Creates from facet state appropriate hash.
- */
- _create_entity_facet_hash: function(facet, state) {
- state = lang.clone(state);
- var entity_name = facet.entity.name;
- var pkeys = this._encode_pkeys(state.pkeys || []);
- delete state.pkeys;
- var args = ioquery.objectToQuery(state || {});
-
- var path = [this.route_prefix, 'e', entity_name, facet.name];
- if (!IPA.is_empty(args)) path.push(pkeys, args);
- else if (!IPA.is_empty(pkeys)) path.push(pkeys);
-
- var hash = path.join('/');
- return hash;
- },
-
- /**
- * Creates hash of general facet.
- */
- _create_facet_hash: function(facet, state) {
- var args = ioquery.objectToQuery(state.args || {});
- var path = [this.route_prefix, 'p', facet.name];
-
- if (!IPA.is_empty(args)) path.push(args);
- var hash = path.join('/');
- return hash;
- },
-
- /**
- * Creates hash from supplied facet and state.
- *
- * @param {facet.facet} facet
- * @param {Object} state
- */
- create_hash: function(facet, state) {
- if (facet.entity) return this._create_entity_facet_hash(facet, state);
- else return this._create_facet_hash(facet, state);
- },
-
-
- /**
* Tells other component to show given facet.
*/
show_facet: function(facet) {
@@ -325,34 +135,6 @@ define(['dojo/_base/declare',
},
/**
- * URI Encodes array items and delimits them by '&'
- * Example: ['foo ', 'bar'] => 'foo%20&bar'
- */
- _encode_pkeys: function(pkeys) {
-
- var ret = [];
- array.forEach(pkeys, function(pkey) {
- ret.push(encodeURIComponent(pkey));
- });
- return ret.join('&');
- },
-
- /**
- * Splits strings by '&' and return an array of URI decoded parts.
- * Example: 'foo%20&bar' => ['foo ', 'bar']
- */
- _decode_pkeys: function(str) {
-
- if (!str) return [];
-
- var keys = str.split('&');
- for (var i=0; i<keys.length; i++) {
- keys[i] = decodeURIComponent(keys[i]);
- }
- return keys;
- },
-
- /**
* Raise 'error'
* @protected
* @fires error
@@ -375,7 +157,6 @@ define(['dojo/_base/declare',
constructor: function(spec) {
spec = spec || {};
- this.init_router();
}
});
diff --git a/install/ui/src/freeipa/navigation/routing.js b/install/ui/src/freeipa/navigation/routing.js
new file mode 100644
index 000000000..6e18b0228
--- /dev/null
+++ b/install/ui/src/freeipa/navigation/routing.js
@@ -0,0 +1,505 @@
+/* Authors:
+ * Petr Vobornik <pvoborni@redhat.com>
+ *
+ * Copyright (C) 2014 Red Hat
+ * see file 'COPYING' for use and warranty information
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+define([
+ 'dojo/_base/declare',
+ 'dojo/_base/lang',
+ 'dojo/_base/array',
+ 'dojo/io-query',
+ '../reg',
+ '../util'
+ ],
+ function(declare, lang, array, ioquery, reg, util) {
+
+/**
+ * Routing mechanism
+ * @class navigation.routing
+ * @singleton
+ */
+var routing = {
+
+ /**
+ * Router instance
+ * @property {navigation.Router}
+ */
+ router: null,
+
+ /**
+ * Map of router handlers
+ * @property {Object}
+ */
+ route_handlers: {},
+
+ /**
+ * Map of hash creators
+ * @property {Object}
+ */
+ hash_creators: {},
+
+ /**
+ * Facet name to hash creator map
+ *
+ * - Key: facet name
+ * - Value: hash creator
+ *
+ * @type {Object}
+ */
+ hc_facet_map: {},
+
+ /**
+ * Hash creator priority queue
+ *
+ * First item == highest priority
+ *
+ * @type {Array}
+ */
+ hc_queue: [],
+
+ /**
+ * Map of navigators
+ * @type {Object}
+ */
+ navigators: {},
+
+ /**
+ * Add hash creator at the beginning of hash creator queue
+ * @param {navigation.routing.HashCreator} hash_creator
+ * @param {Number} [position]
+ */
+ add_hash_creator: function(hash_creator, position) {
+
+ if (position !== undefined) {
+ this.hc_queue.splice(position, 0, hash_creator);
+ } else {
+ this.hc_queue.unshift(hash_creator);
+ }
+ },
+
+ /**
+ * Add hash creator to hash creator map
+ * @param {string} facet_name
+ * @param {navigation.routing.HashCreator} hash_creator
+ */
+ assign_hash_creator: function (facet_name, hash_creator) {
+ this.hc_facet_map[facet_name] = hash_creator;
+ },
+
+ /**
+ * Get hash creator for given facet
+ *
+ * Lookup priority:
+ *
+ * - facet -> hash creator map
+ * - hash creator queue
+ *
+ * @param {facets.Facet} facet [description]
+ * @return {navigation.routing.HashCreator}
+ */
+ get_hash_creator: function(facet) {
+
+ var name = facet.name;
+ var hc = this.hc_facet_map[name];
+ if (!hc) {
+ for (var i=0, l=this.hc_queue.length; i<l; i++) {
+ if (this.hc_queue[i].handles(facet)) {
+ hc = this.hc_queue[i];
+ break;
+ }
+ }
+ }
+ return hc || null;
+ },
+
+ /**
+ * Create hash for given facet
+ *
+ * @param {facets.Facet} facet
+ * @param {Object|null} options
+ * @return {string} hash
+ */
+ create_hash: function(facet, options) {
+ var hc = this.get_hash_creator(facet);
+ if (!hc) return '';
+ return hc.create_hash(this.router, facet, options);
+ },
+
+ /**
+ * Navigate by a Navigator
+ *
+ * Expects path as argument. Path is an array where
+ * first element is name of the Navigator, rest are
+ * navigators params.
+ *
+ * @param {Array} path
+ * @return {boolean}
+ */
+ navigate: function(path) {
+
+ path = path.slice(0);
+ var nav_name = path.shift();
+ var nav = this.get_navigator(nav_name);
+ return nav.navigate.apply(nav, path);
+ },
+
+ /**
+ * Navigate to specific facet with give options
+ * @param {facets.Facet} facet
+ * @param {Object} options Options for hash creator
+ * @return {boolean}
+ */
+ navigate_to_facet: function(facet, options) {
+ var hash = this.create_hash(facet, options);
+ return this.router.navigate_to_hash(hash);
+ },
+
+ update_hash: function(facet, options) {
+
+ var hash = this.create_hash(facet, options);
+ this.router.update_hash(hash, true);
+ },
+
+ /**
+ * Add route handler to router
+ * @param {string|string[]} route Route or routes.
+ * @param {navigation.routing.RouteHandler} handler Handler
+ */
+ add_route: function(route, handler) {
+ this.route_handlers[handler.name] = handler;
+ this.router.register_route(route, handler.get_handler());
+ },
+
+ /**
+ * Add navigator
+ * @param {navigation.routing.Navigator} navigator
+ */
+ add_navigator: function(navigator) {
+ this.navigators[navigator.name] = navigator;
+ },
+
+ /**
+ * Get navigator by name
+ * @param {string} name Navigator's name
+ * @return {navigation.routing.Navigator}
+ */
+ get_navigator: function(name) {
+ return this.navigators[name];
+ },
+
+ /**
+ * Path for default facet
+ * @type {Array}
+ */
+ default_path: ['entity', 'user', 'search'],
+
+ /**
+ * Variations of entity routes
+ * @property {string[]}
+ */
+ entity_routes: [
+ '/e/:entity/:facet/:pkeys/*args',
+ '/e/:entity/:facet//*args',
+ '/e/:entity/:facet/:pkeys',
+ '/e/:entity/:facet',
+ '/e/:entity'
+ ],
+
+ /**
+ * Variations of simple page routes
+ * @property {string[]}
+ */
+ page_routes: [
+ '/p/:page/*args',
+ '/p/:page'
+ ]
+};
+
+/**
+ * General route handler
+ *
+ * @class navigation.routing.RouteHandler
+ */
+routing.RouteHandler = declare([], {
+
+ handler: null,
+
+ name: 'generic',
+
+ /**
+ * Handle router event
+ * @param {Object} event
+ * @param {navigation.Router} router
+ */
+ handle: function (event, router) {
+ if (router.check_clear_ignore()) return;
+
+ var facet_name = event.params.page;
+ var args;
+ try {
+ args = ioquery.queryToObject(event.params.args || '');
+ } catch (e) {
+ router._error('URI error', 'route', event.params);
+ return;
+ }
+
+ // set new facet state
+ var facet = reg.facet.get(facet_name);
+ if (!facet) {
+ router._error('Unknown facet', 'route', event.params);
+ return;
+ }
+ facet.reset_state(args);
+ router.show_facet(facet);
+ },
+
+ /**
+ * Create handler callback for router
+ * @return {Function} callback
+ */
+ get_handler: function() {
+
+ if (!this.handler) {
+ var self = this;
+ this.handler = function(event) {
+ self.handle(event, this);
+ };
+ }
+ return this.handler;
+ }
+});
+
+/**
+ * Entity route handler
+ *
+ * @class navigation.routing.EntityRouteHandler
+ * @extends {navigation.routing.RouteHandler}
+ */
+routing.EntityRouteHandler = declare([routing.RouteHandler], {
+
+ name: 'entity',
+
+ /**
+ * @inheritDoc
+ */
+ handle: function (event, router) {
+ if (router.check_clear_ignore()) return;
+
+ var entity_name = event.params.entity;
+ var facet_name = event.params.facet;
+ var pkeys, args;
+ try {
+ pkeys = this._decode_pkeys(event.params.pkeys || '');
+ args = ioquery.queryToObject(event.params.args || '');
+ } catch (e) {
+ router._error('URI error', 'route', event.params);
+ return;
+ }
+ args.pkeys = pkeys;
+
+ // set new facet state
+ var entity = reg.entity.get(entity_name);
+ if (!entity) {
+ router._error('Unknown entity', 'route', event.params);
+ return;
+ }
+ var facet = entity.get_facet(facet_name);
+ if (!facet) {
+ router._error('Unknown facet', 'route', event.params);
+ return;
+ }
+ facet.reset_state(args);
+ router.show_facet(facet);
+ },
+
+ /**
+ * Splits strings by '&' and return an array of URI decoded parts.
+ * Example: 'foo%20&bar' => ['foo ', 'bar']
+ */
+ _decode_pkeys: function(str) {
+
+ if (!str) return [];
+
+ var keys = str.split('&');
+ for (var i=0; i<keys.length; i++) {
+ keys[i] = decodeURIComponent(keys[i]);
+ }
+ return keys;
+ }
+});
+
+/**
+ * Hash creator creates a hash string from given facet and options
+ *
+ * This is default hash creator for generic facets.
+ *
+ * @class navigation.routing.HashCreator
+ */
+routing.HashCreator = declare([], {
+
+ prefix: 'p',
+
+ name: 'generic',
+
+ create_hash: function(router, facet, options) {
+
+ var path = [router.route_prefix, this.prefix, facet.name];
+ var args = ioquery.objectToQuery(options || {});
+ if (!util.is_empty(args)) path.push(args);
+ var hash = path.join('/');
+ return hash;
+ },
+
+ handles: function(facet) {
+ return true;
+ }
+});
+
+/**
+ * Hash creator for entity facets
+ * @class navigation.routing.EntityHashCreator
+ * @extends navigation.routing.HashCreator
+ */
+routing.EntityHashCreator = declare([routing.HashCreator], {
+
+ prefix: 'e',
+
+ name: 'entity',
+
+ create_hash: function(router, facet, options) {
+
+ options = lang.clone(options);
+ var entity_name = facet.entity.name;
+ var pkeys = this._encode_pkeys(options.pkeys || []);
+ delete options.pkeys;
+ var args = ioquery.objectToQuery(options || {});
+
+ var path = [router.route_prefix, this.prefix, entity_name, facet.name];
+ if (!util.is_empty(args)) path.push(pkeys, args);
+ else if (!util.is_empty(pkeys)) path.push(pkeys);
+
+ var hash = path.join('/');
+ return hash;
+ },
+
+ handles: function(facet) {
+ return !!facet.entity;
+ },
+
+ /**
+ * URI Encodes array items and delimits them by '&'
+ * Example: ['foo ', 'bar'] => 'foo%20&bar'
+ */
+ _encode_pkeys: function(pkeys) {
+
+ var ret = [];
+ array.forEach(pkeys, function(pkey) {
+ ret.push(encodeURIComponent(pkey));
+ });
+ return ret.join('&');
+ }
+});
+
+/**
+ * Navigate to other facet.
+ *
+ * @class navigation.routing.Navigator
+ */
+routing.Navigator = declare([], {
+
+ name: 'generic',
+
+ navigate: function(facet_name, args) {
+
+ var facet = reg.facet.get(facet_name);
+ if (!facet) {
+ routing.router._error('Unknown facet', 'navigation', { facet: facet_name});
+ return false;
+ }
+ if (!args) args = facet.get_state();
+
+ return routing.navigate_to_facet(facet, args);
+ }
+});
+
+/**
+ * Used for switching to entities' facets. Current target facet
+ * state is used as params (pkeys, args) when none of pkeys and args
+ * are used (useful for switching to previous page with keeping the context).
+ *
+ * @class navigation.routing.EntityNavigator
+ * @extends navigation.routing.Navigator
+ */
+routing.EntityNavigator = declare([routing.Navigator], {
+
+ name: 'entity',
+
+ navigate: function(entity_name, facet_name, pkeys, args) {
+
+ var entity = reg.entity.get(entity_name);
+ if (!entity) {
+ routing.router._error('Unknown entity', 'navigation', { entity: entity_name});
+ return false;
+ }
+
+ var facet = entity.get_facet(facet_name);
+ if (!facet) {
+ routing.router._error('Unknown facet', 'navigation', { facet: facet_name});
+ return false;
+ }
+
+ // Use current state if none supplied
+ if (!pkeys && !args) {
+ args = facet.get_state();
+ }
+ args = args || {};
+
+ // Facets may be nested and require more pkeys than supplied.
+ args.pkeys = facet.get_pkeys(pkeys);
+
+ return routing.navigate_to_facet(facet, args);
+ }
+});
+
+/**
+ * Init routing
+ *
+ * Sets default routes, handlers, hash creators and navigators
+ *
+ * @param {navigation.Router} router
+ */
+routing.init = function(router) {
+
+ if (router) this.router = router;
+ var generic_hc = new routing.HashCreator();
+ var entity_hc = new routing.EntityHashCreator();
+ var generic_rh = new routing.RouteHandler();
+ var entity_rh = new routing.EntityRouteHandler();
+ var generic_n = new routing.Navigator();
+ var entity_n = new routing.EntityNavigator();
+ this.add_hash_creator(generic_hc);
+ this.add_hash_creator(entity_hc);
+ this.add_route(this.routes, generic_rh);
+ this.add_route(this.entity_routes, entity_rh);
+ this.add_navigator(generic_n);
+ this.add_navigator(entity_n);
+};
+
+return routing;
+
+}); \ No newline at end of file