X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=addons%2Fweb%2Fstatic%2Fsrc%2Fjs%2Fview_form.js;h=c6295ea7298ff67e24a34b3c26f299ebbf182d44;hb=2b1bd31f7449fc88f1e18eb64384c1b81b85fe28;hp=4a0ccfbb6a56bdf4a76f45e80f7fd99b7eab3476;hpb=0777583ac9534dcdc050b58bde7d2caa3c79c5fd;p=odoo%2Fodoo.git diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 4a0ccfb..c6295ea 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -1,31 +1,58 @@ -openerp.web.form = function (openerp) { +openerp.web.form = function (instance) { +var _t = instance.web._t, + _lt = instance.web._lt; +var QWeb = instance.web.qweb; -var _t = openerp.web._t, - _lt = openerp.web._lt; -var QWeb = openerp.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. + * Events: + * - view_content_has_changed : when the values of the fields have changed. When + * this event is triggered all fields should reprocess their modifiers. + */ +instance.web.form.FieldManagerMixin = { + /** + * Must return the asked field as in fields_get. + */ + get_field: function(field_name) {}, + /** + * Called by the field when the translate button is clicked. + */ + open_translate_dialog: function(field) {}, + /** + * Returns true when the view is in create mode. + */ + is_create_mode: function() {}, +}; -openerp.web.views.add('form', 'openerp.web.FormView'); -openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# */{ +instance.web.views.add('form', 'instance.web.FormView'); +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, - readonly : false, - form_template: "FormView", + template: "FormView", display_name: _lt('Form'), + view_type: "form", /** - * @constructs openerp.web.FormView - * @extends openerp.web.View + * @constructs instance.web.FormView + * @extends instance.web.View * - * @param {openerp.web.Session} session the current openerp session - * @param {openerp.web.DataSet} dataset the dataset this view will work with + * @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 - * - sidebar : [true|false] * - resize_textareas : [true|false|max_height] * - * @property {openerp.web.Registry} registry=openerp.web.form.widgets widgets registry for this form view instance + * @property {instance.web.Registry} registry=instance.web.form.widgets widgets registry for this form view instance */ init: function(parent, dataset, view_id, options) { this._super(parent); @@ -34,100 +61,171 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# this.model = dataset.model; this.view_id = view_id || false; this.fields_view = {}; - this.widgets = {}; - this.widgets_counter = 0; this.fields = {}; this.fields_order = []; this.datarecord = {}; this.default_focus_field = null; this.default_focus_button = null; - this.registry = openerp.web.form.widgets; + this.fields_registry = instance.web.form.widgets; + this.tags_registry = instance.web.form.tags; this.has_been_loaded = $.Deferred(); - this.$form_header = null; this.translatable_fields = []; _.defaults(this.options, { - "not_interactible_on_create": false + "not_interactible_on_create": false, + "initial_mode": "view", }); this.is_initialized = $.Deferred(); this.mutating_mutex = new $.Mutex(); this.on_change_mutex = new $.Mutex(); this.reload_mutex = new $.Mutex(); - }, - start: function() { - this._super(); - return this.init_view(); - }, - init_view: function() { - if (this.embedded_view) { - var def = $.Deferred().then(this.on_loaded); - var self = this; - $.async_when().then(function() {def.resolve(self.embedded_view);}); - return def.promise(); - } else { - var context = new openerp.web.CompoundContext(this.dataset.get_context()); - return this.rpc("/web/view/load", { - "model": this.model, - "view_id": this.view_id, - "view_type": "form", - toolbar: this.options.sidebar, - context: context - }, this.on_loaded); - } - }, - stop: function() { - if (this.sidebar) { - this.sidebar.attachments.stop(); - this.sidebar.stop(); - } - _.each(this.widgets, function(w) { - w.stop(); + this.__clicked_inside = false; + this.__blur_timeout = null; + this.rendering_engine = new instance.web.form.FormRenderingEngineReadonly(this); + this.qweb = null; // A QWeb instance will be created if the view is a QWeb template + }, + destroy: function() { + _.each(this.get_widgets(), function(w) { + w.off('focused blurred'); + w.destroy(); }); + this.$element.off('.formBlur'); this._super(); }, - reposition: function ($e) { - this.$element = $e; - this.on_loaded(); - }, on_loaded: function(data) { var self = this; - if (data) { - this.fields_order = []; - this.fields_view = data; - var frame = new (this.registry.get_object('frame'))(this, this.fields_view.arch); + if (!data) { + throw new Error("No data provided."); + } + if (this.arch) { + throw "Form view does not support multiple calls to on_loaded"; + } + this.fields_order = []; + this.fields_view = data; - this.rendered = QWeb.render(this.form_template, { 'frame': frame, 'widget': this }); + this.rendering_engine.set_fields_registry(this.fields_registry); + this.rendering_engine.set_tags_registry(this.tags_registry); + if (!this.extract_qweb_template(data)) { + this.rendering_engine.set_fields_view(data); + var $dest = this.$element.hasClass("oe_form_container") ? this.$element : this.$element.find('.oe_form_container'); + this.rendering_engine.render_to($dest); } - this.$element.html(this.rendered); - _.each(this.widgets, function(w) { - w.start(); + + this.$element.on('mousedown.formBlur', function () { + self.__clicked_inside = true; }); - this.$form_header = this.$element.find('.oe_form_header:first'); - this.$form_header.find('div.oe_form_pager button[data-pager-action]').click(function() { + + this.$buttons = $(QWeb.render("FormView.buttons", {'widget':self})); + if (this.options.$buttons) { + this.$buttons.appendTo(this.options.$buttons); + } else { + this.$element.find('.oe_form_buttons').replaceWith(this.$buttons); + } + this.$buttons.on('click','.oe_form_button_create',this.on_button_create); + this.$buttons.on('click','.oe_form_button_edit',this.on_button_edit); + this.$buttons.on('click','.oe_form_button_save',this.on_button_save); + this.$buttons.on('click','.oe_form_button_cancel',this.on_button_cancel); + + this.$pager = $(QWeb.render("FormView.pager", {'widget':self})); + if (this.options.$pager) { + this.$pager.appendTo(this.options.$pager); + } else { + this.$element.find('.oe_form_pager').replaceWith(this.$pager); + } + this.$pager.on('click','a[data-pager-action]',function() { var action = $(this).data('pager-action'); self.on_pager_action(action); }); - this.$form_header.find('button.oe_form_button_save').click(this.on_button_save); - this.$form_header.find('button.oe_form_button_cancel').click(this.on_button_cancel); - - if (!this.sidebar && this.options.sidebar && this.options.sidebar_id) { - this.sidebar = new openerp.web.Sidebar(this, this.options.sidebar_id); - this.sidebar.start(); - this.sidebar.do_unfold(); - this.sidebar.attachments = new openerp.web.form.SidebarAttachments(this.sidebar, this); - this.sidebar.add_toolbar(this.fields_view.toolbar); - this.set_common_sidebar_sections(this.sidebar); - - this.sidebar.add_section(_t('Customize'), 'customize'); - this.sidebar.add_items('customize', [{ - label: _t('Set Default'), - form: this, - callback: function (item) { - item.form.open_defaults_dialog(); - } - }]); + this.$sidebar = this.options.$sidebar || this.$element.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', [ + { label: _t('Delete'), callback: self.on_button_delete }, + { label: _t('Duplicate'), callback: self.on_button_duplicate }, + { label: _t('Set Default'), callback: function (item) { self.open_defaults_dialog(); } }, + ]); } + this.on("change:mode", this, this.switch_mode); + this.set({mode: this.options.initial_mode}); this.has_been_loaded.resolve(); + return $.when(); + }, + extract_qweb_template: function(fvg) { + for (var i=0, ii=fvg.arch.children.length; i < ii; i++) { + var child = fvg.arch.children[i]; + if (child.tag === "templates") { + this.qweb = new QWeb2.Engine(); + this.qweb.add_template(instance.web.json_node_to_xml(child)); + if (!this.qweb.has_template('form')) { + throw new Error("No QWeb template found for form view"); + } + return true; + } + } + this.qweb = null; + return false; + }, + get_fvg_from_qweb: function(record) { + var view = this.qweb.render('form', this.get_qweb_context(record)); + var fvg = _.clone(this.fields_view); + fvg.arch = instance.web.xml_to_json(instance.web.str_to_xml(view).firstChild); + return fvg; + }, + get_qweb_context: function(record) { + var self = this, + new_record = {}; + _.each(record, function(value_, name) { + var r = _.clone(self.fields_view.fields[name] || {}); + if ((r.type === 'date' || r.type === 'datetime') && value_) { + r.raw_value = instance.web.auto_str_to_date(value_); + } else { + r.raw_value = value_; + } + r.value = instance.web.format_value(value_, r); + new_record[name] = r; + }); + return { + record : new_record, + new_record : !record.id + }; + }, + kill_current_form: function() { + _.each(this.getChildren(), function(el) { + el.destroy(); + }); + this.fields = {}; + this.fields_order = []; + this.default_focus_field = null; + this.default_focus_button = null; + this.translatable_fields = []; + this.$element.find('.oe_form_container').empty(); + }, + + 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) { @@ -141,9 +239,19 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } } }, - - do_show: function () { + do_show: function (options) { var self = this; + options = options || {}; + if (this.sidebar) { + this.sidebar.$element.show(); + } + if (this.$buttons) { + this.$buttons.show(); + this.$buttons.find('.oe_form_button_save').removeClass('oe_form_button_save_dirty'); + } + if (this.$pager) { + this.$pager.show(); + } this.$element.show().css('visibility', 'hidden'); this.$element.removeClass('oe_form_dirty'); return this.has_been_loaded.pipe(function() { @@ -157,19 +265,25 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# }).pipe(self.on_record_loaded); } result.pipe(function() { + if (options.editable) { + self.set({mode: "edit"}); + } self.$element.css('visibility', 'visible'); }); - if (self.sidebar) { - self.sidebar.$element.show(); - } return result; }); }, do_hide: function () { - this._super(); if (this.sidebar) { this.sidebar.$element.hide(); } + if (this.$buttons) { + this.$buttons.hide(); + } + if (this.$pager) { + this.$pager.hide(); + } + this._super(); }, on_record_loaded: function(record) { var self = this, set_values = []; @@ -179,13 +293,17 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } this.datarecord = record; + if (this.qweb) { + this.kill_current_form(); + this.rendering_engine.set_fields_view(this.get_fvg_from_qweb(record)); + var $dest = this.$element.hasClass("oe_form_container") ? this.$element : this.$element.find('.oe_form_container'); + this.rendering_engine.render_to($dest); + } + _(this.fields).each(function (field, f) { - field.reset(); + field._dirty_flag = false; var result = field.set_value(self.datarecord[f] || false); set_values.push(result); - $.when(result).then(function() { - field.validate(); - }); }); return $.when.apply(null, set_values).pipe(function() { if (!record.id) { @@ -194,7 +312,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# _.each(self.fields_order, function(field_name) { if (record[field_name] !== undefined) { var field = self.fields[field_name]; - field.dirty = true; + field._dirty_flag = true; self.do_onchange(field); } }); @@ -203,7 +321,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# self.is_initialized.resolve(); self.do_update_pager(record.id == null); if (self.sidebar) { - self.sidebar.attachments.do_update(); + self.sidebar.do_attachement_update(self.dataset, self.datarecord.id); } if (self.default_focus_field) { self.default_focus_field.focus(); @@ -212,20 +330,15 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# self.do_push_state({id:record.id}); } self.$element.removeClass('oe_form_dirty'); + self.$buttons.find('.oe_form_button_save').removeClass('oe_form_button_save_dirty'); }); }, on_form_changed: function() { - for (var w in this.widgets) { - w = this.widgets[w]; - w.process_modifiers(); - if (w.field) { - w.validate(); - } - w.update_dom(); - } + this.trigger("view_content_has_changed"); }, do_notify_change: function() { this.$element.addClass('oe_form_dirty'); + this.$buttons.find('.oe_form_button_save').addClass('oe_form_button_save_dirty'); }, on_pager_action: function(action) { if (this.can_be_discarded()) { @@ -247,11 +360,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } }, do_update_pager: function(hide_index) { - var $pager = this.$form_header.find('div.oe_form_pager'); var index = hide_index ? '-' : this.dataset.index + 1; - $pager.find('button').prop('disabled', this.dataset.ids.length < 2); - $pager.find('span.oe_pager_index').html(index); - $pager.find('span.oe_pager_count').html(this.dataset.ids.length); + this.$pager.find('button').prop('disabled', this.dataset.ids.length < 2).end() + .find('span.oe_pager_index').html(index).end() + .find('span.oe_pager_count').html(this.dataset.ids.length); }, parse_on_change: function (on_change, widget) { var self = this; @@ -272,7 +384,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# 'None': function () {return null;}, 'context': function (i) { context_index = i; - var ctx = new openerp.web.CompoundContext(self.dataset.get_context(), widget.build_context() ? widget.build_context() : {}); + var ctx = new instance.web.CompoundContext(self.dataset.get_context(), widget.build_context() ? widget.build_context() : {}); return ctx; } }; @@ -290,8 +402,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } // form field if (self.fields[field]) { - var value = self.fields[field].get_on_change_value(); - return value == null ? false : value; + var value_ = self.fields[field].get_value(); + return value_ == null ? false : value_; } // parent field var splitted = field.split('.'); @@ -354,17 +466,17 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } if (widget.field['change_default']) { - var fieldname = widget.name, value; + var fieldname = widget.name, value_; if (response.value && (fieldname in response.value)) { // Use value from onchange if onchange executed - value = response.value[fieldname]; + value_ = response.value[fieldname]; } else { // otherwise get form value for field - value = self.fields[fieldname].get_on_change_value(); + value_ = self.fields[fieldname].get_value(); } - var condition = fieldname + '=' + value; + var condition = fieldname + '=' + value_; - if (value) { + if (value_) { can_process_onchange = self.rpc({ url: '/web/dataset/call', async: false @@ -392,23 +504,23 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# return self.on_processed_onchange(response, processed); } catch(e) { console.error(e); + instance.webclient.crashmanager.on_javascript_exception(e); return $.Deferred().reject(); } }); }, - on_processed_onchange: function(response, processed) { + on_processed_onchange: function(result, processed) { try { - var result = response; if (result.value) { for (var f in result.value) { if (!result.value.hasOwnProperty(f)) { continue; } var field = this.fields[f]; // If field is not defined in the view, just ignore it if (field) { - var value = result.value[f]; - if (field.get_value() != value) { - field.set_value(value); - field.dirty = true; + var value_ = result.value[f]; + if (field.get_value() != value_) { + field.set_value(value_); + field._dirty_flag = true; if (!_.contains(processed, field.name)) { this.do_onchange(field, processed); } @@ -418,7 +530,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# this.on_form_changed(); } if (!_.isEmpty(result.warning)) { - $(QWeb.render("CrashManagerWarning", result.warning)).dialog({ + instance.web.dialog($(QWeb.render("CrashManager.warning", result.warning)), { + title:result.warning.title, modal: true, buttons: [ {text: _t("Ok"), click: function() { $(this).dialog("close"); }} @@ -427,6 +540,9 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } if (result.domain) { function edit_domain(node) { + if (typeof node !== "object") { + return; + } var new_domain = result.domain[node.attrs.name]; if (new_domain) { node.attrs.domain = new_domain; @@ -438,22 +554,46 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# return $.Deferred().resolve(); } catch(e) { console.error(e); + instance.webclient.crashmanager.on_javascript_exception(e); return $.Deferred().reject(); } }, + switch_mode: function() { + var self = this; + if(this.get("mode") == "view") { + self.$element.removeClass('oe_form_editable').addClass('oe_form_readonly'); + self.$buttons.find('.oe_form_buttons_edit').hide(); + self.$buttons.find('.oe_form_buttons_view').show(); + self.$sidebar.show(); + _.each(this.fields,function(field){ + field.set({"force_readonly": true}); + }); + } else { + self.$element.removeClass('oe_form_readonly').addClass('oe_form_editable'); + self.$buttons.find('.oe_form_buttons_edit').show(); + self.$buttons.find('.oe_form_buttons_view').hide(); + self.$sidebar.hide(); + _.each(this.fields,function(field){ + field.set({"force_readonly": false}); + }); + } + }, on_button_save: function() { var self = this; return this.do_save().then(function(result) { - self.do_prev_view({'created': result.created, 'default': 'page'}); + self.set({mode: "view"}); }); }, - on_button_cancel: function() { + on_button_cancel: function(event) { if (this.can_be_discarded()) { - return this.do_prev_view({'default': 'page'}); + this.set({mode: "view"}); + this.on_record_loaded(this.datarecord); } + return false; }, on_button_new: function() { var self = this; + this.set({mode: "edit"}); var def = $.Deferred(); $.when(this.has_been_loaded).then(function() { if (self.can_be_discarded()) { @@ -471,6 +611,44 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# }); return def.promise(); }, + on_button_edit: function() { + return this.set({mode: "edit"}); + }, + on_button_create: function() { + this.dataset.index = null; + this.do_show(); + }, + on_button_duplicate: function() { + var self = this; + var def = $.Deferred(); + $.when(this.has_been_loaded).then(function() { + self.dataset.call('copy', [self.datarecord.id, {}, self.dataset.context]).then(function(new_id) { + return self.on_created({ result : new_id }); + }).then(function() { + return self.set({mode: "edit"}); + }).then(function() { + def.resolve(); + }); + }); + return def.promise(); + }, + on_button_delete: function() { + var self = this; + var def = $.Deferred(); + $.when(this.has_been_loaded).then(function() { + if (self.datarecord.id && confirm(_t("Do you really want to delete this record?"))) { + self.dataset.unlink([self.datarecord.id]).then(function() { + self.on_pager_action('next'); + def.resolve(); + }); + } else { + $.async_when().then(function () { + def.reject(); + }) + } + }); + return def.promise(); + }, can_be_discarded: function() { return !this.$element.is('.oe_form_dirty') || confirm(_t("Warning, the record has been modified, your changes will be discarded.")); }, @@ -479,7 +657,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# * record or saving an existing one depending on whether the record * already has an id property. * - * @param {Function} success callback on save success + * @param {Function} [success] callback on save success * @param {Boolean} [prepend_on_create=false] if ``do_save`` creates a new record, should that record be inserted at the start of the dataset (by default, records are added at the end) */ do_save: function(success, prepend_on_create) { @@ -493,11 +671,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# f = self.fields[f]; if (!f.is_valid()) { form_invalid = true; - f.update_dom(true); if (!first_invalid_field) { first_invalid_field = f; } - } else if (f.name !== 'id' && !f.readonly && (!self.datarecord.id || f.is_dirty())) { + } else if (f.name !== 'id' && !f.get("readonly") && (!self.datarecord.id || f._dirty_flag)) { // Special case 'id' field, do not save this field // on 'create' : save all non readonly fields // on 'edit' : save non readonly modified fields @@ -505,10 +682,15 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } } if (form_invalid) { + self.set({'display_invalid_fields': true}); + for (var f in self.fields) { + self.fields[f]._check_css_flags(); + } first_invalid_field.focus(); self.on_invalid(); return $.Deferred().reject(); } else { + self.set({'display_invalid_fields': false}); var save_deferral; if (!self.datarecord.id) { //console.log("FormView(", self, ") : About to create", values); @@ -537,7 +719,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# var msg = ""; @@ -549,7 +731,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# return $.Deferred().reject(); } else { return $.when(this.reload()).pipe(function () { - return $.when(r).then(success); }, null); + return r; }) + .then(success); } }, /** @@ -575,16 +758,17 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# 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.alter_ids([this.datarecord.id].concat(this.dataset.ids)); this.dataset.index = 0; } this.do_update_pager(); if (this.sidebar) { - this.sidebar.attachments.do_update(); + this.sidebar.do_attachement_update(this.dataset, this.datarecord.id); } //openerp.log("The record has been created with id #" + this.datarecord.id); - this.reload(); - return $.when(_.extend(r, {created: true})).then(success); + return $.when(this.reload()).pipe(function () { + return _.extend(r, {created: true}); }) + .then(success); } }, on_action: function (action) { @@ -593,6 +777,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# reload: function() { var self = this; return this.reload_mutex.exec(function() { + if (self.dataset.index == null) { + self.do_prev_view(); + return $.Deferred().reject().promise(); + } if (self.dataset.index == null || self.dataset.index < 0) { return $.when(self.on_button_new()); } else { @@ -602,15 +790,20 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } }); }, + get_widgets: function() { + return _.filter(this.getChildren(), function(obj) { + return obj instanceof instance.web.form.FormWidget; + }); + }, get_fields_values: function(blacklist) { blacklist = blacklist || []; var values = {}; var ids = this.get_selected_ids(); values["id"] = ids.length > 0 ? ids[0] : false; - _.each(this.fields, function(value, key) { + _.each(this.fields, function(value_, key) { if (_.include(blacklist, key)) return; - var val = value.get_value(); + var val = value_.get_value(); values[key] = val; }); return values; @@ -627,8 +820,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# }); }, is_dirty: function() { - return _.any(this.fields, function (value) { - return value.is_dirty(); + return _.any(this.fields, function (value_) { + return value_._dirty_flag; }); }, is_interactible_record: function() { @@ -637,7 +830,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# if (this.options.not_interactible_on_create) return false; } else if (typeof(id) === "string") { - if(openerp.web.BufferedDataSet.virtual_id_regex.test(id)) + if(instance.web.BufferedDataSet.virtual_id_regex.test(id)) return false; } return true; @@ -653,29 +846,24 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# // ignore fields which are empty, invisible, readonly, o2m // or m2m if (!value - || field.invisible - || field.readonly + || field.get('invisible') + || field.get("readonly") || field.field.type === 'one2many' || field.field.type === 'many2many') { return false; } - var displayed; - switch(field.field.type) { + var displayed = value; + switch (field.field.type) { case 'selection': displayed = _(field.values).find(function (option) { return option[0] === value; })[1]; break; - case 'many2one': - displayed = field.value[1] || value; - break; - default: - displayed = value; } return { name: name, - string: field.string, + string: field.node.attrs.string || field.field.string, value: value, displayed: displayed, // convert undefined to false @@ -689,7 +877,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# .filter(function (field) { return field.change_default; }) .value(); - var d = new openerp.web.Dialog(this, { + var d = new instance.web.Dialog(this, { title: _t("Set Default"), args: { fields: fields, @@ -701,12 +889,12 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# var $defaults = d.$element.find('#formview_default_fields'); var field_to_set = $defaults.val(); if (!field_to_set) { - $defaults.parent().addClass('invalid'); + $defaults.parent().addClass('oe_form_invalid'); return; } var condition = d.$element.find('#formview_default_conditions').val(), all_users = d.$element.find('#formview_default_all').is(':checked'); - new openerp.web.DataSet(self, 'ir.values').call( + new instance.web.DataSet(self, 'ir.values').call( 'set_default', [ self.dataset.model, field_to_set, @@ -720,9 +908,443 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# }); d.template = 'FormView.set_default'; d.open(); - } + }, + register_field: function(field, name) { + this.fields[name] = field; + this.fields_order.push(name); + + field.on('focused', null, this.proxy('widgetFocused')) + .on('blurred', null, this.proxy('widgetBlurred')); + if (this.get_field(name).translate) { + this.translatable_fields.push(field); + } + field.on('changed_value', this, function() { + field._dirty_flag = true; + if (field.is_syntax_valid()) { + this.do_onchange(field); + this.on_form_changed(true); + this.do_notify_change(); + } + }); + }, + get_field: function(field_name) { + return this.fields_view.fields[field_name]; + }, + is_create_mode: function() { + return !this.datarecord.id; + }, +}); + +/** + * 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($element) {}, +}); + +/** + * 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 = 6.1; + } + }, + set_tags_registry: function(tags_registry) { + this.tags_registry = tags_registry; + }, + set_fields_registry: function(fields_registry) { + this.fields_registry = fields_registry; + }, + // Backward compatibility tools, current default version: v6.1 + 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(''); + } + }); + } + selector = 'form[version!="7.0"] page,form[version!="7.0"]'; + }, + render_to: function($target) { + var self = this; + this.$target = $target; + + // TODO: I know this will save the world and all the kitten for a moment, + // but one day, we will have to get rid of xml2json + var xml = instance.web.json_node_to_xml(this.fvg.arch); + this.$form = $('
' + xml + '
'); + + this.process_version(); + + this.fields_to_init = []; + this.tags_to_init = []; + this.labels = {}; + this.process(this.$form); + + this.$form.appendTo(this.$target); + + _.each(this.fields_to_init, function($elem) { + var name = $elem.attr("name"); + if (!self.fvg.fields[name]) { + throw new Error("Field '" + name + "' specified in view could not be found."); + } + var obj = self.fields_registry.get_any([$elem.attr('widget'), self.fvg.fields[name].type]); + if (!obj) { + throw new Error("Widget type '"+ $elem.attr('widget') + "' is not implemented"); + } + 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")); + w.replace($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])); + w.replace($elem); + }); + // TODO: return a deferred + }, + 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 $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_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; + + $label = $('