(function() { var instance = openerp; var _t = instance.web._t, _lt = instance.web._lt; var QWeb = instance.web.qweb; /** @namespace */ instance.web.form = {}; /** * Interface implemented by the form view or any other object * able to provide the features necessary for the fields to work. * * Properties: * - display_invalid_fields : if true, all fields where is_valid() return true should * be displayed as invalid. * - actual_mode : the current mode of the field manager. Can be "view", "edit" or "create". * Events: * - view_content_has_changed : when the values of the fields have changed. When * this event is triggered all fields should reprocess their modifiers. * - field_changed: : when the value of a field change, an event is triggered * named "field_changed:" with replaced by the name of the field. * This event is not related to the on_change mechanism of OpenERP and is always called * when the value of a field is setted or changed. This event is only triggered when the * value of the field is syntactically valid, but it can be triggered when the value * is sematically invalid (ie, when a required field is false). It is possible that an event * about a precise field is never triggered even if that field exists in the view, in that * case the value of the field is assumed to be false. */ instance.web.form.FieldManagerMixin = { /** * Must return the asked field as in fields_get. */ get_field_desc: function(field_name) {}, /** * Returns the current value of a field present in the view. See the get_value() method * method in FieldInterface for further information. */ get_field_value: function(field_name) {}, /** Gives new values for the fields contained in the view. The new values could not be setted right after the call to this method. Setting new values can trigger on_changes. @param {Object} values A dictonary with key = field name and value = new value. @return {$.Deferred} Is resolved after all the values are setted. */ set_values: function(values) {}, /** Computes an OpenERP domain. @param {Array} expression An OpenERP domain. @return {boolean} The computed value of the domain. */ compute_domain: function(expression) {}, /** Builds an evaluation context for the resolution of the fields' contexts. Please note the field are only supposed to use this context to evualuate their own, they should not extend it. @return {CompoundContext} An OpenERP context. */ build_eval_context: function() {}, }; instance.web.views.add('form', 'instance.web.FormView'); /** * Properties: * - actual_mode: always "view", "edit" or "create". Read-only property. Determines * the mode used by the view. */ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerMixin, { /** * Indicates that this view is not searchable, and thus that no search * view should be displayed (if there is one active). */ searchable: false, template: "FormView", display_name: _lt('Form'), view_type: "form", /** * @constructs instance.web.FormView * @extends instance.web.View * * @param {instance.web.Session} session the current openerp session * @param {instance.web.DataSet} dataset the dataset this view will work with * @param {String} view_id the identifier of the OpenERP view object * @param {Object} options * - resize_textareas : [true|false|max_height] * * @property {instance.web.Registry} registry=instance.web.form.widgets widgets registry for this form view instance */ init: function(parent, dataset, view_id, options) { var self = this; this._super(parent); this.ViewManager = parent; this.set_default_options(options); this.dataset = dataset; this.model = dataset.model; this.view_id = view_id || false; this.fields_view = {}; this.fields = {}; this.fields_order = []; this.datarecord = {}; this._onchange_specs = {}; this.onchanges_mutex = new $.Mutex(); this.default_focus_field = null; this.default_focus_button = null; this.fields_registry = instance.web.form.widgets; this.tags_registry = instance.web.form.tags; this.widgets_registry = instance.web.form.custom_widgets; this.has_been_loaded = $.Deferred(); this.translatable_fields = []; _.defaults(this.options, { "not_interactible_on_create": false, "initial_mode": "view", "disable_autofocus": false, "footer_to_buttons": false, }); this.is_initialized = $.Deferred(); this.mutating_mutex = new $.Mutex(); this.save_list = []; this.render_value_defs = []; this.reload_mutex = new $.Mutex(); this.__clicked_inside = false; this.__blur_timeout = null; this.rendering_engine = new instance.web.form.FormRenderingEngine(this); self.set({actual_mode: self.options.initial_mode}); this.has_been_loaded.done(function() { self._build_onchange_specs(); self.on("change:actual_mode", self, self.check_actual_mode); self.check_actual_mode(); self.on("change:actual_mode", self, self.init_pager); self.init_pager(); }); self.on("load_record", self, self.load_record); instance.web.bus.on('clear_uncommitted_changes', this, function(e) { if (!this.can_be_discarded()) { e.preventDefault(); } }); }, view_loading: function(r) { return this.load_form(r); }, destroy: function() { _.each(this.get_widgets(), function(w) { w.off('focused blurred'); w.destroy(); }); if (this.$el) { this.$el.off('.formBlur'); } this._super(); }, load_form: function(data) { var self = this; if (!data) { throw new Error(_t("No data provided.")); } if (this.arch) { throw "Form view does not support multiple calls to load_form"; } this.fields_order = []; this.fields_view = data; this.rendering_engine.set_fields_registry(this.fields_registry); this.rendering_engine.set_tags_registry(this.tags_registry); this.rendering_engine.set_widgets_registry(this.widgets_registry); this.rendering_engine.set_fields_view(data); var $dest = this.$el.hasClass("oe_form_container") ? this.$el : this.$el.find('.oe_form_container'); this.rendering_engine.render_to($dest); this.$el.on('mousedown.formBlur', function () { self.__clicked_inside = true; }); this.$buttons = $(QWeb.render("FormView.buttons", {'widget':self})); if (this.options.$buttons) { this.$buttons.appendTo(this.options.$buttons); } else { this.$el.find('.oe_form_buttons').replaceWith(this.$buttons); } this.$buttons.on('click', '.oe_form_button_create', this.guard_active(this.on_button_create)); this.$buttons.on('click', '.oe_form_button_edit', this.guard_active(this.on_button_edit)); this.$buttons.on('click', '.oe_form_button_save', this.guard_active(this.on_button_save)); this.$buttons.on('click', '.oe_form_button_cancel', this.guard_active(this.on_button_cancel)); if (this.options.footer_to_buttons) { this.$el.find('footer').appendTo(this.$buttons); } this.$sidebar = this.options.$sidebar || this.$el.find('.oe_form_sidebar'); if (!this.sidebar && this.options.$sidebar) { this.sidebar = new instance.web.Sidebar(this); this.sidebar.appendTo(this.$sidebar); if (this.fields_view.toolbar) { this.sidebar.add_toolbar(this.fields_view.toolbar); } this.sidebar.add_items('other', _.compact([ self.is_action_enabled('delete') && { label: _t('Delete'), callback: self.on_button_delete }, self.is_action_enabled('create') && { label: _t('Duplicate'), callback: self.on_button_duplicate } ])); } this.has_been_loaded.resolve(); // Add bounce effect on button 'Edit' when click on readonly page view. this.$el.find(".oe_form_group_row,.oe_form_field,label,h1,.oe_title,.oe_notebook_page, .oe_list_content").on('click', function (e) { if(self.get("actual_mode") == "view") { var $button = self.options.$buttons.find(".oe_form_button_edit"); $button.openerpBounce(); e.stopPropagation(); instance.web.bus.trigger('click', e); } }); //bounce effect on red button when click on statusbar. this.$el.find(".oe_form_field_status:not(.oe_form_status_clickable)").on('click', function (e) { if((self.get("actual_mode") == "view")) { var $button = self.$el.find(".oe_highlight:not(.oe_form_invisible)").css({'float':'left','clear':'none'}); $button.openerpBounce(); e.stopPropagation(); } }); this.trigger('form_view_loaded', data); return $.when(); }, widgetFocused: function() { // Clear click flag if used to focus a widget this.__clicked_inside = false; if (this.__blur_timeout) { clearTimeout(this.__blur_timeout); this.__blur_timeout = null; } }, widgetBlurred: function() { if (this.__clicked_inside) { // clicked in an other section of the form (than the currently // focused widget) => just ignore the blurring entirely? this.__clicked_inside = false; return; } var self = this; // clear timeout, if any this.widgetFocused(); this.__blur_timeout = setTimeout(function () { self.trigger('blurred'); }, 0); }, do_load_state: function(state, warm) { if (state.id && this.datarecord.id != state.id) { if (this.dataset.get_id_index(state.id) === null) { this.dataset.ids.push(state.id); } this.dataset.select_id(state.id); this.do_show(); } }, /** * * @param {Object} [options] * @param {Boolean} [mode=undefined] If specified, switch the form to specified mode. Can be "edit" or "view". * @param {Boolean} [reload=true] whether the form should reload its content on show, or use the currently loaded record * @return {$.Deferred} */ do_show: function (options) { var self = this; options = options || {}; if (this.sidebar) { this.sidebar.$el.show(); } if (this.$buttons) { this.$buttons.show(); } this.$el.show().css({ opacity: '0', filter: 'alpha(opacity = 0)' }); this.$el.add(this.$buttons).removeClass('oe_form_dirty'); var shown = this.has_been_loaded; if (options.reload !== false) { shown = shown.then(function() { if (self.dataset.index === null) { // null index means we should start a new record return self.on_button_new(); } var fields = _.keys(self.fields_view.fields); fields.push('display_name'); return self.dataset.read_index(fields, { context: { 'bin_size': true } }).then(function(r) { self.trigger('load_record', r); }); }); } return shown.then(function() { self._actualize_mode(options.mode || self.options.initial_mode); self.$el.css({ opacity: '1', filter: 'alpha(opacity = 100)' }); }); }, do_hide: function () { if (this.sidebar) { this.sidebar.$el.hide(); } if (this.$buttons) { this.$buttons.hide(); } if (this.$pager) { this.$pager.hide(); } this._super(); }, load_record: function(record) { var self = this, set_values = []; if (!record) { this.set({ 'title' : undefined }); this.do_warn(_t("Form"), _t("The record could not be found in the database."), true); return $.Deferred().reject(); } this.datarecord = record; this._actualize_mode(); this.set({ 'title' : record.id ? record.display_name : _t("New") }); _(this.fields).each(function (field, f) { field._dirty_flag = false; field._inhibit_on_change_flag = true; var result = field.set_value(self.datarecord[f] || false); field._inhibit_on_change_flag = false; set_values.push(result); }); return $.when.apply(null, set_values).then(function() { if (!record.id) { // trigger onchanges self.do_onchange(null); } self.on_form_changed(); self.rendering_engine.init_fields(); self.is_initialized.resolve(); self.do_update_pager(record.id === null || record.id === undefined); if (self.sidebar) { self.sidebar.do_attachement_update(self.dataset, self.datarecord.id); } if (record.id) { self.do_push_state({id:record.id}); } else { self.do_push_state({}); } self.$el.add(self.$buttons).removeClass('oe_form_dirty'); self.autofocus(); }); }, /** * Loads and sets up the default values for the model as the current * record * * @return {$.Deferred} */ load_defaults: function () { var self = this; var keys = _.keys(this.fields_view.fields); if (keys.length) { return this.dataset.default_get(keys).then(function(r) { self.trigger('load_record', _.clone(r)); }); } return self.trigger('load_record', {}); }, on_form_changed: function() { this.trigger("view_content_has_changed"); }, do_notify_change: function() { this.$el.add(this.$buttons).addClass('oe_form_dirty'); }, execute_pager_action: function(action) { if (this.can_be_discarded()) { switch (action) { case 'first': this.dataset.index = 0; break; case 'previous': this.dataset.previous(); break; case 'next': this.dataset.next(); break; case 'last': this.dataset.index = this.dataset.ids.length - 1; break; } var def = this.reload(); this.trigger('pager_action_executed'); return def; } return $.when(); }, init_pager: function() { var self = this; if (this.$pager) this.$pager.remove(); if (this.get("actual_mode") === "create") return; this.$pager = $(QWeb.render("FormView.pager", {'widget':self})).hide(); if (this.options.$pager) { this.$pager.appendTo(this.options.$pager); } else { this.$el.find('.oe_form_pager').replaceWith(this.$pager); } this.$pager.on('click','a[data-pager-action]',function() { var $el = $(this); if ($el.attr("disabled")) return; var action = $el.data('pager-action'); var def = $.when(self.execute_pager_action(action)); $el.attr("disabled"); def.always(function() { $el.removeAttr("disabled"); }); }); this.do_update_pager(); }, do_update_pager: function(hide_index) { this.$pager.toggle(this.dataset.ids.length > 1); if (hide_index) { $(".oe_form_pager_state", this.$pager).html(""); } else { $(".oe_form_pager_state", this.$pager).html(_.str.sprintf(_t("%d / %d"), this.dataset.index + 1, this.dataset.ids.length)); } }, _build_onchange_specs: function() { var self = this; var find = function(field_name, root) { var fields = [root]; while (fields.length) { var node = fields.pop(); if (!node) { continue; } if (node.tag === 'field' && node.attrs.name === field_name) { return node.attrs.on_change || ""; } fields = _.union(fields, node.children); } return ""; }; self._onchange_fields = []; self._onchange_specs = {}; _.each(this.fields, function(field, name) { self._onchange_fields.push(name); self._onchange_specs[name] = find(name, field.node); _.each(field.field.views, function(view) { _.each(view.fields, function(_, subname) { self._onchange_specs[name + '.' + subname] = find(subname, view.arch); }); }); }); }, _get_onchange_values: function() { var field_values = this.get_fields_values(); if (field_values.id.toString().match(instance.web.BufferedDataSet.virtual_id_regex)) { delete field_values.id; } if (this.dataset.parent_view) { // this belongs to a parent view: add parent field if possible var parent_view = this.dataset.parent_view; var child_name = this.dataset.child_name; var parent_name = parent_view.get_field_desc(child_name).relation_field; if (parent_name) { // consider all fields except the inverse of the parent field var parent_values = parent_view.get_fields_values(); delete parent_values[child_name]; field_values[parent_name] = parent_values; } } return field_values; }, do_onchange: function(widget) { var self = this; var onchange_specs = self._onchange_specs; try { var def = $.when({}); var change_spec = widget ? onchange_specs[widget.name] : null; if (!widget || (!_.isEmpty(change_spec) && change_spec !== "0")) { var ids = [], trigger_field_name = widget ? widget.name : self._onchange_fields, values = self._get_onchange_values(), context = new instance.web.CompoundContext(self.dataset.get_context()); if (widget && widget.build_context()) { context.add(widget.build_context()); } if (self.dataset.parent_view) { var parent_name = self.dataset.parent_view.get_field_desc(self.dataset.child_name).relation_field; context.add({field_parent: parent_name}); } if (self.datarecord.id && !instance.web.BufferedDataSet.virtual_id_regex.test(self.datarecord.id)) { // In case of a o2m virtual id, we should pass an empty ids list ids.push(self.datarecord.id); } def = self.alive(new instance.web.Model(self.dataset.model).call( "onchange", [ids, values, trigger_field_name, onchange_specs, context])); } this.onchanges_mutex.exec(function(){ return def.then(function(response) { var fields = {}; if (widget){ fields[widget.name] = widget.field; } else{ fields = self.fields_view.fields; } var defs = []; _.each(fields, function(field, fieldname){ if (field && field.change_default) { var value_; if (response.value && (fieldname in response.value)) { // Use value from onchange if onchange executed value_ = response.value[fieldname]; } else { // otherwise get form value for field value_ = self.fields[fieldname].get_value(); } var condition = fieldname + '=' + value_; if (value_) { defs.push(self.alive(new instance.web.Model('ir.values').call( 'get_defaults', [self.model, condition] )).then(function (results) { if (!results.length) { return response; } if (!response.value) { response.value = {}; } for(var i=0; i%s', _.escape(f.string)); }).value(); warnings.unshift('
    '); warnings.push('
'); this.do_warn(_t("The following fields are invalid:"), warnings.join('')); }, /** * Reload the form after saving * * @param {Object} r result of the write function. */ record_saved: function(r) { this.trigger('record_saved', r); if (!r) { // should not happen in the server, but may happen for internal purpose return $.Deferred().reject(); } return r; }, /** * Updates the form' dataset to contain the new record: * * * Adds the newly created record to the current dataset (at the end by * default) * * Selects that record (sets the dataset's index to point to the new * record's id). * * Updates the pager and sidebar displays * * @param {Object} r * @param {Boolean} [prepend_on_create=false] adds the newly created record * at the beginning of the dataset instead of the end */ record_created: function(r, prepend_on_create) { var self = this; if (!r) { // should not happen in the server, but may happen for internal purpose this.trigger('record_created', r); return $.Deferred().reject(); } else { this.datarecord.id = r; if (!prepend_on_create) { this.dataset.alter_ids(this.dataset.ids.concat([this.datarecord.id])); this.dataset.index = this.dataset.ids.length - 1; } else { this.dataset.alter_ids([this.datarecord.id].concat(this.dataset.ids)); this.dataset.index = 0; } this.do_update_pager(); if (this.sidebar) { this.sidebar.do_attachement_update(this.dataset, this.datarecord.id); } //openerp.log("The record has been created with id #" + this.datarecord.id); return $.when(this.reload()).then(function () { self.trigger('record_created', r); return _.extend(r, {created: true}); }); } }, on_action: function (action) { console.debug('Executing action', action); }, reload: function() { var self = this; return this.reload_mutex.exec(function() { if (self.dataset.index === null || self.dataset.index === undefined) { self.trigger("previous_view"); return $.Deferred().reject().promise(); } if (self.dataset.index < 0) { return $.when(self.on_button_new()); } else { var fields = _.keys(self.fields_view.fields); fields.push('display_name'); return self.dataset.read_index(fields, { context: { 'bin_size': true }, check_access_rule: true }).then(function(r) { self.trigger('load_record', r); }).fail(function (){ self.do_action('history_back'); }); } }); }, get_widgets: function() { return _.filter(this.getChildren(), function(obj) { return obj instanceof instance.web.form.FormWidget; }); }, get_fields_values: function() { var values = {}; var ids = this.get_selected_ids(); values["id"] = ids.length > 0 ? ids[0] : false; _.each(this.fields, function(value_, key) { values[key] = value_.get_value(); }); return values; }, get_selected_ids: function() { var id = this.dataset.ids[this.dataset.index]; return id ? [id] : []; }, recursive_save: function() { var self = this; return $.when(this.save()).then(function(res) { if (self.dataset.parent_view) return self.dataset.parent_view.recursive_save(); }); }, recursive_reload: function() { var self = this; var pre = $.when(); if (self.dataset.parent_view) pre = self.dataset.parent_view.recursive_reload(); return pre.then(function() { return self.reload(); }); }, is_dirty: function() { return _.any(this.fields, function (value_) { return value_._dirty_flag; }); }, is_interactible_record: function() { var id = this.datarecord.id; if (!id) { if (this.options.not_interactible_on_create) return false; } else if (typeof(id) === "string") { if(instance.web.BufferedDataSet.virtual_id_regex.test(id)) return false; } return true; }, sidebar_eval_context: function () { return $.when(this.build_eval_context()); }, open_defaults_dialog: function () { var self = this; var display = function (field, value) { if (!value) { return value; } if (field instanceof instance.web.form.FieldSelection) { return _(field.get('values')).find(function (option) { return option[0] === value; })[1]; } else if (field instanceof instance.web.form.FieldMany2One) { return field.get_displayed(); } return value; }; var fields = _.chain(this.fields) .map(function (field) { var value = field.get_value(); // ignore fields which are empty, invisible, readonly, o2m // or m2m if (!value || field.get('invisible') || field.get("readonly") || field.field.type === 'one2many' || field.field.type === 'many2many' || field.field.type === 'binary' || field.password) { return false; } return { name: field.name, string: field.string, value: value, displayed: display(field, value), }; }) .compact() .sortBy(function (field) { return field.string; }) .value(); var conditions = _.chain(self.fields) .filter(function (field) { return field.field.change_default; }) .map(function (field) { var value = field.get_value(); return { name: field.name, string: field.string, value: value, displayed: display(field, value), }; }) .value(); var d = new instance.web.Dialog(this, { title: _t("Set Default"), args: { fields: fields, conditions: conditions }, buttons: [ {text: _t("Close"), click: function () { d.close(); }}, {text: _t("Save default"), click: function () { var $defaults = d.$el.find('#formview_default_fields'); var field_to_set = $defaults.val(); if (!field_to_set) { $defaults.parent().addClass('oe_form_invalid'); return; } var condition = d.$el.find('#formview_default_conditions').val(), all_users = d.$el.find('#formview_default_all').is(':checked'); new instance.web.DataSet(self, 'ir.values').call( 'set_default', [ self.dataset.model, field_to_set, self.fields[field_to_set].get_value(), all_users, true, condition || false ]).done(function () { d.close(); }); }} ] }); d.template = 'FormView.set_default'; d.open(); }, register_field: function(field, name) { this.fields[name] = field; this.fields_order.push(name); if (JSON.parse(field.node.attrs.default_focus || "0")) { this.default_focus_field = field; } field.on('focused', null, this.proxy('widgetFocused')) .on('blurred', null, this.proxy('widgetBlurred')); if (this.get_field_desc(name).translate) { this.translatable_fields.push(field); } field.on('changed_value', this, function() { if (field.is_syntax_valid()) { this.trigger('field_changed:' + name); } if (field._inhibit_on_change_flag) { return; } field._dirty_flag = true; if (field.is_syntax_valid()) { this.do_onchange(field); this.on_form_changed(true); this.do_notify_change(); } }); }, get_field_desc: function(field_name) { return this.fields_view.fields[field_name]; }, get_field_value: function(field_name) { return this.fields[field_name].get_value(); }, compute_domain: function(expression) { return instance.web.form.compute_domain(expression, this.fields); }, _build_view_fields_values: function() { var a_dataset = this.dataset; var fields_values = this.get_fields_values(); var active_id = a_dataset.ids[a_dataset.index]; _.extend(fields_values, { active_id: active_id || false, active_ids: active_id ? [active_id] : [], active_model: a_dataset.model, parent: {} }); if (a_dataset.parent_view) { fields_values.parent = a_dataset.parent_view.get_fields_values(); } return fields_values; }, build_eval_context: function() { var a_dataset = this.dataset; return new instance.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values()); }, }); /** * Interface to be implemented by rendering engines for the form view. */ instance.web.form.FormRenderingEngineInterface = instance.web.Class.extend({ set_fields_view: function(fields_view) {}, set_fields_registry: function(fields_registry) {}, render_to: function($el) {}, }); /** * Default rendering engine for the form view. * * It is necessary to set the view using set_view() before usage. */ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInterface.extend({ init: function(view) { this.view = view; }, set_fields_view: function(fvg) { this.fvg = fvg; this.version = parseFloat(this.fvg.arch.attrs.version); if (isNaN(this.version)) { this.version = 7.0; } }, set_tags_registry: function(tags_registry) { this.tags_registry = tags_registry; }, set_fields_registry: function(fields_registry) { this.fields_registry = fields_registry; }, set_widgets_registry: function(widgets_registry) { this.widgets_registry = widgets_registry; }, // Backward compatibility tools, current default version: v7 process_version: function() { if (this.version < 7.0) { this.$form.find('form:first').wrapInner(''); this.$form.find('page').each(function() { if (!$(this).parents('field').length) { $(this).wrapInner(''); } }); } }, get_arch_fragment: function() { var doc = $.parseXML(instance.web.json_node_to_xml(this.fvg.arch)).documentElement; // IE won't allow custom button@type and will revert it to spec default : 'submit' $('button', doc).each(function() { $(this).attr('data-button-type', $(this).attr('type')).attr('type', 'button'); }); // IE's html parser is also a css parser. How convenient... $('board', doc).each(function() { $(this).attr('layout', $(this).attr('style')); }); return $('
').append(instance.web.xml_to_str(doc)); }, render_to: function($target) { var self = this; this.$target = $target; this.$form = this.get_arch_fragment(); this.process_version(); this.fields_to_init = []; this.tags_to_init = []; this.widgets_to_init = []; this.labels = {}; this.process(this.$form); this.$form.appendTo(this.$target); this.to_replace = []; _.each(this.fields_to_init, function($elem) { var name = $elem.attr("name"); if (!self.fvg.fields[name]) { throw new Error(_.str.sprintf(_t("Field '%s' specified in view could not be found."), name)); } var obj = self.fields_registry.get_any([$elem.attr('widget'), self.fvg.fields[name].type]); if (!obj) { throw new Error(_.str.sprintf(_t("Widget type '%s' is not implemented"), $elem.attr('widget'))); } var w = new (obj)(self.view, instance.web.xml_to_json($elem[0])); var $label = self.labels[$elem.attr("name")]; if ($label) { w.set_input_id($label.attr("for")); } self.alter_field(w); self.view.register_field(w, $elem.attr("name")); self.to_replace.push([w, $elem]); }); _.each(this.tags_to_init, function($elem) { var tag_name = $elem[0].tagName.toLowerCase(); var obj = self.tags_registry.get_object(tag_name); var w = new (obj)(self.view, instance.web.xml_to_json($elem[0])); self.to_replace.push([w, $elem]); }); _.each(this.widgets_to_init, function($elem) { var widget_type = $elem.attr("type"); var obj = self.widgets_registry.get_object(widget_type); var w = new (obj)(self.view, instance.web.xml_to_json($elem[0])); self.to_replace.push([w, $elem]); }); }, init_fields: function() { var defs = []; _.each(this.to_replace, function(el) { defs.push(el[0].replace(el[1])); if (el[1].children().length) { el[0].$el.append(el[1].children()); } }); this.to_replace = []; return $.when.apply($, defs); }, render_element: function(template /* dictionaries */) { var dicts = [].slice.call(arguments).slice(1); var dict = _.extend.apply(_, dicts); dict['classnames'] = dict['class'] || ''; // class is a reserved word and might caused problem to Safari when used from QWeb return $(QWeb.render(template, dict)); }, alter_field: function(field) { }, toggle_layout_debugging: function() { if (!this.$target.has('.oe_layout_debug_cell:first').length) { this.$target.find('[title]').removeAttr('title'); this.$target.find('.oe_form_group_cell').each(function() { var text = 'W:' + ($(this).attr('width') || '') + ' - C:' + $(this).attr('colspan'); $(this).attr('title', text); }); } this.$target.toggleClass('oe_layout_debugging'); }, process: function($tag) { var self = this; var tagname = $tag[0].nodeName.toLowerCase(); if (this.tags_registry.contains(tagname)) { this.tags_to_init.push($tag); return (tagname === 'button') ? this.process_button($tag) : $tag; } var fn = self['process_' + tagname]; if (fn) { var args = [].slice.call(arguments); args[0] = $tag; return fn.apply(self, args); } else { // generic tag handling, just process children $tag.children().each(function() { self.process($(this)); }); self.handle_common_properties($tag, $tag); $tag.removeAttr("modifiers"); return $tag; } }, process_button: function ($button) { var self = this; $button.children().each(function() { self.process($(this)); }); return $button; }, process_widget: function($widget) { this.widgets_to_init.push($widget); return $widget; }, process_sheet: function($sheet) { var $new_sheet = this.render_element('FormRenderingSheet', $sheet.getAttributes()); this.handle_common_properties($new_sheet, $sheet); var $dst = $new_sheet.find('.oe_form_sheet'); $sheet.contents().appendTo($dst); $sheet.before($new_sheet).remove(); this.process($new_sheet); }, process_form: function($form) { if ($form.find('> sheet').length === 0) { $form.addClass('oe_form_nosheet'); } var $new_form = this.render_element('FormRenderingForm', $form.getAttributes()); this.handle_common_properties($new_form, $form); $form.contents().appendTo($new_form); if ($form[0] === this.$form[0]) { // If root element, replace it this.$form = $new_form; } else { $form.before($new_form).remove(); } this.process($new_form); }, /* * Used by direct children of a tag only * This method will add the implicit for every field * in the */ preprocess_field: function($field) { var self = this; var name = $field.attr('name'), field_colspan = parseInt($field.attr('colspan'), 10), field_modifiers = JSON.parse($field.attr('modifiers') || '{}'); if ($field.attr('nolabel') === '1') return; $field.attr('nolabel', '1'); var found = false; this.$form.find('label[for="' + name + '"]').each(function(i ,el) { $(el).parents().each(function(unused, tag) { var name = tag.tagName.toLowerCase(); if (name === "field" || name in self.tags_registry.map) found = true; }); }); if (found) return; var $label = $('