diff options
-rw-r--r-- | install/ui/src/freeipa/Application_controller.js | 18 | ||||
-rw-r--r-- | install/ui/src/freeipa/navigation.js | 31 | ||||
-rw-r--r-- | install/ui/src/freeipa/navigation/Router.js | 243 | ||||
-rw-r--r-- | install/ui/src/freeipa/navigation/routing.js | 505 |
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 |