/* Authors: * Pavel Zuna * Adam Young * Endi S. Dewata * Petr Vobornik * * Copyright (C) 2010 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/lang', './builder', './ipa', './jquery', './phases', './reg', './rpc', './spec_util', './text', './facet', './add'], function(lang, builder, IPA, $, phases, reg, rpc, su, text) { /** * Details module * * @class details * @singleton */ var exp = {}; /** * CSS classes for defining basic layout of sections in details facets * @type {String} */ exp.details_section_layout_class = 'col-sm-12 col-md-6'; /** * Details builder * * Processes containers spec and builds sections, widget and fields according * to that spec. Container is usually a details facet or a dialog. For its task * it uses `section_builder`, `widget_builder` and `field_builder` each * builder can be configured. Otherwise it uses default builders. * * @class details.details_builder * @alternateClassName IPA.details_builder */ exp.details_builder = IPA.details_builder = function(spec) { var that = IPA.object(); /** * Container's widget collection * @protected */ that.widgets = spec.container.widgets; /** * Container's field collection * @protected */ that.fields = spec.container.fields; /** * Widget builder * @property {IPA.widget_builder} */ that.widget_builder = spec.widget_builder || IPA.widget_builder(); /** * Fields builder * @property {IPA.field_builder} */ that.field_builder = spec.field_builder || IPA.field_builder(); /** * Section builder * @property {details.section_builder} */ that.section_builder = spec.section_builder || IPA.section_builder(); /** * Build single widget according to its spec and add it to widget collection. * @param {Object} spec widget spec */ that.build_widget = function(spec) { if (!spec) return; that.widget_builder.build_widget(spec, that.widgets); }; /** * Build multiple widgets and add them to widget collection. * @param {Array.} specs widget specs */ that.build_widgets = function(specs) { if (!specs) return; that.widget_builder.build_widgets(specs, that.widgets); }; /** * Build single field and add it to field collection. * @param {Object} spec field spec */ that.build_field = function(spec) { if (!spec) return; that.field_builder.build_field(spec, that.fields); }; /** * Build multiple fields and add them to field collection. * @param {Array.} specs field spec */ that.build_fields = function(specs) { if (!specs) return; that.field_builder.build_fields(specs, that.fields); }; /** * Build sections * @param {Array.} specs section specs */ that.build_sections = function(specs) { if (!specs) return; that.section_builder.build_sections(specs); }; /** * Build section, fields and widgets. * @param {Object} spec facet or dialog spec */ that.build = function(spec) { if (spec.sections) { that.build_sections(spec.sections); } else if (spec.fields && !spec.widgets) { var sections = [ { fields: spec.fields } ]; that.build_sections(sections); } else { that.build_fields(spec.fields); that.build_widgets(spec.widgets); } }; return that; }; /** * Section builder * * Section is a layout unit of details facet or a dialog. * * @class details.section_builder * @alternateClassName IPA.section_builder */ exp.section_builder = IPA.section_builder = function(spec) { spec = spec || {}; var that = IPA.object(); /** * Container * @property {IPA.facet|IPA.dialog} */ that.container = spec.container; /** * Default section factory * * @property {IPA.composite_widget|Object} */ that.section_spec = spec.section_spec || IPA.details_section; /** * Field builder * @property {IPA.field_builder} */ that.field_builder = spec.field_builder; /** * Widget builder * @property {IPA.widget_builder} */ that.widget_builder = spec.widget_builder; /** * Build multiple sections * @param {Array.} sections section specs */ that.build_sections = function(sections) { if(!sections) return; for (var i=0; i < sections.length; i++) { that.build_section(sections[i], i); } }; /** * Build single section * @param {Object} section_spec * @param {number|string} index value which is used in section name if name * is not specified in spec */ that.build_section = function(section_spec, index) { var spec = {}; var overrides = {}; var spec_type = typeof that.section_spec; if (spec_type === 'object') { spec = lang.mixin({}, that.section_spec); } else if (spec_type === "function") { overrides = that.section_spec; } spec = lang.mixin(spec, section_spec); if (!spec.label && spec.name && that.container.entity) { var section_label = '@i18n:objects.'+that.container.entity.name+ '.' + spec.name; spec.label = section_label; } if (!spec.name) spec.name = 'section'+index; var section = builder.build('widget', spec, { entity: that.container.entity, facet: that.container }, overrides); that.container.widgets.add_widget(section); section.$field_adapter = spec.field_adapter; that.create_fields(section, spec.fields); delete section.$field_adapter; }; /** * Create fields and associated widgets * @param {IPA.composite_widget} section * @param {Array.} field_specs */ that.create_fields = function(section, fields_specs) { for (var i=0; i < fields_specs.length; i++) { that.create_field(section, fields_specs[i]); } }; /** * Create field and associated widget * @param {IPA.composite_widget} section * @param {Object} field_spec */ that.create_field = function(section, field_spec) { if (typeof field_spec === 'string') { field_spec = { name: field_spec }; } var widget = that.widget_builder.build_widget(field_spec, section.widgets); if (section.$field_adapter && !field_spec.adapter) { field_spec.adapter = section.$field_adapter; } //spec.$factory refers to widget factory if(field_spec.$factory) delete field_spec.$factory; var field = that.field_builder.build_field(field_spec, that.container.fields); if(widget && field) { field.widget_name = section.name+'.'+widget.name; } }; return that; }; /** * Facet policy * * Object which extends container's (facet or dialog) logic. * * @class details.facet_policy * @alternateClassName IPA.facet_policy */ exp.facet_policy = IPA.facet_policy = function() { var that = IPA.object(); /** * Container * @property {IPA.facet|IPA.dialog} */ that.container = null; /** * Init handler. * * Should be executed in container's init. */ that.init = function() { }; /** * Post Create Handler * * Should be executed at the end of container's create */ that.post_create = function() { }; /** * Post Load Handler * * Should be executed at the end of container's load * @param {Object} data */ that.post_load = function(data) { }; return that; }; /** * Facet policy collection * * @class details.facet_policies * @alternateClassName IPA.facet_policies */ exp.facet_policies = IPA.facet_policies = function(spec) { var that = IPA.object(); /** * Container * @property {IPA.facet|IPA.dialog} */ that.container = spec.container; /** * Facet Policies * @readonly * @property {Array.} */ that.policies = []; /** * Add policy * @param {details.facet_policy} policy */ that.add_policy = function(policy) { policy.container = that.container; that.policies.push(policy); }; /** * Add multiple policies * @param {Array.} policies */ that.add_policies = function(policies) { if (!policies) return; for (var i=0; i', { 'class': 'details-content row' }).appendTo(container); that.widgets.create(that.content); }; /** * @inheritDoc */ that.show = function() { that.facet_show(); var pkey = that.get_pkey(); that.header.set_pkey(pkey); }; /** * Field's dirty event handler * * Sets this dirty state * * @protected * @param {boolean} dirty */ that.field_dirty_changed = function(dirty) { var old_dirty = that.dirty; if (dirty) { that.dirty = true; } else { that.dirty = that.is_dirty(); } if (old_dirty !== that.dirty) { that.dirty_changed.notify([that.dirty]); } }; /** * Evaluates if facet is dirty. * * Facet is dirty if any child widget is dirty. * @return {boolean} dirty */ that.is_dirty = function() { var fields = that.fields.get_fields(); for (var i=0; i -1) continue; var values = field_info.field.save(); exp.command_builder.add_field_option( command, field_info.field, values); } }; /** * Create update command based on field part of update info * @protected * @param {details.update_info} update_info * @return {rpc.command} */ that.create_fields_update_command = function(update_info) { var args = that.get_pkeys(); var options = { all: true }; if (that.check_rights) options.rights = true; var command = rpc.command({ entity: that.entity.name, method: that.update_command_name, args: args, options: options }); //set command options that.add_fields_to_command(update_info, command); return command; }; /** * Create batch command from update info * * Created batch command consists of update info's commands and a mod command * to reflect field part of update info (if present). * @protected * @param {details.update_info} update_info * @return {rpc.batch_command} */ that.create_batch_update_command = function(update_info) { var batch = rpc.batch_command({ name: that.entity.name + '_details_update' }); var new_update_info = IPA.update_info_builder.copy(update_info); if (update_info.fields.length > 0) { var command = that.create_fields_update_command(update_info); new_update_info.append_command(command, IPA.config.default_priority); } new_update_info.commands.sort(function(a, b) { return a.priority - b.priority; }); for (var i=0; i < new_update_info.commands.length; i++) { batch.add_command(new_update_info.commands[i].command); } return batch; }; /** * Show validation error * @protected */ that.show_validation_error = function() { var dialog = IPA.message_dialog({ name: 'validation_error', title: '@i18n:dialogs.validation_title', message: '@i18n:dialogs.validation_message' }); dialog.open(); }; /** * Create update command * @protected * @return {rpc.command|rpc.batch_command} */ that.create_update_command = function() { var command, update_info; if (that.command_mode === 'info') { update_info = that.get_update_info(); } else { update_info = that.save_as_update_info(true, true); } if (update_info.commands.length <= 0) { //normal command command = that.create_fields_update_command(update_info); } else { //batch command command = that.create_batch_update_command(update_info); } return command; }; /** * Perform update operation * * Update reflects current state into data store. * * @param {Function} on_success success handler * @param {Function} on_error error handler */ that.update = function(on_success, on_error) { var command = that.create_update_command(); command.on_success = function(data, text_status, xhr) { that.update_on_success(data, text_status, xhr); if (on_success) on_success.call(this, data, text_status, xhr); }; command.on_error = function(xhr, text_status, error_thrown) { that.update_on_error(xhr, text_status, error_thrown); if (on_error) on_error.call(this, xhr, text_status, error_thrown); }; command.execute(); }; /** * Get refresh command name * @protected * @return {string} */ that.get_refresh_command_name = function() { return that.entity.name+'_show'; }; /** * Create refresh command * @protected * @return {rpc.command} */ that.create_refresh_command = function() { var options = { all: true }; if (that.check_rights) options.rights = true; var command = rpc.command({ name: that.get_refresh_command_name(), entity: that.entity.name, method: 'show', options: options }); if (that.get_pkey()) { command.args = that.get_pkeys(); } return command; }; /** * Refresh success handler * @protected * @param {Object} data * @param {string} text_status * @param {XMLHttpRequest} xhr */ that.refresh_on_success = function(data, text_status, xhr) { that.load(data); that.show_content(); }; /** * Refresh error handler * @protected * @param {XMLHttpRequest} xhr * @param {string} text_status * @param {Object} error_thrown */ that.refresh_on_error = function(xhr, text_status, error_thrown) { that.redirect_error(error_thrown); that.report_error(error_thrown); }; /** * @inheritDoc */ that.refresh = function(on_success, on_error) { if (!that.get_pkey() && that.entity.redirect_facet) { that.redirect(); return; } var command = that.create_refresh_command(); command.on_success = function(data, text_status, xhr) { that.refresh_on_success(data, text_status, xhr); if (on_success) on_success.call(this, data, text_status, xhr); }; command.on_error = function(xhr, text_status, error_thrown) { that.refresh_on_error(xhr, text_status, error_thrown); if (on_error) on_error.call(this, xhr, text_status, error_thrown); }; command.execute(); }; /** * @inheritDoc */ that.clear = function() { that.header.clear(); that.widgets.clear(); }; /** * Create update info * * - used in `update_info` command mode * @protected * @return {details.update_info} */ that.get_update_info = function() { var update_info = IPA.update_info_builder.new_update_info(); var fields = that.fields.get_fields(); for (var i = 0; i < fields.length; i++) { var field = fields[i]; if (field.get_update_info) { var ui = field.get_update_info(); update_info = IPA.update_info_builder.merge(update_info, ui); } } return update_info; }; /** * Create builders needed for initialization * @protected */ that.create_builder = function() { var widget_builder = IPA.widget_builder({ widget_options: { entity: that.entity, facet: that } }); var field_builder = IPA.field_builder({ field_options: { entity: that.entity, facet: that } }); var section_builder = IPA.section_builder({ container: that, widget_builder: widget_builder, field_builder: field_builder, section_spec: { $factory: IPA.details_section, layout_css_class: that.section_layout_class } }); that.builder = IPA.details_builder({ container: that, widget_builder: widget_builder, field_builder: field_builder, section_builder: section_builder }); }; /** * Initialize details facet * * - called automatically if `no_init==true` is not present * * @protected */ that.init_details_facet = function() { that.init_facet(); that.create_builder(); that.builder.build(spec); that.fields.widgets_created(); that.policies.init(); }; if (!no_init) that.init_details_facet(); // methods that should be invoked by subclasses that.details_facet_create_update_command = that.create_update_command; that.details_facet_create_refresh_command = that.create_refresh_command; that.details_facet_refresh_on_success = that.refresh_on_success; that.details_facet_load = that.load; return that; }; /** * Update info * @class details.update_info * @alternateClassName IPA.update_info */ exp.update_info = IPA.update_info = function(spec) { var that = IPA.object(); /** * Fields update info * @property {Array.} */ that.fields = spec.fields || []; /** * Update commands info * @property {Array.} */ that.commands = spec.commands || []; /** * Create new field info and add it to collection * @param {IPA.field} field * @param {Object} value field's value */ that.append_field = function(field, value) { var field_info = IPA.update_info_builder.new_field_info(field, value); that.fields.push(field_info); }; /** * Create new command info and add it to collection * @param {rpc.command} command * @param {number} priority */ that.append_command = function (command, priority) { var command_info = IPA.update_info_builder.new_command_info(command, priority); that.commands.push(command_info); }; return that; }; /** * Command info * @class details.command_info * @alternateClassName IPA.command_info */ exp.command_info = IPA.command_info = function(spec) { var that = IPA.object(); /** * Command * @property {rpc.command} */ that.command = spec.command; /** * Priority * * - controls command execution order * @property {number} */ that.priority = spec.priority || IPA.config.default_priority; return that; }; /** * Field update info * @class details.field_info * @alternateClassName IPA.field_info */ exp.field_info = IPA.field_info = function(spec) { var that = IPA.object(); /** * Field * @property {IPA.field} */ that.field = spec.field; /** * Value * @property {Object} */ that.value = spec.value; return that; }; /** * Update info builder * @class details.update_info_builder * @alternateClassName IPA.update_info_builder * @singleton */ exp.update_info_builder = IPA.update_info_builder = function() { var that = IPA.object(); /** * Create update info from field and command infos * @param {Array.} fields * @param {Array.} commands * @return {details.update_info} */ that.new_update_info = function (fields, commands) { return IPA.update_info({ fields: fields, commands: commands }); }; /** * Create field info * @param {details.field_info} field * @param {Object} value * @return {details.field_info} */ that.new_field_info = function(field, value) { return IPA.field_info({ field: field, value: value }); }; /** * Create new command info * @param {rpc.command} command * @param {number} priority * @return {details.command_info} */ that.new_command_info = function(command, priority) { return IPA.command_info({ command: command, priority: priority }); }; /** * Merge two commands info into new one * @param {details.command_info} a * @param {details.command_info} b * @return {details.command_info} */ that.merge = function(a, b) { return that.new_update_info( a.fields.concat(b.fields), a.commands.concat(b.commands)); }; /** * Create copy of command info * @param {details.command_info} original * @return {details.command_info} copy */ that.copy = function(original) { return that.new_update_info( original.fields.concat([]), original.commands.concat([])); }; return that; }(); /** * Field add/mod command builder * * @class details.command_builder * @singleton */ exp.command_builder = function() { var that = IPA.object(); /** * Add option to command with field values * @param {rpc.command} command * @param {IPA.field} field * @param {Array} values */ that.add_field_option = function(command, field, values) { if (!field || !values) return; var name = field.param; if (field.metadata) { if (field.metadata.primary_key) return; if (values.length === 1) { command.set_option(name, values[0]); } else { command.set_option(name, values); } } else { if (values.length) { command.add_option('setattr', name+'='+values[0]); } else { command.add_option('setattr', name+'='); } for (var k=1; k