diff options
author | Petr Vobornik <pvoborni@redhat.com> | 2013-01-31 17:25:14 +0100 |
---|---|---|
committer | Petr Vobornik <pvoborni@redhat.com> | 2013-05-06 16:22:17 +0200 |
commit | 31d7486b88bc0e30c8a84ab4d2f73c35a700dad8 (patch) | |
tree | f28ee432487ef269e285064c5588e4b4168c5205 /install/ui/src/freeipa/facet.js | |
parent | 7edf044a440d9a7f60c691811acedfc6a20ecbfe (diff) | |
download | freeipa-31d7486b88bc0e30c8a84ab4d2f73c35a700dad8.tar.gz freeipa-31d7486b88bc0e30c8a84ab4d2f73c35a700dad8.tar.xz freeipa-31d7486b88bc0e30c8a84ab4d2f73c35a700dad8.zip |
Remove IPA.nav usage, obsolete entity.get_primary_key
https://fedorahosted.org/freeipa/ticket/3236
Diffstat (limited to 'install/ui/src/freeipa/facet.js')
-rw-r--r-- | install/ui/src/freeipa/facet.js | 464 |
1 files changed, 416 insertions, 48 deletions
diff --git a/install/ui/src/freeipa/facet.js b/install/ui/src/freeipa/facet.js index 3ad868e84..f63e6d7ae 100644 --- a/install/ui/src/freeipa/facet.js +++ b/install/ui/src/freeipa/facet.js @@ -21,8 +21,80 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -define(['./ipa', './jquery', './dialog', './field', './widget'], function(IPA, $) { - +define([ + 'dojo/_base/declare', + 'dojo/_base/lang', + 'dojo/topic', + 'dojo/dom-construct', + 'dojo/on', + 'dojo/Stateful', + 'dojo/Evented', + './ipa', + './jquery', + './navigation', + './dialog', + './field', + './widget' + ], function(declare, lang, topic, construct, on, Stateful, Evented, + IPA, $, navigation) { + +/** + * Facet represents the content of currently displayed page. + * + * = Show, Clear, Refresh mechanism = + * + * Use cases: + * a) Display facet with defined arguments. + * b) Switch to facet + * c) Update facet state + * + * == Display facet by route == + * 1) somebody sets route + * 2) Route is evaluated, arguments extracted. + * 3) Facet state is updated `update_state(args, pkeys)`.(saves previous state) + * 4) Facet show() is called + * + * == Display facet with defined arguments == + * 1) Somebody calls navigation.show(xxx); + * 2) Facet state is updated `update_state(args, pkeys)`.(saves previous state) + * 3) Route is updated, but the hash change is ignored + * 4) Facet show() is called. + * 5.1) First time show + * a) creates DOM + * b) display DOM + * c) refresh(); + * 5.2) Next time + * a) display DOM + * b) needs_update()? (compares previous state with current) + * true: + * 1) clear() - each facet can override to supress clear or + * control the behaviour + * 2) refresh() + * + * == Swith to facet == + * Same as display facet but only without arguments. Arguments are extracted at + * step 2. + * + * == Update facet state == + * 1) update_state(args, pkeys?) + * 2) needs_update()? + * true: + * a) clear() + * b) refresh() + * 2) Update route, ignore hash change event + * + * == Updating hash == + * Hash updates are responsibility of navigation component and application + * controller. Application controller should listen to facet's `state_change` + * event. And call something like navigation.update_hash(facet). + * + * navigation.update_hash should find all the necessary state properties (args, + * pkeys). + * + * == needs_update method == + * + * + */ IPA.facet = function(spec, no_init) { spec = spec || {}; @@ -58,12 +130,190 @@ IPA.facet = function(spec, no_init) { that.dialogs = $.ordered_map(); + /** + * domNode of container + * Suppose to contain domNode of this and other facets. + */ + that.container_node = spec.container_node; + + /** + * FIXME: that.container should be eliminated + * now it's the same as domNode + */ + //that.container + + /** + * domNode which contains all content of a facet. + * Should contain error content and content. When error is moved to + * standalone facet it will replace functionality of content. + */ + that.domNode = null; + // facet group name that.facet_group = spec.facet_group; that.redirect_info = spec.redirect_info; - that.state = {}; + /** + * Public state + * + */ + that.state = new FacetState(); + + that.set_pkeys = function(pkeys) { + + pkeys = that.get_pkeys(pkeys); + that.state.set('pkeys', pkeys); + }; + + /** + * Return THE pkey of this facet. Basically the last one of pkeys list. + * + * @type String + */ + that.get_pkey = function() { + var pkeys = that.state.get('pkeys'); + if (pkeys.length) { + return pkeys[pkeys.length-1]; + } + return ''; + }; + + /** + * Gets copy of pkeys list. + * It automatically adds empty pkeys ('') for each containing entity if not + * specified. + * + * One can get merge current pkeys with supplied if `pkeys` param is + * specified. + * + * @param String[] new pkeys to merge + */ + that.get_pkeys = function(pkeys) { + var new_keys = []; + var cur_keys = that.state.get('pkeys') || []; + var current_entity = that.entity; + pkeys = pkeys || []; + var arg_l = pkeys.length; + var cur_l = cur_keys.length; + var tot_c = 0; + while (current_entity) { + current_entity = current_entity.get_containing_entity(); + tot_c++; + } + + if (tot_c < arg_l || tot_c < cur_l) throw { + error: 'Invalid pkeys count. Supplied more than expected.' + }; + + var arg_off = tot_c - arg_l; + var cur_off = cur_l - tot_c; + + for (var i=0; i<tot_c; i++) { + // first try to use supplied + if (tot_c - arg_l - i <= 0) new_keys[i] = pkeys[i-arg_off]; + // then current + else if (tot_c - cur_l - i <= 0) new_keys[i] = cur_keys[i-cur_off]; + // then empty + else new_keys[i] = ''; + } + + return new_keys; + }; + + that.state_diff = function(a, b) { + var diff = false; + var checked = {}; + + var check_diff = function(a, b, skip) { + + var same = true; + skip = skip || {}; + + for (var key in a) { + if (a.hasOwnProperty(key) && !(key in skip)) { + var va = a[key]; + var vb = b[key]; + if ((lang.isArray(va) && !IPA.array_diff(va,vb)) || + (a[key] !== b[key])) { + same = false; + skip[a] = true; + break; + } + } + } + return !same; + }; + + diff = check_diff(a,b, checked); + diff = diff || check_diff(b,a, checked); + return diff; + }; + + that.set_state = function(state) { + + if (state.pkeys) { + state.pkeys = that.get_pkeys(state.pkeys); + } + that.state.reset(state); + }; + + that.get_state = function() { + return that.state.clone(); + }; + + /** + * Merges state into current and notifies it. + */ + that.update_state = function(state) { + + if (state.pkeys) { + state.pkeys = that.get_pkeys(state.pkeys); + } + that.state.set(state); + }; + + that.on_state_reset = function(old_state, state) { + that._on_state_change(state); + }; + + that.on_state_change = function(name, old_value, value) { + var new_state = that.state.clone(); + that._on_state_change(new_state); + }; + + that._on_state_change = function(state) { + + // basically a show method without displaying the facet + // TODO: change to something fine grained + + // we don't have to reflect any changes if facet dom is not yet built + + that._notify_state_change(state); + + if (!that.domNode) return; + + var needs_update = that.needs_update(); + that.old_state = state; + + if (needs_update) { + that.clear(); + } + + that.show_content(); + that.header.select_tab(); + + if (needs_update) { + that.refresh(); + } + }; + + that._notify_state_change = function(state) { + topic.publish('facet-state-change', { + facet: that, + state:state + }); + }; that.get_dialog = function(name) { return that.dialogs.get(name); @@ -74,27 +324,47 @@ IPA.facet = function(spec, no_init) { return that; }; - that.create = function(container) { + that.create = function() { - that.container = container; + if (that.domNode) { + that.domNode.empty(); + that.domNode.detach(); + } else { + that.domNode = $('<div/>', { + 'class': 'facet', + name: that.name + }); + } + + var domNode = that.domNode; + that.container = domNode; + + if (!that.container_node) throw { + error: 'Can\'t create facet. No container node defined.' + }; + var node = domNode[0]; + construct.place(node,that.container_node); - if (that.disable_facet_tabs) that.container.addClass('no-facet-tabs'); - that.container.addClass(that.display_class); + + if (that.disable_facet_tabs) domNode.addClass('no-facet-tabs'); + domNode.addClass(that.display_class); that.header_container = $('<div/>', { 'class': 'facet-header' - }).appendTo(container); + }).appendTo(domNode); that.create_header(that.header_container); that.content = $('<div/>', { 'class': 'facet-content' - }).appendTo(container); + }).appendTo(domNode); that.error_container = $('<div/>', { 'class': 'facet-content facet-error' - }).appendTo(container); + }).appendTo(domNode); that.create_content(that.content); + + }; that.create_header = function(container) { @@ -122,8 +392,30 @@ IPA.facet = function(spec, no_init) { }; that.show = function() { - that.container.css('display', 'block'); - that.show_content(); + + that.entity.facet = that; // FIXME: remove + + if (!that.domNode) { + that.create(); + + var needs_update = that.needs_update(); + + if (needs_update) { + that.clear(); + } + + that.domNode.css('display', 'block'); + that.show_content(); + that.header.select_tab(); + + if (needs_update) { + that.refresh(); + } + } else { + that.domNode.css('display', 'block'); + that.show_content(); + that.header.select_tab(); + } }; that.show_content = function() { @@ -142,7 +434,7 @@ IPA.facet = function(spec, no_init) { }; that.hide = function() { - that.container.css('display', 'none'); + that.domNode.css('display', 'none'); }; that.load = function(data) { @@ -175,6 +467,8 @@ IPA.facet = function(spec, no_init) { needs_update = needs_update || that.expired_flag; needs_update = needs_update || that.error_displayed(); + needs_update = needs_update || that.state_diff(that.old_state || {}, that.state); + return needs_update; }; @@ -191,6 +485,29 @@ IPA.facet = function(spec, no_init) { return false; }; + /** + * Wheater we can switch to different facet. + * @returns Boolean + */ + that.can_leave = function() { + return !that.is_dirty(); + }; + + /** + * Show dialog displaying a message explaining why we can't switch facet. + * User can supply callback which is called when a leave is permitted. + * + * Listeneres should set 'callback' property to listen state evaluation. + */ + that.show_leave_dialog = function(permit_callback) { + + var dialog = IPA.dirty_dialog({ + facet: that + }); + + return dialog; + }; + that.report_error = function(error_thrown) { var add_option = function(ul, text, handler) { @@ -235,7 +552,7 @@ IPA.facet = function(spec, no_init) { options_list, IPA.messages.error_report.main_page, function() { - IPA.nav.show_top_level_page(); + navigation.show_default(); } ); @@ -260,16 +577,11 @@ IPA.facet = function(spec, no_init) { } var facet_name = that.entity.redirect_facet; var entity_name = entity.name; - var tab_name, facet; + var facet; if (that.redirect_info) { entity_name = that.redirect_info.entity || entity_name; facet_name = that.redirect_info.facet || facet_name; - tab_name = that.redirect_info.tab; - } - - if (tab_name) { - facet = IPA.nav.get_tab_facet(tab_name); } if (!facet) { @@ -284,7 +596,7 @@ IPA.facet = function(spec, no_init) { var facet = that.get_redirect_facet(); if (!facet) return; - IPA.nav.show_page(facet.entity.name, facet.name); + navigation.show(facet); }; var redirect_error_codes = [4001]; @@ -306,6 +618,8 @@ IPA.facet = function(spec, no_init) { that.action_state.init(that); that.actions.init(that); that.header.init(); + on(that.state, 'reset', that.on_state_reset); + that.state.watch(that.on_state_change); var buttons_spec = { factory: IPA.control_buttons_widget, @@ -369,7 +683,7 @@ IPA.facet_header = function(spec) { if (that.facet.disable_facet_tabs) return; $(that.facet_tabs).find('a').removeClass('selected'); - var facet_name = IPA.nav.get_state(that.facet.entity.name+'-facet'); + var facet_name = that.facet.name; if (!facet_name || facet_name === 'default') { that.facet_tabs.find('a:first').addClass('selected'); @@ -388,26 +702,29 @@ IPA.facet_header = function(spec) { if (!that.facet.disable_breadcrumb) { var breadcrumb = []; - var keys = []; + + // all pkeys should be available in facet + var keys = that.facet.get_pkeys(); var entity = that.facet.entity.get_containing_entity(); + i = keys.length - 2; //set pointer to first containing entity while (entity) { - key = IPA.nav.get_state(entity.name+'-pkey'); + key = keys[i]; breadcrumb.unshift($('<a/>', { 'class': 'breadcrumb-element', text: key, title: entity.metadata.label_singular, click: function(entity) { return function() { - IPA.nav.show_page(entity.name, 'default'); + navigation.show_entity(entity.name, 'default'); return false; }; }(entity) })); entity = entity.get_containing_entity(); - keys.unshift(key); + i--; } //calculation of breadcrumb keys length @@ -471,8 +788,8 @@ IPA.facet_header = function(spec) { return false; } - var pkey = IPA.nav.get_state(that.facet.entity.name+'-pkey'); - IPA.nav.show_page(that.facet.entity.name, other_facet.name, pkey); + var pkeys = that.facet.get_pkeys(); + navigation.show(other_facet, [pkeys]); return false; } @@ -762,6 +1079,7 @@ IPA.table_facet = function(spec, no_init) { that.add_column = function(column) { column.entity = that.managed_entity; + column.facet = that; that.columns.put(column.name, column); }; @@ -865,15 +1183,12 @@ IPA.table_facet = function(spec, no_init) { delete that.table.current_page; - var state = {}; - var page = parseInt(IPA.nav.get_state(that.entity.name+'-page'), 10) || 1; + var page = parseInt(that.state.page, 10) || 1; if (page < 1) { - state[that.entity.name+'-page'] = 1; - IPA.nav.push_state(state); + that.state.set({page: 1}); return; } else if (page > that.table.total_pages) { - state[that.entity.name+'-page'] = that.table.total_pages; - IPA.nav.push_state(state); + that.state.set({page: that.table.total_pages}); return; } that.table.current_page = page; @@ -1027,7 +1342,16 @@ IPA.table_facet = function(spec, no_init) { if (column.link && column.primary_key) { column.link_handler = function(value) { - IPA.nav.show_page(entity.name, that.details_facet_name, value); + var pkeys = [value]; + + // for nested entities + var containing_entity = entity.get_containing_entity(); + if (containing_entity && that.entity.name === containing_entity.name) { + pkeys = that.get_pkeys(); + pkeys.push(value); + } + + navigation.show_entity(entity.name, that.details_facet_name, pkeys); return false; }; } @@ -1042,19 +1366,17 @@ IPA.table_facet = function(spec, no_init) { that.table.prev_page = function() { if (that.table.current_page > 1) { - var state = {}; - state[that.entity.name+'-page'] = that.table.current_page - 1; + var page = that.table.current_page - 1; that.set_expired_flag(); - IPA.nav.push_state(state); + that.state.set({page: page}); } }; that.table.next_page = function() { if (that.table.current_page < that.table.total_pages) { - var state = {}; - state[that.entity.name+'-page'] = that.table.current_page + 1; + var page = that.table.current_page + 1; that.set_expired_flag(); - IPA.nav.push_state(state); + that.state.set({page: page}); } }; @@ -1064,10 +1386,8 @@ IPA.table_facet = function(spec, no_init) { } else if (page > that.total_pages) { page = that.total_pages; } - var state = {}; - state[that.entity.name+'-page'] = page; that.set_expired_flag(); - IPA.nav.push_state(state); + that.state.set({page: page}); }; }; @@ -1657,9 +1977,7 @@ IPA.self_service_state_evaluator = function(spec) { var old_state = that.state; that.state = []; - var self_service = IPA.nav.name === 'self-service'; - - if (self_service) { + if (IPA.is_selfservice) { that.state.push('self-service'); } @@ -2042,5 +2360,55 @@ IPA.action_list_widget = function(spec) { return that; }; -return {}; +var FacetState = declare([Stateful, Evented], { + + /** + * Properties to ignore in clear and clone operation + */ + _ignore_properties: {_watchCallbacks:1, onreset:1}, + + /** + * Gets object containing shallow copy of state's properties. + */ + clone: function() { + var clone = {}; + for(var x in this){ + if (this.hasOwnProperty(x) && !(x in this._ignore_properties)) { + clone[x] = lang.clone(this[x]); + } + } + return clone; + }, + + /** + * Unset all properties. + */ + clear: function() { + var undefined; + for(var x in this){ + if (this.hasOwnProperty(x) && !(x in this._ignore_properties)) { + this.set(x, undefined); + } + } + }, + + /** + * Set completly new state. Old state is cleared. + * + * Supresses all watch callbacks. + * Others can be notified by listening to 'reset' event. + */ + reset: function(object) { + var _watchCallbacks = this._watchCallbacks; + delete this._watchCallbacks; //prevent watch callbacks + var old_state = this.clone(); + this.clear(); + this.set(object); + var new_state = this.clone(); + this._watchCallbacks = _watchCallbacks; + this.emit('reset', new_state, old_state); + } +}); + +return { FacetState: FacetState}; }); |