/* Authors: * Pavel Zuna * Endi Sukma Dewata * Adam Young * Petr Vobornik * * Copyright (C) 2010-2011 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 . */ 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 || {}; spec.state = spec.state || {}; $.extend(spec.state, { factory: IPA.state }); var that = {}; that.entity = IPA.get_entity(spec.entity); that.name = spec.name; that.label = spec.label; that.title = spec.title || that.label; that.tab_label = spec.tab_label || that.label; that.display_class = spec.display_class; that.no_update = spec.no_update; that.disable_breadcrumb = spec.disable_breadcrumb; that.disable_facet_tabs = spec.disable_facet_tabs; that.action_state = IPA.build(spec.state); that.actions = IPA.build({ actions: spec.actions }, IPA.action_holder_builder); that.header_actions = spec.header_actions; that.header = spec.header || IPA.facet_header({ facet: that }); that._needs_update = spec.needs_update; that.expired_flag = true; that.last_updated = null; that.expire_timeout = spec.expire_timeout || 600; //[seconds] that.on_update = IPA.observer(); that.post_load = IPA.observer(); 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; /** * 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', { '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) domNode.addClass('no-facet-tabs'); domNode.addClass(that.display_class); that.header_container = $('
', { 'class': 'facet-header' }).appendTo(domNode); that.create_header(that.header_container); that.content = $('
', { 'class': 'facet-content' }).appendTo(domNode); that.error_container = $('
', { 'class': 'facet-content facet-error' }).appendTo(domNode); that.create_content(that.content); }; that.create_header = function(container) { that.header.create(container); that.controls = $('
', { 'class': 'facet-controls' }).appendTo(container); }; that.create_content = function(container) { }; that.create_control_buttons = function(container) { if (that.control_buttons) { that.control_buttons.create(container); } }; that.set_title = function(container, title) { var element = $('h1', that.title_container); element.html(title); }; that.show = function() { 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() { that.content.css('display', 'block'); that.error_container.css('display', 'none'); }; that.show_error = function() { that.content.css('display', 'none'); that.error_container.css('display', 'block'); }; that.error_displayed = function() { return that.error_container && that.error_container.css('display') === 'block'; }; that.hide = function() { that.domNode.css('display', 'none'); }; that.load = function(data) { that.data = data; that.header.load(data); }; that.refresh = function() { }; that.clear = function() { }; that.needs_update = function() { if (that._needs_update !== undefined) return that._needs_update; var needs_update = false; if (that.expire_timeout && that.expire_timeout > 0) { if (!that.last_updated) { needs_update = true; } else { var now = Date.now(); needs_update = (now - that.last_updated) > that.expire_timeout * 1000; } } 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; }; that.set_expired_flag = function() { that.expired_flag = true; }; that.clear_expired_flag = function() { that.expired_flag = false; that.last_updated = Date.now(); }; that.is_dirty = function() { 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) { var li = $('
  • ').appendTo(ul); $('', { href: '#', text: text, click: function() { handler(); return false; } }).appendTo(li); }; var title = IPA.messages.error_report.title; title = title.replace('${error}', error_thrown.name); that.error_container.empty(); that.error_container.append('

    '+title+'

    '); var details = $('
    ', { 'class': 'error-details' }).appendTo(that.error_container); details.append('

    '+error_thrown.message+'

    '); $('
    ', { text: IPA.messages.error_report.options }).appendTo(that.error_container); var options_list = $('