diff options
Diffstat (limited to 'addons/web/static/src/js/view_form.js')
| -rw-r--r-- | addons/web/static/src/js/view_form.js | 2829 |
1 files changed, 1559 insertions, 1270 deletions
diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 50c5dcb976a..7e5b173fd37 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -1,31 +1,32 @@ -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 = {}; -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({ /** * 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,98 +35,86 @@ 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); - } + this.mode = 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() { - if (this.sidebar) { - this.sidebar.attachments.destroy(); - this.sidebar.destroy(); - } - _.each(this.widgets, function(w) { + _.each(this.get_widgets(), function(w) { w.destroy(); }); this._super(); }, - reposition: function ($e) { - this.$element = $e; - this.on_loaded(); - }, on_loaded: function(data) { var self = this; if (!data) { - throw "No data provided."; + throw new Error("No data provided."); } - if (this.root_frame) { - throw "Form view does not support multiple calls to on_loaded"; + 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, {'widget': this}); - this.$element.html(this.rendered); - - this.root_frame = instanciate_widget(this.registry.get_object('frame'), this, this.fields_view.arch); - var to_append = $(".oe_form_header", this.$element); - this.root_frame.appendTo(to_append.length > 0 ? to_append : this.$element); - this.root_frame.$element.children().unwrap(); - - 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.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.$sidebar = this.options.$sidebar || this.$element.find('.oe_form_sidebar'); + + 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','button.oe_form_button_save',this.on_button_save) + .on('click','button.oe_form_button_cancel',this.on_button_cancel) + .on('click','button.oe_form_button_edit',this.on_button_edit) + .on('click','button.oe_form_button_create',this.on_button_create) + .on('click','button.oe_form_button_duplicate',this.on_button_duplicate) + .on('click','button.oe_form_button_delete',this.on_button_delete); + + 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', [{ + 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('Set Default'), form: this, callback: function (item) { @@ -133,9 +122,61 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } }]); } + 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(); }, - do_load_state: function(state, warm) { if (state.id && this.datarecord.id != state.id) { if (!this.dataset.get_id_index(state.id)) { @@ -147,9 +188,18 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } } }, - do_show: function () { var self = this; + 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() { @@ -165,17 +215,20 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# result.pipe(function() { 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 = []; @@ -185,13 +238,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) { @@ -200,7 +257,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); } }); @@ -209,7 +266,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(); @@ -218,20 +275,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()) { @@ -253,11 +305,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; @@ -278,7 +329,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; } }; @@ -296,8 +347,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('.'); @@ -360,17 +411,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 @@ -411,10 +462,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# 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); } @@ -424,7 +475,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# this.on_form_changed(); } if (!_.isEmpty(result.warning)) { - openerp.web.dialog($(QWeb.render("CrashManagerWarning", result.warning)), { + instance.web.dialog($(QWeb.render("CrashManagerWarning", result.warning)), { modal: true, buttons: [ {text: _t("Ok"), click: function() { $(this).dialog("close"); }} @@ -447,19 +498,38 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# return $.Deferred().reject(); } }, + switch_mode: function() { + var mode = this.get("mode"); + var self = this; + if(mode=="view") { + self.$buttons.find('.oe_form_buttons_edit').hide(); + self.$buttons.find('.oe_form_buttons_view').show(); + _.each(this.fields,function(field){ + field.set({"force_readonly": true}); + }); + } else { + self.$buttons.find('.oe_form_buttons_edit').show(); + self.$buttons.find('.oe_form_buttons_view').hide(); + _.each(this.fields,function(field){ + field.set({"force_readonly": false}); + }); + } + self.mode = mode; + }, 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() { if (this.can_be_discarded()) { - return this.do_prev_view({'default': 'page'}); + this.set({mode: "view"}); } }, 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()) { @@ -477,6 +547,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.")); }, @@ -499,11 +607,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 @@ -511,20 +618,23 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } } if (form_invalid) { + self.set({'display_invalid_fields': true}); 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); save_deferral = self.dataset.create(values).pipe(function(r) { return self.on_created(r, undefined, prepend_on_create); }, null); - } else if (_.isEmpty(values)) { + } else if (_.isEmpty(values) && ! self.force_dirty) { //console.log("FormView(", self, ") : Nothing to save"); save_deferral = $.Deferred().resolve({}).promise(); } else { + self.force_dirty = false; //console.log("FormView(", self, ") : About to save", values); save_deferral = self.dataset.write(self.datarecord.id, values, {}).pipe(function(r) { return self.on_saved(r); @@ -542,7 +652,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# var msg = "<ul>"; _.each(this.fields, function(f) { if (!f.is_valid()) { - msg += "<li>" + f.string + "</li>"; + msg += "<li>" + f.node.attrs.string + "</li>"; } }); msg += "</ul>"; @@ -580,14 +690,14 @@ 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); + //instance.log("The record has been created with id #" + this.datarecord.id); this.reload(); return $.when(_.extend(r, {created: true})).then(success); } @@ -598,6 +708,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 { @@ -607,15 +721,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.Widget; + }); + }, 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; @@ -632,8 +751,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() { @@ -642,7 +761,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; @@ -654,12 +773,12 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# var self = this; var fields = _.chain(this.fields) .map(function (field, name) { - var value = field.get_value(); + var value_ = field.get_value(); // ignore fields which are empty, invisible, readonly, o2m // or m2m - if (!value - || field.invisible - || field.readonly + if (!value_ + || field.get('invisible') + || field.get("readonly") || field.field.type === 'one2many' || field.field.type === 'many2many') { return false; @@ -668,33 +787,33 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# switch(field.field.type) { case 'selection': displayed = _(field.values).find(function (option) { - return option[0] === value; + return option[0] === value_; })[1]; break; case 'many2one': - displayed = field.value[1] || value; + displayed = value_; break; default: - displayed = value; + displayed = value_; } return { name: name, - string: field.string, - value: value, + string: field.node_atts.string, + value: value_, displayed: displayed, // convert undefined to false change_default: !!field.field.change_default } }) .compact() - .sortBy(function (field) { return field.string; }) + .sortBy(function (field) { return field.node_atts.string; }) .value(); var conditions = _.chain(fields) .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, @@ -706,12 +825,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, @@ -725,9 +844,368 @@ 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); + 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; + }, }); -openerp.web.FormDialog = openerp.web.Dialog.extend({ + +/** + * Interface to be implemented by rendering engines for the form view. + */ +instance.web.form.FormRenderingEngineInterface = { + 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.Class.extend({ + init: function(view) { + this.view = view; + }, + set_fields_view: function(fvg) { + this.fvg = fvg; + }, + set_tags_registry: function(tags_registry) { + this.tags_registry = tags_registry; + }, + set_fields_registry: function(fields_registry) { + this.fields_registry = fields_registry; + }, + 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 = $('<div class="oe_form">' + xml + '</div>'); + if (this.fvg.arch.attrs && this.fvg.arch.attrs['layout'] !== 'manual') { + this.$form.attr('layout', 'auto'); + } + + 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 '"+ key + "' 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, layout/* dictionaries */) { + var dicts = [].slice.call(arguments).slice(2); + dicts.unshift({ 'layout' : layout }); + var dict = _.extend.apply(_, dicts); + dict['classnames'] = dict['class'] || ''; // class is a reserved word and might caused problem to Safari when used from QWeb + var alt_template = template + '.' + layout; + template = QWeb.has_template(alt_template) ? alt_template : template; + 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, layout) { + var self = this; + if ($tag.attr('layout') === 'auto') { + $tag.addClass('oe_form_autolayout'); + } + layout = $tag.attr('layout') || layout || 'auto'; + $tag.removeAttr('layout'); + 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; + args[1] = layout; + return fn.apply(self, args); + } else { + // generic tag handling, just process children + $tag.children().each(function() { + self.process($(this), layout); + }); + self.handle_common_properties($tag, $tag); + $tag.removeAttr("modifiers"); + return $tag; + } + }, + process_sheet: function() { + this.process_form.apply(this, arguments); + }, + process_form: function($form, layout) { + var $new_form = this.render_element('FormRenderingForm', layout, $form.getAttributes()); + var $dst = (layout === 'auto') ? $new_form.find('group:first') : $new_form; + $new_form.attr("modifiers", $form.attr("modifiers")); + $form.children().appendTo($dst); + 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); + }, + 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 = $('<label/>').attr({ + 'for' : name, + "modifiers": JSON.stringify({invisible: field_modifiers.invisible}), + "string": $field.attr('string'), + "help": $field.attr('help'), + }); + $label.insertBefore($field); + if (field_colspan > 1) { + $field.attr('colspan', field_colspan - 1); + } + return $label; + }, + process_field: function($field, layout) { + var $label = this.preprocess_field($field); + if ($label) + this.process($label, layout); + this.fields_to_init.push($field); + return $field; + }, + process_group: function($group, layout) { + var self = this; + if ($group.parent().is('.oe_form_group_cell')) { + $group.parent().addClass('oe_form_group_nested'); + } + $group.children('field').each(function() { + self.preprocess_field($(this)); + }); + var $new_group = this.render_element('FormRenderingGroup', layout, $group.getAttributes()), + $table; + if ($new_group.is('table')) { + $table = $new_group; + } else { + $table = $new_group.find('table:first'); + } + $table.addClass('oe_form_group'); + var $tr, $td, + cols = parseInt($group.attr('col') || 4, 10), + row_cols = cols; + + var children = []; + $group.children().each(function(a,b,c) { + var $child = $(this), + colspan = parseInt($child.attr('colspan') || 1, 10), + tagName = $child[0].tagName.toLowerCase(); + if (tagName === 'newline') { + $tr = null; + return; + } + if (!$tr || row_cols < colspan) { + $tr = $('<tr/>').addClass('oe_form_group_row').appendTo($table); + row_cols = cols; + } + row_cols -= colspan; + + $td = $('<td/>').addClass('oe_form_group_cell').attr('colspan', colspan); + // invisibility transfer + var field_modifiers = JSON.parse($child.attr('modifiers') || '{}'); + var invisible = field_modifiers.invisible; + field_modifiers.invisible = undefined; + $child.attr('modifiers', JSON.stringify(field_modifiers)); + self.handle_common_properties($td, $("<dummy>").attr("modifiers", JSON.stringify({invisible: invisible}))); + + $tr.append($td.append($child)); + children.push($child[0]); + }); + if (row_cols && $td) { + $td.attr('colspan', parseInt($td.attr('colspan'), 10) + row_cols); + } + $group.before($new_group).remove(); + + // Now compute width of cells + $table.find('> tbody > tr').each(function() { + var to_compute = [], + row_cols = cols, + total = 100; + $(this).children().each(function() { + var $td = $(this), + $child = $td.children(':first'); + switch ($child[0].tagName.toLowerCase()) { + case 'separator': + if ($child.attr('orientation') === 'vertical') { + $td.addClass('oe_vertical_separator').attr('width', '1'); + $td.empty(); + row_cols--; + } + break; + case 'label': + if ($child.attr('for')) { + $td.attr('width', '1%'); + row_cols--; + total--; + } + break; + default: + to_compute.push($td); + } + }); + var unit = Math.floor(total / row_cols); + _.each(to_compute, function($td, i) { + var width = parseInt($td.attr('colspan'), 10) * unit; + $td.attr('width', ((i == to_compute.length - 1) ? total : width) + '%'); + total -= width; + }); + }); + _.each(children, function(el) { + self.process($(el)); + }); + this.handle_common_properties($new_group, $group); + return $new_group; + }, + process_notebook: function($notebook, layout) { + var self = this; + var pages = []; + $notebook.find('> page').each(function() { + var $page = $(this); + var page_attrs = $page.getAttributes(); + page_attrs.id = _.uniqueId('notebook_page_'); + pages.push(page_attrs); + var $new_page = self.render_element('FormRenderingNotebookPage', layout, page_attrs); + var $dst = (layout === 'auto') ? $new_page.find('group:first') : $new_page; + $page.children().appendTo($dst); + $page.before($new_page).remove(); + self.handle_common_properties($new_page, $page); + }); + var $new_notebook = this.render_element('FormRenderingNotebook', layout, { pages : pages }); + $notebook.children().appendTo($new_notebook); + $notebook.before($new_notebook).remove(); + $new_notebook.children().each(function() { + self.process($(this)); + }); + $new_notebook.tabs(); + this.handle_common_properties($new_notebook, $notebook); + return $new_notebook; + }, + process_separator: function($separator, layout) { + var $new_separator = this.render_element('FormRenderingSeparator', layout, $separator.getAttributes()); + $separator.before($new_separator).remove(); + this.handle_common_properties($new_separator, $separator); + return $new_separator; + }, + process_label: function($label, layout) { + var name = $label.attr("for"), + field_orm = this.fvg.fields[name]; + var dict = { + string: $label.attr('string') || (field_orm || {}).string || '', + help: $label.attr('help') || (field_orm || {}).help || '', + _for: name ? _.uniqueId('oe-field-input-') : undefined, + }; + var align = parseFloat(dict.align); + if (isNaN(align) || align === 1) { + align = 'right'; + } else if (align === 0) { + align = 'left'; + } else { + align = 'center'; + } + dict.align = align; + var $new_label = this.render_element('FormRenderingLabel', layout, dict); + $label.before($new_label).remove(); + this.handle_common_properties($new_label, $label); + if (name) { + this.labels[name] = $new_label; + } + return $new_label; + }, + handle_common_properties: function($new_element, $node) { + var str_modifiers = $node.attr("modifiers") || "{}" + var modifiers = JSON.parse(str_modifiers); + if (modifiers.invisible !== undefined) + new instance.web.form.InvisibilityChanger(this.view, this.view, modifiers.invisible, $new_element); + $new_element.addClass($node.attr("class") || ""); + }, +}); + +instance.web.form.FormRenderingEngineReadonly = instance.web.form.FormRenderingEngine.extend({ + alter_field: function(field) { + field.set({"force_readonly": true}); + }, +}); + +instance.web.form.FormDialog = instance.web.Dialog.extend({ init: function(parent, options, view_id, dataset) { this._super(parent, options); this.dataset = dataset; @@ -736,8 +1214,7 @@ openerp.web.FormDialog = openerp.web.Dialog.extend({ }, start: function() { this._super(); - this.form = new openerp.web.FormView(this, this.dataset, this.view_id, { - sidebar: false, + this.form = new instance.web.FormView(this, this.dataset, this.view_id, { pager: false }); this.form.appendTo(this.$element); @@ -758,62 +1235,7 @@ openerp.web.FormDialog = openerp.web.Dialog.extend({ } }); -/** @namespace */ -openerp.web.form = {}; - -openerp.web.form.SidebarAttachments = openerp.web.OldWidget.extend({ - init: function(parent, form_view) { - var $section = parent.add_section(_t('Attachments'), 'attachments'); - this.$div = $('<div class="oe-sidebar-attachments"></div>'); - $section.append(this.$div); - - this._super(parent, $section.attr('id')); - this.view = form_view; - }, - do_update: function() { - if (!this.view.datarecord.id) { - this.on_attachments_loaded([]); - } else { - (new openerp.web.DataSetSearch( - this, 'ir.attachment', this.view.dataset.get_context(), - [ - ['res_model', '=', this.view.dataset.model], - ['res_id', '=', this.view.datarecord.id], - ['type', 'in', ['binary', 'url']] - ])).read_slice(['name', 'url', 'type'], {}).then(this.on_attachments_loaded); - } - }, - on_attachments_loaded: function(attachments) { - this.attachments = attachments; - this.$div.html(QWeb.render('FormView.sidebar.attachments', this)); - this.$element.find('.oe-binary-file').change(this.on_attachment_changed); - this.$element.find('.oe-sidebar-attachment-delete').click(this.on_attachment_delete); - }, - on_attachment_changed: function(e) { - window[this.element_id + '_iframe'] = this.do_update; - var $e = $(e.target); - if ($e.val() != '') { - this.$element.find('form.oe-binary-form').submit(); - $e.parent().find('input[type=file]').prop('disabled', true); - $e.parent().find('button').prop('disabled', true).find('img, span').toggle(); - } - }, - on_attachment_delete: function(e) { - var self = this, $e = $(e.currentTarget); - var name = _.str.trim($e.parent().find('a.oe-sidebar-attachments-link').text()); - if (confirm(_.str.sprintf(_t("Do you really want to delete the attachment %s?"), name))) { - this.rpc('/web/dataset/unlink', { - model: 'ir.attachment', - ids: [parseInt($e.attr('data-id'))] - }, function(r) { - $e.parent().remove(); - self.do_notify("Delete an attachment", "The attachment '" + name + "' has been deleted"); - }); - } - } -}); - -openerp.web.form.compute_domain = function(expr, fields) { +instance.web.form.compute_domain = function(expr, fields) { var stack = []; for (var i = expr.length - 1; i >= 0; i--) { var ex = expr[i]; @@ -842,7 +1264,7 @@ openerp.web.form.compute_domain = function(expr, fields) { _t("Unknown field %s in domain %s"), ex[0], JSON.stringify(expr))); } - var field_value = field.get_value ? fields[ex[0]].get_value() : fields[ex[0]].value; + var field_value = field.get_value ? field.get_value() : field.value; var op = ex[1]; var val = ex[2]; @@ -884,60 +1306,84 @@ openerp.web.form.compute_domain = function(expr, fields) { return _.all(stack, _.identity); }; -openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.form.Widget# */{ - form_template: 'Widget', +/** + * Must be applied over an class already possessing the GetterSetterMixin. + * + * Apply the result of the "invisible" domain to this.$element. + */ +instance.web.form.InvisibilityChangerMixin = { + init: function(field_manager, invisible_domain) { + this._ic_field_manager = field_manager + this._ic_invisible_modifier = invisible_domain; + this._ic_field_manager.on("view_content_has_changed", this, function() { + var result = this._ic_invisible_modifier === undefined ? false : + instance.web.form.compute_domain(this._ic_invisible_modifier, this._ic_field_manager.fields); + this.set({"invisible": result}); + }); + this.set({invisible: this._ic_invisible_modifier === true}); + }, + start: function() { + var check_visibility = function() { + if (this.get("invisible")) { + this.$element.hide(); + } else { + this.$element.show(); + } + }; + this.on("change:invisible", this, check_visibility); + _.bind(check_visibility, this)(); + }, +}; + +instance.web.form.InvisibilityChanger = instance.web.Class.extend(_.extend({}, instance.web.GetterSetterMixin, instance.web.form.InvisibilityChangerMixin, { + init: function(parent, field_manager, invisible_domain, $element) { + this.setParent(parent); + instance.web.GetterSetterMixin.init.call(this); + instance.web.form.InvisibilityChangerMixin.init.call(this, field_manager, invisible_domain); + this.$element = $element; + this.start(); + }, +})); + +instance.web.form.Widget = instance.web.Widget.extend(_.extend({}, instance.web.form.InvisibilityChangerMixin, { /** - * @constructs openerp.web.form.Widget - * @extends openerp.web.OldWidget + * @constructs instance.web.form.Widget + * @extends instance.web.Widget * * @param view * @param node */ init: function(view, node) { + this._super(view); this.view = view; this.node = node; this.modifiers = JSON.parse(this.node.attrs.modifiers || '{}'); - this.always_invisible = (this.modifiers.invisible && this.modifiers.invisible === true); - this.type = this.type || node.tag; - this.element_name = this.element_name || this.type; - - this._super(view); - - this.children = node.children; - this.colspan = parseInt(node.attrs.colspan || 1, 10); - this.decrease_max_width = 0; - - this.string = this.string || node.attrs.string; - this.help = this.help || node.attrs.help; - this.invisible = this.modifiers['invisible'] === true; - this.classname = 'oe_form_' + this.type; - - this.align = parseFloat(this.node.attrs.align); - if (isNaN(this.align) || this.align === 1) { - this.align = 'right'; - } else if (this.align === 0) { - this.align = 'left'; - } else { - this.align = 'center'; - } + instance.web.form.InvisibilityChangerMixin.init.call(this, view, this.modifiers.invisible); - this.width = this.node.attrs.width; + this.view.on("view_content_has_changed", this, this.process_modifiers); + }, + renderElement: function() { + this._super(); + this.$element.addClass(this.node.attrs["class"] || ""); + }, + start: function() { + this._super(); + instance.web.form.InvisibilityChangerMixin.start.call(this); }, destroy: function() { - this._super.apply(this, arguments); $.fn.tipsy.clear(); + this._super.apply(this, arguments); }, process_modifiers: function() { - var compute_domain = openerp.web.form.compute_domain; + var compute_domain = instance.web.form.compute_domain; + var to_set = {}; for (var a in this.modifiers) { - this[a] = compute_domain(this.modifiers[a], this.view.fields); + if (!_.include(["invisible"], a)) { + var val = compute_domain(this.modifiers[a], this.view.fields); + to_set[a] = val; + } } - }, - update_dom: function() { - this.$element.toggle(!this.invisible); - }, - renderElement: function() { - this.$element.html(QWeb.render(this.form_template, { "widget": this })); + this.set(to_set); }, do_attach_tooltip: function(widget, trigger, options) { widget = widget || this; @@ -947,12 +1393,12 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo delayOut: 0, fade: true, title: function() { - var template = widget.form_template + '.tooltip'; + var template = widget.template + '.tooltip'; if (!QWeb.has_template(template)) { template = 'WidgetLabel.tooltip'; } return QWeb.render(template, { - debug: openerp.connection.debug, + debug: instance.connection.debug, widget: widget })}, gravity: $.fn.tipsy.autoBounds(50, 'nw'), @@ -979,7 +1425,7 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo }, _build_eval_context: function(blacklist) { var a_dataset = this.view.dataset; - return new openerp.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values(blacklist)); + return new instance.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values(blacklist)); }, /** * Builds a new context usable for operations related to fields by merging @@ -994,7 +1440,7 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo if (v_context.__ref || true) { //TODO: remove true var fields_values = this._build_eval_context(blacklist); - v_context = new openerp.web.CompoundContext(v_context).set_eval_context(fields_values); + v_context = new instance.web.CompoundContext(v_context).set_eval_context(fields_values); } return v_context; }, @@ -1005,235 +1451,28 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo var final_domain = n_domain !== null ? n_domain : f_domain; if (!(final_domain instanceof Array) || true) { //TODO: remove true var fields_values = this._build_eval_context(); - final_domain = new openerp.web.CompoundDomain(final_domain).set_eval_context(fields_values); + final_domain = new instance.web.CompoundDomain(final_domain).set_eval_context(fields_values); } return final_domain; } -}); +})); -openerp.web.form.WidgetFrame = openerp.web.form.Widget.extend({ - form_template: 'WidgetFrame', - init: function(view, node) { - this._super(view, node); - this.columns = parseInt(node.attrs.col || 4, 10); - this.x = 0; - this.y = 0; - this.table = []; - this.add_row(); - for (var i = 0; i < node.children.length; i++) { - var n = node.children[i]; - if (n.tag == "newline") { - this.add_row(); - } else { - this.handle_node(n); - } - } - this.set_row_cells_with(this.table[this.table.length - 1]); - }, - renderElement: function() { - this._super(); - var self = this; - _.each(this.table, function(row) { - _.each(row, function(td) { - td.$element = self.$element.find('.' + td.element_class); - td.renderElement(); - }); - }); - }, - start: function() { - _.each(this.table, function(row) { - _.each(row, function(td) { - td.start(); - }); - }); - }, - add_row: function(){ - if (this.table.length) { - this.set_row_cells_with(this.table[this.table.length - 1]); - } - var row = []; - this.table.push(row); - this.x = 0; - this.y += 1; - return row; - }, - set_row_cells_with: function(row) { - var bypass = 0, - max_width = 100, - row_length = row.length; - for (var i = 0; i < row.length; i++) { - if (row[i].always_invisible) { - row_length--; - } else { - bypass += row[i].width === undefined ? 0 : 1; - max_width -= row[i].decrease_max_width; - } - } - var size_unit = Math.round(max_width / (this.columns - bypass)), - colspan_sum = 0; - for (var i = 0; i < row.length; i++) { - var w = row[i]; - if (w.always_invisible) { - continue; - } - colspan_sum += w.colspan; - if (w.width === undefined) { - var width = (i === row_length - 1 && colspan_sum === this.columns) ? max_width : Math.round(size_unit * w.colspan); - max_width -= width; - w.width = width + '%'; - } - } - }, - handle_node: function(node) { - var type = {}; - if (node.tag == 'field') { - type = this.view.fields_view.fields[node.attrs.name] || {}; - if (node.attrs.widget == 'statusbar' && node.attrs.nolabel !== '1') { - // This way we can retain backward compatibility between addons and old clients - node.attrs.colspan = (parseInt(node.attrs.colspan, 10) || 1) + 1; - node.attrs.nolabel = '1'; - } - } - var widget = instanciate_widget(this.view.registry.get_any( - [node.attrs.widget, type.type, node.tag]), this.view, node); - if (node.tag == 'field') { - if (!this.view.default_focus_field || node.attrs.default_focus == '1') { - this.view.default_focus_field = widget; - } - if (node.attrs.nolabel != '1') { - var label = instanciate_widget(this.view.registry.get_object('label'), this.view, node); - label["for"] = widget; - this.add_widget(label, widget.colspan + 1); - } - } - this.add_widget(widget); - }, - add_widget: function(widget, colspan) { - var current_row = this.table[this.table.length - 1]; - if (!widget.always_invisible) { - colspan = colspan || widget.colspan; - if (current_row.length && (this.x + colspan) > this.columns) { - current_row = this.add_row(); - } - this.x += widget.colspan; - } - current_row.push(widget); - return widget; - } -}); - -openerp.web.form.WidgetGroup = openerp.web.form.WidgetFrame.extend({ - form_template: 'WidgetGroup' -}), - -openerp.web.form.WidgetNotebook = openerp.web.form.Widget.extend({ - form_template: 'WidgetNotebook', - init: function(view, node) { - this._super(view, node); - this.pages = []; - for (var i = 0; i < node.children.length; i++) { - var n = node.children[i]; - if (n.tag == "page") { - var page = instanciate_widget(this.view.registry.get_object('notebookpage'), - this.view, n, this, this.pages.length); - this.pages.push(page); - } - } - }, - renderElement: function() { - this._super(); - var self = this; - _.each(this.pages, function(page) { - page.$element = self.$element.find('.' + page.element_class); - page.renderElement(); - }); - }, - start: function() { - var self = this; - this._super.apply(this, arguments); - this.$element.find('> ul > li').each(function (index, tab_li) { - var page = self.pages[index], - id = _.uniqueId(self.element_name + '-'); - page.element_id = id; - $(tab_li).find('a').attr('href', '#' + id); - }); - this.$element.find('> div').each(function (index, page) { - page.id = self.pages[index].element_id; - }); - this.$element.tabs(); - this.view.on_button_new.add_first(this.do_select_first_visible_tab); - if (openerp.connection.debug) { - this.do_attach_tooltip(this, this.$element.find('ul:first'), { - gravity: 's' - }); - } - _.each(this.pages, function(page) { - page.start(); - }); - }, - do_select_first_visible_tab: function() { - for (var i = 0; i < this.pages.length; i++) { - var page = this.pages[i]; - if (page.invisible === false) { - this.$element.tabs('select', page.index); - break; - } - } - } -}); - -openerp.web.form.WidgetNotebookPage = openerp.web.form.WidgetFrame.extend({ - form_template: 'WidgetNotebookPage', - init: function(view, node, notebook, index) { - this.notebook = notebook; - this.index = index; - this.element_name = 'page_' + index; - this._super(view, node); - }, - start: function() { - this._super.apply(this, arguments); - this.$element_tab = this.notebook.$element.find( - '> ul > li:eq(' + this.index + ')'); - }, - update_dom: function() { - if (this.invisible && this.index === this.notebook.$element.tabs('option', 'selected')) { - this.notebook.do_select_first_visible_tab(); - } - this.$element_tab.toggle(!this.invisible); - this.$element.toggle(!this.invisible); - } -}); - -openerp.web.form.WidgetSeparator = openerp.web.form.Widget.extend({ - form_template: 'WidgetSeparator', - init: function(view, node) { - this._super(view, node); - this.orientation = node.attrs.orientation || 'horizontal'; - if (this.orientation === 'vertical') { - this.width = '1'; - } - this.classname += '_' + this.orientation; - } -}); - -openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({ - form_template: 'WidgetButton', +instance.web.form.WidgetButton = instance.web.form.Widget.extend({ + template: 'WidgetButton', init: function(view, node) { this._super(view, node); this.force_disabled = false; - if (this.string) { - // We don't have button key bindings in the webclient - this.string = this.string.replace(/_/g, ''); - } - if (node.attrs.default_focus == '1') { + this.string = (this.node.attrs.string || '').replace(/_/g, ''); + if (this.node.attrs.default_focus == '1') { // TODO fme: provide enter key binding to widgets this.view.default_focus_button = this; } + this.view.on('view_content_has_changed', this, this.check_disable); }, start: function() { this._super.apply(this, arguments); - this.$element.find("button").click(this.on_click); - if (this.help || openerp.connection.debug) { + this.$element.click(this.on_click); + if (this.node.attrs.help || instance.connection.debug) { this.do_attach_tooltip(); } }, @@ -1251,7 +1490,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({ var exec_action = function() { if (self.node.attrs.confirm) { var def = $.Deferred(); - var dialog = openerp.web.dialog($('<div>' + self.node.attrs.confirm + '</div>'), { + var dialog = instance.web.dialog($('<div/>').text(self.node.attrs.confirm), { title: _t('Confirm'), modal: true, buttons: [ @@ -1275,6 +1514,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({ } }; if (!this.node.attrs.special) { + this.view.force_dirty = true; return this.view.recursive_save().pipe(exec_action); } else { return exec_action(); @@ -1285,7 +1525,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({ var context = this.node.attrs.context; if (context && context.__ref) { - context = new openerp.web.CompoundContext(context); + context = new instance.web.CompoundContext(context); context.set_eval_context(this._build_eval_context()); } @@ -1295,81 +1535,57 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({ self.view.reload(); }); }, - update_dom: function() { - this._super.apply(this, arguments); - this.check_disable(); - }, check_disable: function() { - var disabled = (this.readonly || this.force_disabled || !this.view.is_interactible_record()); - this.$element.find('button').prop('disabled', disabled); - this.$element.find("button").css('color', disabled ? 'grey' : ''); + var disabled = (this.force_disabled || !this.view.is_interactible_record()); + this.$element.prop('disabled', disabled); + this.$element.css('color', disabled ? 'grey' : ''); } }); -openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({ - form_template: 'WidgetLabel', - init: function(view, node) { - this.element_name = 'label_' + node.attrs.name; - - this._super(view, node); - - if (this.node.tag == 'label' && !this.string && this.node.children.length) { - this.string = this.node.children[0]; - this.align = 'left'; - } - - if (this.node.tag == 'label' && (this.align === 'left' || this.node.attrs.colspan || (this.string && this.string.length > 32))) { - this.form_template = "WidgetParagraph"; - this.colspan = parseInt(this.node.attrs.colspan || 1, 10); - // Widgets default to right-aligned, but paragraph defaults to - // left-aligned - if (isNaN(parseFloat(this.node.attrs.align))) { - this.align = 'left'; - } - - this.multilines = this.string && _.str.lines(this.string).length > 1; - } else { - this.colspan = 1; - this.width = '1%'; - this.decrease_max_width = 1; - this.nowrap = true; - } - }, - renderElement: function() { - var rendered; - if (this['for'] && this.type !== 'label') { - rendered = QWeb.render(this.form_template, {widget: this['for']}); - } else { - // Actual label widgets should not have a false and have type label - rendered = QWeb.render(this.form_template, {widget: this}); - } - this.$element.html(rendered); - }, - start: function() { - this._super(); - var self = this; - if (this['for'] && (this['for'].help || openerp.connection.debug)) { - this.do_attach_tooltip(self['for']); - } - this.$element.find("label").dblclick(function() { - var widget = self['for'] || self; - openerp.log(widget.element_class , widget); - window.w = widget; - }); - } -}); +/** + * 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.FieldManagerInterface = { + /** + * 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() {}, +}; /** * Interface to be implemented by fields. * - * Novajs Attributes: - * - ... - * - * Novajs Events: - * - ... + * Properties: + * - readonly: boolean. If set to true the field should appear in readonly mode. + * - force_readonly: boolean, When it is true, the field should always appear + * in read only mode, no matter what the value of the "readonly" property can be. + * Events: + * - changed_value: triggered to inform the view to check on_changes * */ -openerp.web.form.FieldInterface = { +instance.web.form.FieldInterface = { + /** + * Constructor takes 2 arguments: + * - field_manager: Implements FieldManagerInterface + * - node: the "<field>" node in json form + */ + init: function(field_manager, node) {}, /** * Called by the form view to indicate the value of the field. * @@ -1386,7 +1602,7 @@ openerp.web.form.FieldInterface = { * as well as any format commonly returned in a on_change. It must be able to autodetect those formats as * no information is ever given to know which format is used. */ - set_value: function(value) {}, + set_value: function(value_) {}, /** * Get the current value of the widget. * @@ -1405,221 +1621,307 @@ openerp.web.form.FieldInterface = { * syntax for "read" and "write" (example: m2o: set_value([0, "Administrator"]), get_value() => 0). */ get_value: function() {}, + /** + * Inform the current object of the id it should use to match a html <label> that exists somewhere in the + * view. + */ + set_input_id: function(id) {}, + /** + * Returns true if is_syntax_valid() returns true and the value is semantically + * valid too according to the semantic restrictions applied to the field. + */ + is_valid: function() {}, + /** + * Returns true if the field holds a value which is syntaxically correct, ignoring + * the potential semantic restrictions applied to the field. + */ + is_syntax_valid: function() {}, + /** + * Must set the focus on the field. + */ + focus: function() {}, }; /** - * Abstract class for classes implementing FieldInterface. Should be renamed to AbstractField some - * day. + * Abstract class for classes implementing FieldInterface. + * + * Properties: + * - effective_readonly: when it is true, the widget is displayed as readonly. Vary depending + * the values of the "readonly" property and the "force_readonly" property on the field manager. + * - value: useful property to hold the value of the field. By default, set_value() and get_value() + * set and retrieve the value property. Changing the value property also triggers automatically + * a 'changed_value' event that inform the view to trigger on_changes. + * */ -openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.form.Field# */{ +instance.web.form.AbstractField = instance.web.form.Widget.extend(/** @lends instance.web.form.AbstractField# */{ /** - * @constructs openerp.web.form.Field - * @extends openerp.web.form.Widget + * @constructs instance.web.form.AbstractField + * @extends instance.web.form.Widget * - * @param view + * @param field_manager * @param node */ - init: function(view, node) { - this.name = node.attrs.name; - this.value = undefined; - view.fields[this.name] = this; - view.fields_order.push(this.name); - this.type = node.attrs.widget || view.fields_view.fields[node.attrs.name].type; - this.element_name = "field_" + this.name + "_" + this.type; - - this._super(view, node); - - if (node.attrs.nolabel != '1' && this.colspan > 1) { - this.colspan--; - } - this.field = view.fields_view.fields[node.attrs.name] || {}; - this.string = node.attrs.string || this.field.string; - this.help = node.attrs.help || this.field.help; - this.nolabel = (this.field.nolabel || node.attrs.nolabel) === '1'; - this.readonly = this.modifiers['readonly'] === true; - this.required = this.modifiers['required'] === true; - this.invalid = this.dirty = false; - - this.classname = 'oe_form_field_' + this.type; + init: function(field_manager, node) { + this._super(field_manager, node); + this.field_manager = field_manager; + this.name = this.node.attrs.name; + this.set({'value': false}); + this.field = this.field_manager.get_field(this.name); + this.set({required: this.modifiers['required'] === true}); + + // some events to make the property "effective_readonly" sync automatically with "readonly" and + // "force_readonly" + this.set({"readonly": this.modifiers['readonly'] === true}); + var test_effective_readonly = function() { + this.set({"effective_readonly": this.get("readonly") || !!this.get("force_readonly")}); + }; + this.on("change:readonly", this, test_effective_readonly); + this.on("change:force_readonly", this, test_effective_readonly); + _.bind(test_effective_readonly, this)(); + + this.on("change:value", this, function() { + if (! this._inhibit_on_change) + this.trigger('changed_value'); + this._check_css_flags(); + }); }, start: function() { this._super.apply(this, arguments); if (this.field.translate) { - this.view.translatable_fields.push(this); this.$element.addClass('oe_form_field_translatable'); - this.$element.find('.oe_field_translate').click(this.on_translate); + this.$element.find('.oe_field_translate').click(_.bind(function() { + this.field_manager.open_translate_dialog(this); + }, this)); } - if (this.nolabel && openerp.connection.debug) { + if (instance.connection.debug) { this.do_attach_tooltip(this, this.$element); } + if (!this.disable_utility_classes) { + var set_required = function() { + this.$element.toggleClass('oe_form_required', this.get("required")); + }; + this.on("change:required", this, set_required); + _.bind(set_required, this)(); + } }, - set_value: function(value) { - this.value = value; - this.invalid = false; - this.update_dom(); - this.on_value_changed(); - }, - set_value_from_ui: function() { - this.on_value_changed(); - }, - on_value_changed: function() { - }, - on_translate: function() { - this.view.open_translate_dialog(this); + set_value: function(value_) { + this._inhibit_on_change = true; + this.set({'value': value_}); + this._inhibit_on_change = false; }, get_value: function() { - return this.value; + return this.get('value'); }, is_valid: function() { - return !this.invalid; + return this.is_syntax_valid() && (! this.get('required') || ! this.is_false()); }, - is_dirty: function() { - return this.dirty && !this.readonly; + is_syntax_valid: function() { + return true; }, - get_on_change_value: function() { - return this.get_value(); + /** + * Method useful to implement to ease validity testing. Must return true if the current + * value is similar to false in OpenERP. + */ + is_false: function() { + return this.get('value') === false; }, - update_dom: function(show_invalid) { - this._super.apply(this, arguments); + _check_css_flags: function(show_invalid) { if (this.field.translate) { - this.$element.find('.oe_field_translate').toggle(!!this.view.datarecord.id); + this.$element.find('.oe_field_translate').toggle(!this.field_manager.is_create_mode()); } if (!this.disable_utility_classes) { - this.$element.toggleClass('disabled', this.readonly); - this.$element.toggleClass('required', this.required); - if (show_invalid) { - this.$element.toggleClass('invalid', !this.is_valid()); + if (this.field_manager.get('display_invalid_fields')) { + this.$element.toggleClass('oe_form_invalid', !this.is_valid()); } } }, - on_ui_change: function() { - this.dirty = true; - this.validate(); - if (this.is_valid()) { - this.set_value_from_ui(); - this.view.do_onchange(this); - this.view.on_form_changed(true); - this.view.do_notify_change(); - } else { - this.update_dom(true); - } - }, - validate: function() { - this.invalid = false; + focus: function() { }, - focus: function($element) { - if ($element) { - setTimeout(function() { - $element.focus(); - }, 50); - } - }, - reset: function() { - this.dirty = false; + /** + * Utility method to focus an element, but only after a small amount of time. + */ + delay_focus: function($elem) { + setTimeout(function() { + $elem.focus(); + }, 50); }, + /** + * Utility method to get the widget options defined in the field xml description. + */ get_definition_options: function() { if (!this.definition_options) { var str = this.node.attrs.options || '{}'; this.definition_options = JSON.parse(str); } return this.definition_options; - } + }, + set_input_id: function(id) { + this.id_for_label = id; + }, }); -openerp.web.form.FieldChar = openerp.web.form.Field.extend({ - form_template: 'FieldChar', - init: function (view, node) { - this._super(view, node); - this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1'; - }, +/** + * A mixin to apply on any field that has to completely re-render when its readonly state + * switch. + */ +instance.web.form.ReinitializeFieldMixin = { + /** + * Default implementation of start(), use it or call explicitly initialize_field(). + */ start: function() { - this._super.apply(this, arguments); - this.$element.find('input').change(this.on_ui_change); + this._super(); + this.initialize_field(); }, - set_value: function(value) { - this._super.apply(this, arguments); - var show_value = openerp.web.format_value(value, this, ''); - this.$element.find('input').val(show_value); - return show_value; + initialize_field: function() { + this.on("change:effective_readonly", this, function() { + this.destroy_content(); + this.renderElement(); + this.initialize_content(); + this.render_value(); + }); + this.initialize_content(); + this.render_value(); }, - update_dom: function() { - this._super.apply(this, arguments); - this.$element.find('input').prop('readonly', this.readonly); + /** + * Called to destroy anything that could have been created previously, called before a + * re-initialization. + */ + destroy_content: function() {}, + /** + * Called to initialize the content. + */ + initialize_content: function() {}, + /** + * Called to render the value. Should also be explicitly called at the end of a set_value(). + */ + render_value: function() {}, +}; + +instance.web.form.FieldChar = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { + template: 'FieldChar', + init: function (field_manager, node) { + this._super(field_manager, node); + this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1'; }, - set_value_from_ui: function() { - this.value = openerp.web.parse_value(this.$element.find('input').val(), this); - this._super(); + initialize_content: function() { + var self = this; + this.$element.find('input').change(function() { + self.set({'value': instance.web.parse_value(self.$element.find('input').val(), self)}); + }); }, - validate: function() { - this.invalid = false; - try { - var value = openerp.web.parse_value(this.$element.find('input').val(), this, ''); - this.invalid = this.required && value === ''; - } catch(e) { - this.invalid = true; + set_value: function(value_) { + this._super(value_); + this.render_value(); + }, + render_value: function() { + var show_value = instance.web.format_value(this.get('value'), this, ''); + if (!this.get("effective_readonly")) { + this.$element.find('input').val(show_value); + } else { + if (this.password) { + show_value = new Array(show_value.length + 1).join('*'); + } + this.$element.text(show_value); } }, - focus: function($element) { - this._super($element || this.$element.find('input:first')); + is_syntax_valid: function() { + if (!this.get("effective_readonly")) { + try { + var value_ = instance.web.parse_value(this.$element.find('input').val(), this, ''); + return true; + } catch(e) { + return false; + } + } + return true; + }, + is_false: function() { + return this.get('value') === ''; + }, + focus: function() { + this.delay_focus(this.$element.find('input:first')); } -}); +})); -openerp.web.form.FieldID = openerp.web.form.FieldChar.extend({ - update_dom: function() { - this._super.apply(this, arguments); - this.$element.find('input').prop('readonly', true); - } +instance.web.form.FieldID = instance.web.form.FieldChar.extend({ + }); -openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({ - form_template: 'FieldEmail', - start: function() { - this._super.apply(this, arguments); +instance.web.form.FieldEmail = instance.web.form.FieldChar.extend({ + template: 'FieldEmail', + initialize_content: function() { + this._super(); this.$element.find('button').click(this.on_button_clicked); }, + render_value: function() { + if (!this.get("effective_readonly")) { + this._super(); + } else { + this.$element.find('a') + .attr('href', 'mailto:' + this.get('value')) + .text(this.get('value')); + } + }, on_button_clicked: function() { - if (!this.value || !this.is_valid()) { + if (!this.get('value') || !this.is_syntax_valid()) { this.do_warn("E-mail error", "Can't send email to invalid e-mail address"); } else { - location.href = 'mailto:' + this.value; + location.href = 'mailto:' + this.get('value'); } } }); -openerp.web.form.FieldUrl = openerp.web.form.FieldChar.extend({ - form_template: 'FieldUrl', - start: function() { - this._super.apply(this, arguments); +instance.web.form.FieldUrl = instance.web.form.FieldChar.extend({ + template: 'FieldUrl', + initialize_content: function() { + this._super(); this.$element.find('button').click(this.on_button_clicked); }, + render_value: function() { + if (!this.get("effective_readonly")) { + this._super(); + } else { + var tmp = this.get('value'); + var s = /(\w+):(.+)/.exec(tmp); + if (!s) { + tmp = "http://" + this.get('value'); + } + this.$element.find('a').attr('href', tmp).text(tmp); + } + }, on_button_clicked: function() { - if (!this.value) { + if (!this.get('value')) { this.do_warn("Resource error", "This resource is empty"); } else { - window.open(this.value); + var url = $.trim(this.get('value')); + if(/^www\./i.test(url)) + url = 'http://'+url; + window.open(url); } } }); -openerp.web.form.FieldFloat = openerp.web.form.FieldChar.extend({ - init: function (view, node) { - this._super(view, node); - if (node.attrs.digits) { +instance.web.form.FieldFloat = instance.web.form.FieldChar.extend({ + is_field_number: true, + init: function (field_manager, node) { + this._super(field_manager, node); + this.set({'value': 0}); + if (this.node.attrs.digits) { this.digits = py.eval(node.attrs.digits); } else { - this.digits = view.fields_view.fields[node.attrs.name].digits; + this.digits = this.field.digits; } }, - set_value: function(value) { - if (value === false || value === undefined) { + set_value: function(value_) { + if (value_ === false || value_ === undefined) { // As in GTK client, floats default to 0 - value = 0; + value_ = 0; } - this._super.apply(this, [value]); + this._super.apply(this, [value_]); } }); -openerp.web.DateTimeWidget = openerp.web.OldWidget.extend({ +instance.web.DateTimeWidget = instance.web.OldWidget.extend({ template: "web.datetimepicker", jqueryui_object: 'datetimepicker', type_of_date: "datetime", @@ -1640,46 +1942,46 @@ openerp.web.DateTimeWidget = openerp.web.OldWidget.extend({ showButtonPanel: true }); this.$element.find('img.oe_datepicker_trigger').click(function() { - if (!self.readonly && !self.picker('widget').is(':visible')) { - self.picker('setDate', self.value ? openerp.web.auto_str_to_date(self.value) : new Date()); + if (!self.get("effective_readonly") && !self.picker('widget').is(':visible')) { + self.picker('setDate', self.get('value') ? instance.web.auto_str_to_date(self.get('value')) : new Date()); self.$input_picker.show(); self.picker('show'); self.$input_picker.hide(); } }); this.set_readonly(false); - this.value = false; + this.set({'value': false}); }, picker: function() { return $.fn[this.jqueryui_object].apply(this.$input_picker, arguments); }, - on_picker_select: function(text, instance) { + on_picker_select: function(text, instance_) { var date = this.picker('getDate'); this.$input.val(date ? this.format_client(date) : '').change(); }, - set_value: function(value) { - this.value = value; - this.$input.val(value ? this.format_client(value) : ''); + set_value: function(value_) { + this.set({'value': value_}); + this.$input.val(value_ ? this.format_client(value_) : ''); }, get_value: function() { - return this.value; + return this.get('value'); }, - set_value_from_ui: function() { - var value = this.$input.val() || false; - this.value = this.parse_client(value); + set_value_from_ui_: function() { + var value_ = this.$input.val() || false; + this.set({'value': this.parse_client(value_)}); }, set_readonly: function(readonly) { this.readonly = readonly; this.$input.prop('readonly', this.readonly); this.$element.find('img.oe_datepicker_trigger').toggleClass('oe_input_icon_disabled', readonly); }, - is_valid: function(required) { - var value = this.$input.val(); - if (value === "") { - return !required; + is_valid_: function() { + var value_ = this.$input.val(); + if (value_ === "") { + return true; } else { try { - this.parse_client(value); + this.parse_client(value_); return true; } catch(e) { return false; @@ -1687,99 +1989,120 @@ openerp.web.DateTimeWidget = openerp.web.OldWidget.extend({ } }, parse_client: function(v) { - return openerp.web.parse_value(v, {"widget": this.type_of_date}); + return instance.web.parse_value(v, {"widget": this.type_of_date}); }, format_client: function(v) { - return openerp.web.format_value(v, {"widget": this.type_of_date}); + return instance.web.format_value(v, {"widget": this.type_of_date}); }, on_change: function() { - if (this.is_valid()) { - this.set_value_from_ui(); + if (this.is_valid_()) { + this.set_value_from_ui_(); } } }); -openerp.web.DateWidget = openerp.web.DateTimeWidget.extend({ +instance.web.DateWidget = instance.web.DateTimeWidget.extend({ jqueryui_object: 'datepicker', type_of_date: "date" }); -openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({ - form_template: "EmptyComponent", +instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { + template: "EmptyComponent", build_widget: function() { - return new openerp.web.DateTimeWidget(this); + return new instance.web.DateTimeWidget(this); }, - start: function() { - var self = this; - this._super.apply(this, arguments); - this.datewidget = this.build_widget(); - this.datewidget.on_change.add_last(this.on_ui_change); - this.datewidget.appendTo(this.$element); + destroy_content: function() { + if (this.datewidget) { + this.datewidget.destroy(); + this.datewidget = undefined; + } + }, + initialize_content: function() { + if (!this.get("effective_readonly")) { + this.datewidget = this.build_widget(); + this.datewidget.on_change.add_last(_.bind(function() { + this.set({'value': this.datewidget.get_value()}); + }, this)); + this.datewidget.appendTo(this.$element); + } }, - set_value: function(value) { - this._super(value); - this.datewidget.set_value(value); + set_value: function(value_) { + this._super(value_); + this.render_value(); }, - get_value: function() { - return this.datewidget.get_value(); + render_value: function() { + if (!this.get("effective_readonly")) { + this.datewidget.set_value(this.get('value')); + } else { + this.$element.text(instance.web.format_value(this.get('value'), this, '')); + } }, - update_dom: function() { - this._super.apply(this, arguments); - this.datewidget.set_readonly(this.readonly); + is_syntax_valid: function() { + if (!this.get("effective_readonly")) { + return this.datewidget.is_valid_(); + } + return true; }, - validate: function() { - this.invalid = !this.datewidget.is_valid(this.required); + is_false: function() { + return this.get('value') === ''; }, - focus: function($element) { - this._super($element || this.datewidget.$input); + focus: function() { + if (this.datewidget && this.datewidget.$input) + this.delay_focus(this.datewidget.$input); } -}); +})); -openerp.web.form.FieldDate = openerp.web.form.FieldDatetime.extend({ +instance.web.form.FieldDate = instance.web.form.FieldDatetime.extend({ build_widget: function() { - return new openerp.web.DateWidget(this); + return new instance.web.DateWidget(this); } }); -openerp.web.form.FieldText = openerp.web.form.Field.extend({ - form_template: 'FieldText', - start: function() { - this._super.apply(this, arguments); - this.$element.find('textarea').change(this.on_ui_change); +instance.web.form.FieldText = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { + template: 'FieldText', + initialize_content: function() { + this.$textarea = this.$element.find('textarea'); this.resized = false; + if (!this.get("effective_readonly")) { + this.$textarea.change(_.bind(function() { + this.set({'value': instance.web.parse_value(this.$textarea.val(), this)}); + }, this)); + } else { + this.$textarea.attr('disabled', 'disabled'); + } }, - set_value: function(value) { + set_value: function(value_) { this._super.apply(this, arguments); - var show_value = openerp.web.format_value(value, this, ''); - this.$element.find('textarea').val(show_value); + this.render_value(); + }, + render_value: function() { + var show_value = instance.web.format_value(this.get('value'), this, ''); + this.$textarea.val(show_value); if (!this.resized && this.view.options.resize_textareas) { this.do_resize(this.view.options.resize_textareas); this.resized = true; } }, - update_dom: function() { - this._super.apply(this, arguments); - this.$element.find('textarea').prop('readonly', this.readonly); - }, - set_value_from_ui: function() { - this.value = openerp.web.parse_value(this.$element.find('textarea').val(), this); - this._super(); - }, - validate: function() { - this.invalid = false; - try { - var value = openerp.web.parse_value(this.$element.find('textarea').val(), this, ''); - this.invalid = this.required && value === ''; - } catch(e) { - this.invalid = true; + is_syntax_valid: function() { + if (!this.get("effective_readonly")) { + try { + var value_ = instance.web.parse_value(this.$textarea.val(), this, ''); + return true; + } catch(e) { + return false; + } } + return true; + }, + is_false: function() { + return this.get('value') === ''; }, focus: function($element) { - this._super($element || this.$element.find('textarea:first')); + this.delay_focus(this.$textarea); }, do_resize: function(max_height) { max_height = parseInt(max_height, 10); - var $input = this.$element.find('textarea'), + var $input = this.$textarea, $div = $('<div style="position: absolute; z-index: 1000; top: 0"/>').width($input.width()), new_height; $div.text($input.val()); @@ -1797,64 +2120,60 @@ openerp.web.form.FieldText = openerp.web.form.Field.extend({ $div.remove(); $input.height(new_height); }, - reset: function() { - this.resized = false; - } -}); +})); -openerp.web.form.FieldBoolean = openerp.web.form.Field.extend({ - form_template: 'FieldBoolean', +instance.web.form.FieldBoolean = instance.web.form.AbstractField.extend({ + template: 'FieldBoolean', start: function() { - var self = this; this._super.apply(this, arguments); - this.$element.find('input').click(self.on_ui_change); - }, - set_value: function(value) { - this._super.apply(this, arguments); - this.$element.find('input')[0].checked = value; - }, - set_value_from_ui: function() { - this.value = this.$element.find('input').is(':checked'); - this._super(); + this.$checkbox = $("input", this.$element); + this.$element.click(_.bind(function() { + this.set({'value': this.$checkbox.is(':checked')}); + }, this)); + var check_readonly = function() { + this.$checkbox.prop('disabled', this.get("effective_readonly")); + }; + this.on("change:effective_readonly", this, check_readonly); + _.bind(check_readonly, this)(); }, - update_dom: function() { + set_value: function(value_) { this._super.apply(this, arguments); - this.$element.find('input').prop('disabled', this.readonly); + this.$checkbox[0].checked = value_; }, - focus: function($element) { - this._super($element || this.$element.find('input:first')); + focus: function() { + this.delay_focus(this.$checkbox); } }); -openerp.web.form.FieldProgressBar = openerp.web.form.Field.extend({ - form_template: 'FieldProgressBar', +instance.web.form.FieldProgressBar = instance.web.form.AbstractField.extend({ + template: 'FieldProgressBar', start: function() { this._super.apply(this, arguments); - this.$element.find('div').progressbar({ - value: this.value, - disabled: this.readonly + this.$element.progressbar({ + value: this.get('value'), + disabled: this.get("effective_readonly") }); }, - set_value: function(value) { + set_value: function(value_) { this._super.apply(this, arguments); - var show_value = Number(value); + var show_value = Number(value_); if (isNaN(show_value)) { show_value = 0; } - var formatted_value = openerp.web.format_value(show_value, { type : 'float' }, '0'); - this.$element.find('div').progressbar('option', 'value', show_value).find('span').html(formatted_value + '%'); + var formatted_value = instance.web.format_value(show_value, { type : 'float' }, '0'); + this.$element.progressbar('option', 'value', show_value).find('span').html(formatted_value + '%'); } }); -openerp.web.form.FieldTextXml = openerp.web.form.Field.extend({ +instance.web.form.FieldTextXml = instance.web.form.AbstractField.extend({ // to replace view editor }); -openerp.web.form.FieldSelection = openerp.web.form.Field.extend({ - form_template: 'FieldSelection', - init: function(view, node) { +instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { + template: 'FieldSelection', + init: function(field_manager, node) { var self = this; - this._super(view, node); + this._super(field_manager, node); this.values = _.clone(this.field.selection); _.each(this.values, function(v, i) { if (v[0] === false && v[1] === '') { @@ -1863,7 +2182,7 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({ }); this.values.unshift([false, '']); }, - start: function() { + initialize_content: function() { // Flag indicating whether we're in an event chain containing a change // event on the select, in order to know what to do on keyup[RETURN]: // * If the user presses [RETURN] as part of changing the value of a @@ -1876,9 +2195,10 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({ // changing the selected value), takes the action as validating the // row var ischanging = false; - this._super.apply(this, arguments); this.$element.find('select') - .change(this.on_ui_change) + .change(_.bind(function() { + this.set({'value': this.values[this.$element.find('select')[0].selectedIndex][0]}); + }, this)) .change(function () { ischanging = true; }) .click(function () { ischanging = false; }) .keyup(function (e) { @@ -1887,32 +2207,37 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({ ischanging = false; }); }, - set_value: function(value) { - value = value === null ? false : value; - value = value instanceof Array ? value[0] : value; - this._super(value); - var index = 0; - for (var i = 0, ii = this.values.length; i < ii; i++) { - if (this.values[i][0] === value) index = i; - } - this.$element.find('select')[0].selectedIndex = index; - }, - set_value_from_ui: function() { - this.value = this.values[this.$element.find('select')[0].selectedIndex][0]; - this._super(); + set_value: function(value_) { + value_ = value_ === null ? false : value_; + value_ = value_ instanceof Array ? value_[0] : value_; + this._super(value_); + this.render_value(); }, - update_dom: function() { - this._super.apply(this, arguments); - this.$element.find('select').prop('disabled', this.readonly); + render_value: function() { + if (!this.get("effective_readonly")) { + var index = 0; + for (var i = 0, ii = this.values.length; i < ii; i++) { + if (this.values[i][0] === this.get('value')) index = i; + } + this.$element.find('select')[0].selectedIndex = index; + } else { + var self = this; + var option = _(this.values) + .detect(function (record) { return record[0] === self.get('value'); }); + this.$element.text(option ? option[1] : this.values[0][1]); + } }, - validate: function() { - var value = this.values[this.$element.find('select')[0].selectedIndex]; - this.invalid = !(value && !(this.required && value[0] === false)); + is_syntax_valid: function() { + if (this.get("effective_readonly")) { + return true; + } + var value_ = this.values[this.$element.find('select')[0].selectedIndex]; + return !! value_; }, - focus: function($element) { - this._super($element || this.$element.find('select:first')); + focus: function() { + this.delay_focus(this.$element.find('select:first')); } -}); +})); // jquery autocomplete tweak to allow html (function() { @@ -1921,8 +2246,8 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({ function filter( array, term ) { var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" ); - return $.grep( array, function(value) { - return matcher.test( $( "<div>" ).html( value.label || value.value || value ).text() ); + return $.grep( array, function(value_) { + return matcher.test( $( "<div>" ).html( value_.label || value_.value || value_ ).text() ); }); } @@ -1946,114 +2271,88 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({ }); })(); -openerp.web.form.dialog = function(content, options) { +instance.web.form.dialog = function(content, options) { options = _.extend({ width: '90%', height: 'auto', min_width: '800px' }, options || {}); - var dialog = new openerp.web.Dialog(null, options, content).open(); + var dialog = new instance.web.Dialog(null, options, content).open(); return dialog.$element; }; -openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ - form_template: 'FieldMany2One', - init: function(view, node) { - this._super(view, node); +instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { + template: "FieldMany2One", + init: function(field_manager, node) { + this._super(field_manager, node); this.limit = 7; - this.value = null; - this.cm_id = _.uniqueId('m2o_cm_'); + this.set({'value': false}); + this.display_value = {}; this.last_search = []; - this.tmp_value = undefined; + this.floating = false; + this.inhibit_on_change = false; this.orderer = new openerp.web.DropMisordered(); }, start: function() { this._super(); + instance.web.form.ReinitializeFieldMixin.start.call(this); + this.on("change:value", this, function() { + this.floating = false; + this.render_value(); + }); + }, + initialize_content: function() { + if (!this.get("effective_readonly")) + this.render_editable(); + this.render_value(); + }, + render_editable: function() { var self = this; this.$input = this.$element.find("input"); + + self.$input.tipsy({ + title: function() { + return "No element was selected, you should create or select one from the dropdown list."; + }, + trigger:'manual', + fade: true, + }); + this.$drop_down = this.$element.find(".oe-m2o-drop-down-button"); - this.$menu_btn = this.$element.find(".oe-m2o-cm-button"); - - // context menu - var init_context_menu_def = $.Deferred().then(function(e) { - var rdataset = new openerp.web.DataSet(self, "ir.values", self.build_context()); - rdataset.call("get", ['action', 'client_action_relate', - [[self.field.relation, false]], false, rdataset.get_context()], false, 0) - .then(function(result) { - self.related_entries = result; - - var $cmenu = $("#" + self.cm_id); - $cmenu.append(QWeb.render("FieldMany2One.context_menu", {widget: self})); - var bindings = {}; - bindings[self.cm_id + "_search"] = function() { - if (self.readonly) - return; - self._search_create_popup("search"); - }; - bindings[self.cm_id + "_create"] = function() { - if (self.readonly) - return; - self._search_create_popup("form"); - }; - bindings[self.cm_id + "_open"] = function() { - if (!self.value) { - return; - } - var pop = new openerp.web.form.FormOpenPopup(self.view); - pop.show_element( - self.field.relation, - self.value[0], - self.build_context(), - { - title: _t("Open: ") + (self.string || self.name) - } - ); - pop.on_write_completed.add_last(function() { - self.set_value(self.value[0]); - }); - }; - _.each(_.range(self.related_entries.length), function(i) { - bindings[self.cm_id + "_related_" + i] = function() { - self.open_related(self.related_entries[i]); - }; - }); - var cmenu = self.$menu_btn.contextMenu(self.cm_id, {'noRightClick': true, - bindings: bindings, itemStyle: {"color": ""}, - onContextMenu: function() { - if(self.value) { - $("#" + self.cm_id + " .oe_m2o_menu_item_mandatory").removeClass("oe-m2o-disabled-cm"); - } else { - $("#" + self.cm_id + " .oe_m2o_menu_item_mandatory").addClass("oe-m2o-disabled-cm"); - } - if (!self.readonly) { - $("#" + self.cm_id + " .oe_m2o_menu_item_noreadonly").removeClass("oe-m2o-disabled-cm"); - } else { - $("#" + self.cm_id + " .oe_m2o_menu_item_noreadonly").addClass("oe-m2o-disabled-cm"); - } - return true; - }, menuStyle: {width: "200px"} - }); - $.async_when().then(function() {self.$menu_btn.trigger(e);}); + this.$follow_button = $(".oe-m2o-cm-button", this.$element); + + this.$follow_button.click(function() { + if (!self.get('value')) { + return; + } + var pop = new instance.web.form.FormOpenPopup(self.view); + pop.show_element( + self.field.relation, + self.get("value"), + self.build_context(), + { + title: _t("Open: ") + (self.string || self.name) + } + ); + pop.on_write_completed.add_last(function() { + self.display_value = {}; + self.render_value(); }); }); - var ctx_callback = function(e) {init_context_menu_def.resolve(e); e.preventDefault()}; - this.$menu_btn.click(ctx_callback); // some behavior for input this.$input.keyup(function() { if (self.$input.val() === "") { - self._change_int_value(null); - } else if (self.value === null || (self.value && self.$input.val() !== self.value[1])) { - self._change_int_value(undefined); + self.set({value: false}); + } else { + self.floating = true; } }); this.$drop_down.click(function() { - if (self.readonly) - return; if (self.$input.autocomplete("widget").is(":visible")) { self.$input.autocomplete("close"); } else { - if (self.value) { + if (self.get("value") && ! self.floating) { self.$input.autocomplete("search", ""); } else { self.$input.autocomplete("search"); @@ -2061,16 +2360,43 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ self.$input.focus(); } }); + var tip_def = $.Deferred(); + var untip_def = $.Deferred(); + var tip_delay = 200; + var tip_duration = 3000; var anyoneLoosesFocus = function() { - if (!self.$input.is(":focus") && - !self.$input.autocomplete("widget").is(":visible") && - !self.value) { - if (self.value === undefined && self.last_search.length > 0) { - self._change_int_ext_value(self.last_search[0]); + if (self.floating) { + if (self.last_search.length > 0) { + if (self.last_search[0][0] != self.get("value")) { + self.display_value = {}; + self.display_value["" + self.last_search[0][0]] = self.last_search[0][1]; + self.set({value: self.last_search[0][0]}); + } else { + self.render_value(); + } } else { - self._change_int_ext_value(null); + self.set({value: false}); } } + if (! self.get("value")) { + tip_def.reject(); + untip_def.reject(); + tip_def = $.Deferred(); + tip_def.then(function() { + self.$input.tipsy("show"); + }); + setTimeout(function() { + tip_def.resolve(); + untip_def.reject(); + untip_def = $.Deferred(); + untip_def.then(function() { + self.$input.tipsy("hide"); + }); + setTimeout(function() {untip_def.resolve();}, tip_duration); + }, tip_delay); + } else { + tip_def.reject(); + } }; this.$input.focusout(anyoneLoosesFocus); @@ -2082,9 +2408,11 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ isSelecting = true; var item = ui.item; if (item.id) { - self._change_int_value([item.id, item.name]); + self.display_value = {}; + self.display_value["" + item.id] = item.name; + self.set({value: item.id}); } else if (item.action) { - self._change_int_value(undefined); + self.floating = true; item.action(); return false; } @@ -2112,7 +2440,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ var search_val = request.term; var self = this; - var dataset = new openerp.web.DataSet(this, this.field.relation, self.build_context()); + var dataset = new instance.web.DataSet(this, this.field.relation, self.build_context()); this.orderer.add(dataset.name_search( search_val, self.build_domain(), 'ilike', this.limit + 1)).then(function(data) { @@ -2133,7 +2461,6 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ values.push({label: _t("<em>Â Â Â Search More...</em>"), action: function() { dataset.name_search(search_val, self.build_domain(), 'ilike' , false, function(data) { - self._change_int_value(null); self._search_create_popup("search", data); }); }}); @@ -2142,7 +2469,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ var raw_result = _(data.result).map(function(x) {return x[1];}); if (search_val.length > 0 && !_.include(raw_result, search_val) && - (!self.value || search_val !== self.value[1])) { + (!self.get("value") || self.floating)) { values.push({label: _.str.sprintf(_t('<em>Â Â Â Create "<strong>%s</strong>"</em>'), $('<span />').text(search_val).html()), action: function() { self._quick_create(search_val); @@ -2150,7 +2477,6 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ } // create... values.push({label: _t("<em>Â Â Â Create and Edit...</em>"), action: function() { - self._change_int_value(null); self._search_create_popup("form", undefined, {"default_name": search_val}); }}); @@ -2160,13 +2486,14 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ _quick_create: function(name) { var self = this; var slow_create = function () { - self._change_int_value(null); self._search_create_popup("form", undefined, {"default_name": name}); }; if (self.get_definition_options().quick_create === undefined || self.get_definition_options().quick_create) { - new openerp.web.DataSet(this, this.field.relation, self.build_context()) + new instance.web.DataSet(this, this.field.relation, self.build_context()) .name_create(name, function(data) { - self._change_int_ext_value(data); + self.display_value = {}; + self.display_value["" + data[0]] = data[1]; + self.set({value: data[0]}); }).fail(function(error, event) { event.preventDefault(); slow_create(); @@ -2177,7 +2504,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ // all search/create popup handling _search_create_popup: function(view, ids, context) { var self = this; - var pop = new openerp.web.form.SelectCreatePopup(this); + var pop = new instance.web.form.SelectCreatePopup(this); pop.select_element( self.field.relation, { @@ -2187,100 +2514,71 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ disable_multiple_selection: true }, self.build_domain(), - new openerp.web.CompoundContext(self.build_context(), context || {}) + new instance.web.CompoundContext(self.build_context(), context || {}) ); pop.on_select_elements.add(function(element_ids) { - new openerp.web.DataSet(self, self.field.relation, self.build_context()) - .name_get([element_ids[0]], function(data) { - self._change_int_ext_value(data[0]); - }); + self.set({value: element_ids[0]}); }); }, - _change_int_ext_value: function(value) { - this._change_int_value(value); - this.$input.val(this.value ? this.value[1] : ""); - }, - _change_int_value: function(value) { - this.value = value; - var back_orig_value = this.original_value; - if (this.value === null || this.value) { - this.original_value = this.value; + render_value: function(no_recurse) { + var self = this; + if (! this.get("value")) { + this.display_string(""); + return; } - if (back_orig_value === undefined) { // first use after a set_value() + var display = this.display_value["" + this.get("value")]; + if (display) { + this.display_string(display); return; } - if (this.value !== undefined && ((back_orig_value ? back_orig_value[0] : null) - !== (this.value ? this.value[0] : null))) { - this.on_ui_change(); + if (! no_recurse) { + var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.view.dataset.get_context()); + dataset.name_get([self.get("value")], function(data) { + self.display_value["" + self.get("value")] = data[0][1]; + self.render_value(true); + }); } }, - set_value: function(value) { - value = value || null; - this.invalid = false; + display_string: function(str) { var self = this; - this.tmp_value = value; - self.update_dom(); - self.on_value_changed(); - var real_set_value = function(rval) { - self.tmp_value = undefined; - self.value = rval; - self.original_value = undefined; - self._change_int_ext_value(rval); - }; - if (value && !(value instanceof Array)) { - // name_get in a m2o does not use the context of the field - new openerp.web.DataSet(this, this.field.relation, self.view.dataset.get_context()) - .name_get([value], function(data) { - real_set_value(data[0]); - }).fail(function() {self.tmp_value = undefined;}); + if (!this.get("effective_readonly")) { + this.$input.val(str); } else { - $.async_when().then(function() {real_set_value(value);}); - } - }, - get_value: function() { - if (this.tmp_value !== undefined) { - if (this.tmp_value instanceof Array) { - return this.tmp_value[0]; - } - return this.tmp_value ? this.tmp_value : false; - } - if (this.value === undefined) - return this.original_value ? this.original_value[0] : false; - return this.value ? this.value[0] : false; - }, - validate: function() { - this.invalid = false; - var val = this.tmp_value !== undefined ? this.tmp_value : this.value; - if (val === null) { - this.invalid = this.required; + this.$element.find('a') + .unbind('click') + .text(str) + .click(function () { + self.do_action({ + type: 'ir.actions.act_window', + res_model: self.field.relation, + res_id: self.get("value"), + context: self.build_context(), + views: [[false, 'form']], + target: 'current' + }); + return false; + }); } }, - open_related: function(related) { + set_value: function(value_) { var self = this; - if (!self.value) - return; - var additional_context = { - active_id: self.value[0], - active_ids: [self.value[0]], - active_model: self.field.relation - }; - self.rpc("/web/action/load", { - action_id: related[2].id, - context: additional_context - }, function(result) { - result.result.context = _.extend(result.result.context || {}, additional_context); - self.do_action(result.result); - }); + if (value_ instanceof Array) { + this.display_value = {}; + this.display_value["" + value_[0]] = value_[1]; + value_ = value_[0]; + } + value_ = value_ || false; + this.inhibit_on_change = true; + this._super(value_); + this.inhibit_on_change = false; }, - focus: function ($element) { - this._super($element || this.$input); + is_false: function() { + return ! this.get("value"); }, - update_dom: function() { - this._super.apply(this, arguments); - this.$input.prop('readonly', this.readonly); + focus: function () { + this.delay_focus(this.$input); } -}); -openerp.web.check_interface(openerp.web.form.FieldMany2One, openerp.web.form.FieldInterface); +})); /* # Values: (0, 0, { fields }) create @@ -2327,24 +2625,24 @@ var commands = { return [6, false, ids]; } }; -openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ - form_template: 'FieldOne2Many', +instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ multi_selection: false, - init: function(view, node) { - this._super(view, node); + disable_utility_classes: true, + init: function(field_manager, node) { + this._super(field_manager, node); + lazy_build_o2m_kanban_view(); this.is_loaded = $.Deferred(); this.initial_is_loaded = this.is_loaded; this.is_setted = $.Deferred(); this.form_last_update = $.Deferred(); this.init_form_last_update = this.form_last_update; - this.disable_utility_classes = true; }, start: function() { this._super.apply(this, arguments); var self = this; - this.dataset = new openerp.web.form.One2ManyDataSet(this, this.field.relation); + this.dataset = new instance.web.form.One2ManyDataSet(this, this.field.relation); this.dataset.o2m = this; this.dataset.parent_view = this.view; this.dataset.child_name = this.name; @@ -2356,16 +2654,23 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ this.is_setted.then(function() { self.load_views(); }); + this.is_loaded.then(function() { + self.on("change:effective_readonly", self, function() { + self.is_loaded = self.is_loaded.pipe(function() { + self.viewmanager.destroy(); + return $.when(self.load_views()).then(function() { + self.reload_current_view(); + }); + }); + }); + }); }, trigger_on_change: function() { var tmp = this.doing_on_change; this.doing_on_change = true; - this.on_ui_change(); + this.trigger('changed_value'); this.doing_on_change = tmp; }, - is_readonly: function() { - return this.readonly || this.force_readonly; - }, load_views: function() { var self = this; @@ -2376,21 +2681,21 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ var view = { view_id: false, view_type: mode == "tree" ? "list" : mode, - options: { sidebar : false } + options: {} }; if (self.field.views && self.field.views[mode]) { view.embedded_view = self.field.views[mode]; } if(view.view_type === "list") { view.options.selectable = self.multi_selection; - if (self.is_readonly()) { + if (self.get("effective_readonly")) { view.options.addable = null; view.options.deletable = null; view.options.isClarkGable = false; } } else if (view.view_type === "form") { - if (self.is_readonly()) { - view.view_type = 'page'; + if (self.get("effective_readonly")) { + view.view_type = 'form'; } view.options.not_interactible_on_create = true; } @@ -2398,12 +2703,14 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ }); this.views = views; - this.viewmanager = new openerp.web.ViewManager(this, this.dataset, views, {}); + this.viewmanager = new instance.web.ViewManager(this, this.dataset, views, { + $sidebar: false, + }); this.viewmanager.template = 'One2Many.viewmanager'; - this.viewmanager.registry = openerp.web.views.extend({ - list: 'openerp.web.form.One2ManyListView', - form: 'openerp.web.form.One2ManyFormView', - page: 'openerp.web.PageView' + this.viewmanager.registry = instance.web.views.extend({ + list: 'instance.web.form.One2ManyListView', + form: 'instance.web.form.One2ManyFormView', + kanban: 'instance.web.form.One2ManyKanbanView', }); var once = $.Deferred().then(function() { self.init_form_last_update.resolve(); @@ -2412,12 +2719,12 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ self.initial_is_loaded.resolve(); }); this.viewmanager.on_controller_inited.add_last(function(view_type, controller) { + controller.o2m = self; if (view_type == "list") { - controller.o2m = self; - if (self.is_readonly()) + if (self.get("effective_readonly")) controller.set_editable(false); - } else if (view_type == "form" || view_type == 'page') { - if (view_type == 'page' || self.is_readonly()) { + } else if (view_type === "form") { + if (self.get("effective_readonly")) { $(".oe_form_buttons", controller.$element).children().remove(); } controller.on_record_loaded.add_last(function() { @@ -2451,7 +2758,7 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ var view = self.viewmanager.views[active_view].controller; if(active_view === "list") { return view.reload_content(); - } else if (active_view === "form" || active_view === 'page') { + } else if (active_view === "form") { if (self.dataset.index === null && self.dataset.ids.length >= 1) { self.dataset.index = 0; } @@ -2460,18 +2767,18 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ }; self.form_last_update = self.form_last_update.pipe(act, act); return self.form_last_update; - } else if (active_view === "graph") { + } else if (view.do_search) { return view.do_search(self.build_domain(), self.dataset.get_context(), []); } }, undefined); }, - set_value: function(value) { - value = value || []; + set_value: function(value_) { + value_ = value_ || []; var self = this; this.dataset.reset_ids([]); - if(value.length >= 1 && value[0] instanceof Array) { + if(value_.length >= 1 && value_[0] instanceof Array) { var ids = []; - _.each(value, function(command) { + _.each(value_, function(command) { var obj = {values: command[2]}; switch (command[0]) { case commands.CREATE: @@ -2500,10 +2807,10 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ }); this._super(ids); this.dataset.set_ids(ids); - } else if (value.length >= 1 && typeof(value[0]) === "object") { + } else if (value_.length >= 1 && typeof(value_[0]) === "object") { var ids = []; this.dataset.delete_all = true; - _.each(value, function(command) { + _.each(value_, function(command) { var obj = {values: command}; obj['id'] = _.uniqueId(self.dataset.virtual_id_prefix); obj.defaults = {}; @@ -2514,8 +2821,8 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ this._super(ids); this.dataset.set_ids(ids); } else { - this._super(value); - this.dataset.reset_ids(value); + this._super(value_); + this.dataset.reset_ids(value_); } if (this.dataset.index === null && this.dataset.ids.length > 0) { this.dataset.index = 0; @@ -2572,61 +2879,37 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ return false; }, this)); }, - is_valid: function() { - this.validate(); - return this._super(); - }, - validate: function() { - this.invalid = false; + is_syntax_valid: function() { if (!this.viewmanager.views[this.viewmanager.active_view]) - return; + return true; var view = this.viewmanager.views[this.viewmanager.active_view].controller; if (this.viewmanager.active_view === "form") { for (var f in view.fields) { f = view.fields[f]; if (!f.is_valid()) { - this.invalid = true; - return; + return false; } } } + return true; }, - is_dirty: function() { - this.save_any_view(); - return this._super(); - }, - update_dom: function() { - this._super.apply(this, arguments); - var self = this; - if (this.previous_readonly !== this.readonly) { - this.previous_readonly = this.readonly; - if (this.viewmanager) { - this.is_loaded = this.is_loaded.pipe(function() { - self.viewmanager.destroy(); - return $.when(self.load_views()).then(function() { - self.reload_current_view(); - }); - }); - } - } - } }); -openerp.web.form.One2ManyDataSet = openerp.web.BufferedDataSet.extend({ +instance.web.form.One2ManyDataSet = instance.web.BufferedDataSet.extend({ get_context: function() { this.context = this.o2m.build_context([this.o2m.name]); return this.context; } }); -openerp.web.form.One2ManyListView = openerp.web.ListView.extend({ +instance.web.form.One2ManyListView = instance.web.ListView.extend({ _template: 'One2Many.listview', do_add_record: function () { if (this.options.editable) { this._super.apply(this, arguments); } else { var self = this; - var pop = new openerp.web.form.SelectCreatePopup(this); + var pop = new instance.web.form.SelectCreatePopup(this); pop.on_default_get.add(self.dataset.on_default_get); pop.select_element( self.o2m.field.relation, @@ -2657,7 +2940,7 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({ }, do_activate_record: function(index, id) { var self = this; - var pop = new openerp.web.form.FormOpenPopup(self.o2m.view); + var pop = new instance.web.form.FormOpenPopup(self.o2m.view); pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(), { title: _t("Open: ") + self.name, auto_write: false, @@ -2668,7 +2951,7 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({ return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments); }, form_view_options: {'not_interactible_on_create':true}, - readonly: self.o2m.is_readonly() + readonly: self.o2m.get("effective_readonly") }); pop.on_write.add(function(id, data) { self.o2m.dataset.write(id, data, {}, function(r) { @@ -2683,12 +2966,12 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({ } }); -openerp.web.form.One2ManyFormView = openerp.web.FormView.extend({ +instance.web.form.One2ManyFormView = instance.web.FormView.extend({ form_template: 'One2Many.formview', on_loaded: function(data) { this._super(data); var self = this; - this.$form_header.find('button.oe_form_button_create').click(function() { + this.$buttons.find('button.oe_form_button_create').click(function() { self.do_save().then(self.on_button_new); }); }, @@ -2701,12 +2984,43 @@ openerp.web.form.One2ManyFormView = openerp.web.FormView.extend({ } }); -openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({ - form_template: 'FieldMany2Many', +var lazy_build_o2m_kanban_view = function() { +if (! instance.web_kanban || instance.web.form.One2ManyKanbanView) + return; +instance.web.form.One2ManyKanbanView = instance.web_kanban.KanbanView.extend({ + open_record: function(id) { + var self = this; + var pop = new instance.web.form.FormOpenPopup(self.o2m.view); + pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(), { + title: _t("Open: ") + self.name, + auto_write: false, + alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined, + parent_view: self.o2m.view, + child_name: self.o2m.name, + read_function: function() { + return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments); + }, + form_view_options: {'not_interactible_on_create':true}, + readonly: self.o2m.get("effective_readonly"), + }); + pop.on_write.add(function(id, data) { + self.o2m.dataset.write(id, data, {}, function(r) { + self.o2m.reload_current_view(); + }); + }); + + }, +}); +} + +/* + * TODO niv: clean those deferred stuff, it could be better + */ +instance.web.form.FieldMany2Many = instance.web.form.AbstractField.extend({ multi_selection: false, - init: function(view, node) { - this._super(view, node); - this.list_id = _.uniqueId("many2many"); + disable_utility_classes: true, + init: function(field_manager, node) { + this._super(field_manager, node); this.is_loaded = $.Deferred(); this.initial_is_loaded = this.is_loaded; this.is_setted = $.Deferred(); @@ -2716,43 +3030,44 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({ var self = this; - this.dataset = new openerp.web.form.Many2ManyDataSet(this, this.field.relation); + this.dataset = new instance.web.form.Many2ManyDataSet(this, this.field.relation); this.dataset.m2m = this; this.dataset.on_unlink.add_last(function(ids) { - self.on_ui_change(); + self.dataset_changed(); }); this.is_setted.then(function() { self.load_view(); }); + this.is_loaded.then(function() { + self.on("change:effective_readonly", self, function() { + self.is_loaded = self.is_loaded.pipe(function() { + self.list_view.destroy(); + return $.when(self.load_view()).then(function() { + self.reload_content(); + }); + }); + }); + }) }, - set_value: function(value) { - value = value || []; - if (value.length >= 1 && value[0] instanceof Array) { - value = value[0][2]; + set_value: function(value_) { + value_ = value_ || []; + if (value_.length >= 1 && value_[0] instanceof Array) { + value_ = value_[0][2]; } - this._super(value); - this.dataset.set_ids(value); + this._super(value_); + this.dataset.set_ids(value_); var self = this; self.reload_content(); this.is_setted.resolve(); }, - get_value: function() { - return [commands.replace_with(this.dataset.ids)]; - }, - validate: function() { - this.invalid = false; - }, - is_readonly: function() { - return this.readonly || this.force_readonly; - }, load_view: function() { var self = this; - this.list_view = new openerp.web.form.Many2ManyListView(this, this.dataset, false, { - 'addable': self.is_readonly() ? null : _t("Add"), - 'deletable': self.is_readonly() ? false : true, + this.list_view = new instance.web.form.Many2ManyListView(this, this.dataset, false, { + 'addable': self.get("effective_readonly") ? null : _t("Add"), + 'deletable': self.get("effective_readonly") ? false : true, 'selectable': self.multi_selection, - 'isClarkGable': self.is_readonly() ? false : true + 'isClarkGable': self.get("effective_readonly") ? false : true }); var embedded = (this.field.views || {}).tree; if (embedded) { @@ -2765,7 +3080,7 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({ loaded.resolve(); }); $.async_when().then(function () { - self.list_view.appendTo($("#" + self.list_id)); + self.list_view.appendTo(self.$element); }); return loaded; }, @@ -2775,24 +3090,12 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({ return self.list_view.reload_content(); }); }, - update_dom: function() { - this._super.apply(this, arguments); - var self = this; - if (this.previous_readonly !== this.readonly) { - this.previous_readonly = this.readonly; - if (this.list_view) { - this.is_loaded = this.is_loaded.pipe(function() { - self.list_view.destroy(); - return $.when(self.load_view()).then(function() { - self.reload_content(); - }); - }); - } - } - } + dataset_changed: function() { + this.set({'value': [commands.replace_with(this.dataset.ids)]}); + }, }); -openerp.web.form.Many2ManyDataSet = openerp.web.DataSetStatic.extend({ +instance.web.form.Many2ManyDataSet = instance.web.DataSetStatic.extend({ get_context: function() { this.context = this.m2m.build_context(); return this.context; @@ -2801,25 +3104,25 @@ openerp.web.form.Many2ManyDataSet = openerp.web.DataSetStatic.extend({ /** * @class - * @extends openerp.web.ListView + * @extends instance.web.ListView */ -openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends openerp.web.form.Many2ManyListView# */{ +instance.web.form.Many2ManyListView = instance.web.ListView.extend(/** @lends instance.web.form.Many2ManyListView# */{ do_add_record: function () { - var pop = new openerp.web.form.SelectCreatePopup(this); + var pop = new instance.web.form.SelectCreatePopup(this); pop.select_element( this.model, { title: _t("Add: ") + this.name }, - new openerp.web.CompoundDomain(this.m2m_field.build_domain(), ["!", ["id", "in", this.m2m_field.dataset.ids]]), + new instance.web.CompoundDomain(this.m2m_field.build_domain(), ["!", ["id", "in", this.m2m_field.dataset.ids]]), this.m2m_field.build_context() ); var self = this; pop.on_select_elements.add(function(element_ids) { - _.each(element_ids, function(element_id) { - if(! _.detect(self.dataset.ids, function(x) {return x == element_id;})) { - self.dataset.set_ids([].concat(self.dataset.ids, [element_id])); - self.m2m_field.on_ui_change(); + _.each(element_ids, function(one_id) { + if(! _.detect(self.dataset.ids, function(x) {return x == one_id;})) { + self.dataset.set_ids([].concat(self.dataset.ids, [one_id])); + self.m2m_field.dataset_changed(); self.reload_content(); } }); @@ -2827,10 +3130,10 @@ openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends open }, do_activate_record: function(index, id) { var self = this; - var pop = new openerp.web.form.FormOpenPopup(this); + var pop = new instance.web.form.FormOpenPopup(this); pop.show_element(this.dataset.model, id, this.m2m_field.build_context(), { title: _t("Open: ") + this.name, - readonly: this.getParent().is_readonly() + readonly: this.getParent().get("effective_readonly") }); pop.on_write_completed.add_last(function() { self.reload_content(); @@ -2840,9 +3143,9 @@ openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends open /** * @class - * @extends openerp.web.OldWidget + * @extends instance.web.OldWidget */ -openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends openerp.web.form.SelectCreatePopup# */{ +instance.web.form.SelectCreatePopup = instance.web.OldWidget.extend(/** @lends instance.web.form.SelectCreatePopup# */{ template: "SelectCreatePopup", /** * options: @@ -2868,7 +3171,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope this.initial_ids = this.options.initial_ids; this.created_elements = []; this.renderElement(); - openerp.web.form.dialog(this.$element, { + instance.web.form.dialog(this.$element, { close: function() { self.check_exit(); }, @@ -2879,7 +3182,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope start: function() { this._super(); var self = this; - this.dataset = new openerp.web.ProxyDataSet(this, this.model, + this.dataset = new instance.web.ProxyDataSet(this, this.model, this.context); this.dataset.create_function = function() { return self.options.create_function.apply(null, arguments).then(function(r) { @@ -2899,10 +3202,10 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope contexts: [this.context] }, function (results) { var search_defaults = {}; - _.each(results.context, function (value, key) { + _.each(results.context, function (value_, key) { var match = /^search_default_(.*)$/.exec(key); if (match) { - search_defaults[match[1]] = value; + search_defaults[match[1]] = value_; } }); self.setup_search_view(search_defaults); @@ -2920,7 +3223,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope if (this.searchview) { this.searchview.destroy(); } - this.searchview = new openerp.web.SearchView(this, + this.searchview = new instance.web.SearchView(this, this.dataset, false, search_defaults); this.searchview.on_search.add(function(domains, contexts, groupbys) { if (self.initial_ids) { @@ -2932,13 +3235,13 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope } }); this.searchview.on_loaded.add_last(function () { - self.view_list = new openerp.web.form.SelectCreateListView(self, + self.view_list = new instance.web.form.SelectCreateListView(self, self.dataset, false, _.extend({'deletable': false, 'selectable': !self.options.disable_multiple_selection }, self.options.list_view_options || {})); self.view_list.popup = self; - self.view_list.appendTo($("#" + self.element_id + "_view_list")).pipe(function() { + self.view_list.appendTo($(".oe-select-create-popup-view-list", self.$element)).pipe(function() { self.view_list.do_show(); }).pipe(function() { self.searchview.do_search(); @@ -2960,7 +3263,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope }); }); }); - this.searchview.appendTo($("#" + this.element_id + "_search")); + this.searchview.appendTo($(".oe-select-create-popup-view-list", self.$element)); }, do_search: function(domains, contexts, groupbys) { var self = this; @@ -2974,14 +3277,14 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope }, create_row: function() { var self = this; - var wdataset = new openerp.web.DataSetSearch(this, this.model, this.context, this.domain); + var wdataset = new instance.web.DataSetSearch(this, this.model, this.context, this.domain); wdataset.parent_view = this.options.parent_view; wdataset.child_name = this.options.child_name; return wdataset.create.apply(wdataset, arguments); }, write_row: function() { var self = this; - var wdataset = new openerp.web.DataSetSearch(this, this.model, this.context, this.domain); + var wdataset = new instance.web.DataSetSearch(this, this.model, this.context, this.domain); wdataset.parent_view = this.options.parent_view; wdataset.child_name = this.options.child_name; return wdataset.write.apply(wdataset, arguments); @@ -3005,11 +3308,11 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope this.view_list.$element.hide(); } this.dataset.index = null; - this.view_form = new openerp.web.FormView(this, this.dataset, false, self.options.form_view_options); + this.view_form = new instance.web.FormView(this, this.dataset, false, self.options.form_view_options); if (this.options.alternative_form_view) { this.view_form.set_embedded_view(this.options.alternative_form_view); } - this.view_form.appendTo(this.$element.find("#" + this.element_id + "_view_form")); + this.view_form.appendTo(this.$element.find(".oe-select-create-popup-view-form")); this.view_form.on_loaded.add_last(function() { var $buttons = self.view_form.$element.find(".oe_form_buttons"); $buttons.html(QWeb.render("SelectCreatePopup.form.buttons", {widget:self})); @@ -3045,7 +3348,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope on_default_get: function(res) {} }); -openerp.web.form.SelectCreateListView = openerp.web.ListView.extend({ +instance.web.form.SelectCreateListView = instance.web.ListView.extend({ do_add_record: function () { this.popup.new_object(); }, @@ -3061,9 +3364,9 @@ openerp.web.form.SelectCreateListView = openerp.web.ListView.extend({ /** * @class - * @extends openerp.web.OldWidget + * @extends instance.web.OldWidget */ -openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp.web.form.FormOpenPopup# */{ +instance.web.form.FormOpenPopup = instance.web.OldWidget.extend(/** @lends instance.web.form.FormOpenPopup# */{ template: "FormOpenPopup", /** * options: @@ -3081,7 +3384,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp this.context = context || {}; this.options = _.defaults(options || {}, {"auto_write": true}); this.renderElement(); - openerp.web.dialog(this.$element, { + instance.web.dialog(this.$element, { title: options.title || '', modal: true, width: 960, @@ -3091,7 +3394,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp }, start: function() { this._super(); - this.dataset = new openerp.web.form.FormOpenDataset(this, this.model, this.context); + this.dataset = new instance.web.form.FormOpenDataset(this, this.model, this.context); this.dataset.fop = this; this.dataset.ids = [this.row_id]; this.dataset.index = 0; @@ -3103,7 +3406,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp if (!this.options.auto_write) return; var self = this; - var wdataset = new openerp.web.DataSetSearch(this, this.model, this.context, this.domain); + var wdataset = new instance.web.DataSetSearch(this, this.model, this.context, this.domain); wdataset.parent_view = this.options.parent_view; wdataset.child_name = this.options.child_name; wdataset.write(id, data, {}, function(r) { @@ -3113,14 +3416,14 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp on_write_completed: function() {}, setup_form_view: function() { var self = this; - var FormClass = this.options.readonly - ? openerp.web.views.get_object('page') - : openerp.web.views.get_object('form'); - this.view_form = new FormClass(this, this.dataset, false, self.options.form_view_options); + var FormClass = instance.web.views.get_object('form'); + var options = _.clone(self.options.form_view_options) || {}; + options.initial_mode = this.options.readonly ? "view" : "edit"; + this.view_form = new FormClass(this, this.dataset, false, options); if (this.options.alternative_form_view) { this.view_form.set_embedded_view(this.options.alternative_form_view); } - this.view_form.appendTo(this.$element.find("#" + this.element_id + "_view_form")); + this.view_form.appendTo(this.$element.find(".oe-form-open-popup-form-view")); this.view_form.on_loaded.add_last(function() { var $buttons = self.view_form.$element.find(".oe_form_buttons"); $buttons.html(QWeb.render("FormOpenPopup.form.buttons")); @@ -3144,7 +3447,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp } }); -openerp.web.form.FormOpenDataset = openerp.web.ProxyDataSet.extend({ +instance.web.form.FormOpenDataset = instance.web.ProxyDataSet.extend({ read_ids: function() { if (this.fop.options.read_function) { return this.fop.options.read_function.apply(null, arguments); @@ -3154,40 +3457,28 @@ openerp.web.form.FormOpenDataset = openerp.web.ProxyDataSet.extend({ } }); -openerp.web.form.FieldReference = openerp.web.form.Field.extend({ - form_template: 'FieldReference', - init: function(view, node) { - this._super(view, node); +instance.web.form.FieldReference = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { + template: 'FieldReference', + init: function(field_manager, node) { + this._super(field_manager, node); this.fields_view = { fields: { selection: { - selection: view.fields_view.fields[this.name].selection + selection: this.view.fields_view.fields[this.name].selection }, m2o: { relation: null } } }; - this.get_fields_values = view.get_fields_values; - this.get_selected_ids = view.get_selected_ids; + this.get_fields_values = this.view.get_fields_values; + this.get_selected_ids = this.view.get_selected_ids; this.do_onchange = this.on_form_changed = this.do_notify_change = this.on_nop; this.dataset = this.view.dataset; - this.widgets_counter = 0; this.view_id = 'reference_' + _.uniqueId(); - this.widgets = {}; this.fields = {}; this.fields_order = []; - this.selection = new openerp.web.form.FieldSelection(this, { attrs: { - name: 'selection', - widget: 'selection' - }}); this.reference_ready = true; - this.selection.on_value_changed.add_last(this.on_selection_changed); - this.m2o = new openerp.web.form.FieldMany2One(this, { attrs: { - name: 'm2o', - widget: 'many2one' - }}); - this.m2o.on_ui_change.add_last(this.on_ui_change); }, on_nop: function() { }, @@ -3195,59 +3486,82 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({ if (this.reference_ready) { var sel = this.selection.get_value(); this.m2o.field.relation = sel; - this.m2o.set_value(null); + this.m2o.set_value(false); this.m2o.$element.toggle(sel !== false); } }, - start: function() { - this._super(); + destroy_content: function() { + if (this.selection) { + this.selection.destroy(); + this.selection = undefined; + } + if (this.m2o) { + this.m2o.destroy(); + this.m2o = undefined; + } + }, + initialize_content: function() { + this.selection = new instance.web.form.FieldSelection(this, { attrs: { + name: 'selection', + widget: 'selection' + }}); + this.selection.set({readonly: this.get('effective_readonly')}); + this.selection.on("change:value", this, this.on_selection_changed); this.selection.$element = $(".oe_form_view_reference_selection", this.$element); this.selection.renderElement(); this.selection.start(); + + this.m2o = new instance.web.form.FieldMany2One(this, { attrs: { + name: 'm2o', + widget: 'many2one' + }}); + this.m2o.set({"readonly": this.get("effective_readonly")}); + this.m2o.on("change:value", this, this.data_changed); this.m2o.$element = $(".oe_form_view_reference_m2o", this.$element); this.m2o.renderElement(); this.m2o.start(); }, - is_valid: function() { - return this.required === false || typeof(this.get_value()) === 'string'; + is_false: function() { + return typeof(this.get_value()) !== 'string'; }, - is_dirty: function() { - return this.selection.is_dirty() || this.m2o.is_dirty(); + set_value: function(value_) { + this._super(value_); + this.render_value(); }, - set_value: function(value) { - this._super(value); + render_value: function() { this.reference_ready = false; var vals = [], sel_val, m2o_val; - if (typeof(value) === 'string') { - vals = value.split(','); + if (typeof(this.get('value')) === 'string') { + vals = this.get('value').split(','); } sel_val = vals[0] || false; m2o_val = vals[1] ? parseInt(vals[1], 10) : false; - this.selection.set_value(sel_val); + if (!this.get("effective_readonly")) { + this.selection.set_value(sel_val); + } this.m2o.field.relation = sel_val; this.m2o.set_value(m2o_val); this.m2o.$element.toggle(sel_val !== false); this.reference_ready = true; }, - get_value: function() { + data_changed: function() { var model = this.selection.get_value(), id = this.m2o.get_value(); if (typeof(model) === 'string' && typeof(id) === 'number') { - return model + ',' + id; + this.set({'value': model + ',' + id}); } else { - return false; + this.set({'value': false}); } - } -}); + }, +})); -openerp.web.form.FieldBinary = openerp.web.form.Field.extend({ - init: function(view, node) { - this._super(view, node); +instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { + init: function(field_manager, node) { + this._super(field_manager, node); this.iframe = this.element_id + '_iframe'; this.binary_value = false; }, - start: function() { - this._super.apply(this, arguments); + initialize_content: function() { this.$element.find('input.oe-binary-file').change(this.on_file_change); this.$element.find('button.oe-binary-file-save').click(this.on_save_as); this.$element.find('.oe-binary-file-clear').click(this.on_clear); @@ -3281,7 +3595,6 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({ console.warn("Error while uploading file : ", name); } else { this.on_file_uploaded_and_valid.apply(this, arguments); - this.on_ui_change(); } this.$element.find('.oe-binary-progress').hide(); this.$element.find('.oe-binary').show(); @@ -3300,48 +3613,64 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({ context: this.view.dataset.get_context() })}, complete: $.unblockUI, - error: openerp.webclient.crashmanager.on_rpc_error + error: instance.webclient.crashmanager.on_rpc_error }); }, on_clear: function() { - if (this.value !== false) { - this.value = false; + if (this.get('value') !== false) { this.binary_value = false; - this.on_ui_change(); + this.set({'value': false}); } return false; } -}); +})); -openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({ - form_template: 'FieldBinaryFile', - update_dom: function() { - this._super.apply(this, arguments); - this.$element.find('.oe-binary-file-set, .oe-binary-file-clear').toggle(!this.readonly); - this.$element.find('input[type=text]').prop('readonly', this.readonly); +instance.web.form.FieldBinaryFile = instance.web.form.FieldBinary.extend({ + template: 'FieldBinaryFile', + initialize_content: function() { + this._super(); + if (this.get("effective_readonly")) { + var self = this; + this.$element.find('a').click(function() { + if (self.get('value')) { + self.on_save_as(); + } + return false; + }); + } }, - set_value: function(value) { + set_value: function(value_) { this._super.apply(this, arguments); - var show_value; - if (this.node.attrs.filename) { - show_value = this.view.datarecord[this.node.attrs.filename] || ''; + this.render_value(); + }, + render_value: function() { + if (!this.get("effective_readonly")) { + var show_value; + if (this.node.attrs.filename) { + show_value = this.view.datarecord[this.node.attrs.filename] || ''; + } else { + show_value = (this.get('value') != null && this.get('value') !== false) ? this.get('value') : ''; + } + this.$element.find('input').eq(0).val(show_value); } else { - show_value = (value != null && value !== false) ? value : ''; + this.$element.find('a').show(!!this.get('value')); + if (this.get('value')) { + var show_value = _t("Download") + " " + (this.view.datarecord[this.node.attrs.filename] || ''); + this.$element.find('a').text(show_value); + } } - this.$element.find('input').eq(0).val(show_value); }, on_file_uploaded_and_valid: function(size, name, content_type, file_base64) { - this.value = file_base64; this.binary_value = true; + this.set({'value': file_base64}); var show_value = name + " (" + this.human_filesize(size) + ")"; this.$element.find('input').eq(0).val(show_value); this.set_filename(name); }, - set_filename: function(value) { + set_filename: function(value_) { var filename = this.node.attrs.filename; if (this.view.fields[filename]) { - this.view.fields[filename].set_value(value); - this.view.fields[filename].on_ui_change(); + this.view.fields[filename].set({value: value_}); } }, on_clear: function() { @@ -3351,58 +3680,59 @@ openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({ } }); -openerp.web.form.FieldBinaryImage = openerp.web.form.FieldBinary.extend({ - form_template: 'FieldBinaryImage', - start: function() { - this._super.apply(this, arguments); - this.$image = this.$element.find('img.oe-binary-image'); +instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({ + template: 'FieldBinaryImage', + initialize_content: function() { + this._super(); + this.$placeholder = $(".oe_form_field-binary-image-placeholder", this.$element); + if (!this.get("effective_readonly")) + this.$element.find('.oe-binary').show(); + else + this.$element.find('.oe-binary').hide(); }, - update_dom: function() { + set_value: function(value_) { this._super.apply(this, arguments); - this.$element.find('.oe-binary').toggle(!this.readonly); + this.render_value(); }, - set_value: function(value) { - this._super.apply(this, arguments); - this.set_image_maxwidth(); - + render_value: function() { var url; - if (value && value.substr(0, 10).indexOf(' ') == -1) { - url = 'data:image/png;base64,' + this.value; - } else { + if (this.get('value') && this.get('value').substr(0, 10).indexOf(' ') == -1) { + url = 'data:image/png;base64,' + this.get('value'); + } else if (this.get('value')) { url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' + this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + '&t=' + (new Date().getTime()); + } else { + url = "/web/static/src/img/placeholder.png"; } - this.$image.attr('src', url); - }, - set_image_maxwidth: function() { - this.$image.css('max-width', this.$element.width()); + var rendered = QWeb.render("FieldBinaryImage-img", {widget: this, url: url});; + this.$placeholder.html(rendered); }, on_file_change: function() { - this.set_image_maxwidth(); + this.render_value(); this._super.apply(this, arguments); }, on_file_uploaded_and_valid: function(size, name, content_type, file_base64) { - this.value = file_base64; + this.set({'value': file_base64}); this.binary_value = true; - this.$image.attr('src', 'data:' + (content_type || 'image/png') + ';base64,' + file_base64); + this.render_value(); }, on_clear: function() { this._super.apply(this, arguments); - this.$image.attr('src', '/web/static/src/img/placeholder.png'); + this.render_value(); } }); -openerp.web.form.FieldStatus = openerp.web.form.Field.extend({ - form_template: "EmptyComponent", +instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({ + template: "EmptyComponent", start: function() { this._super(); this.selected_value = null; this.render_list(); }, - set_value: function(value) { - this._super(value); - this.selected_value = value; + set_value: function(value_) { + this._super(value_); + this.selected_value = value_; this.render_list(); }, @@ -3420,7 +3750,7 @@ openerp.web.form.FieldStatus = openerp.web.form.Field.extend({ }); } - var content = openerp.web.qweb.render("FieldStatus.content", {widget: this, _:_}); + var content = instance.web.qweb.render("FieldStatus.content", {widget: this, _:_}); this.$element.html(content); var colors = JSON.parse((this.node.attrs || {}).statusbar_colors || "{}"); @@ -3457,77 +3787,36 @@ openerp.web.form.FieldStatus = openerp.web.form.Field.extend({ } }); -openerp.web.form.WidgetHtml = openerp.web.form.Widget.extend({ - renderElement: function () { - var $root = $('<div class="oe_form_html_view">'); - this.render_children(this, $root); - var rendered = $root.html(); - this.$element.html(rendered); - }, - render_children: function (object, $into) { - var self = this, - fields = this.view.fields_view.fields; - _(object.children).each(function (child) { - if (typeof child === 'string') { - $into.text(child); - } else if (child.tag === 'field') { - var widget = instanciate_widget(self.view.registry.get_object('frame'), - self.view, {tag: 'ueule', attrs: {}, children: [child] }); - widget.appendTo($into); - } else { - var $child = $(document.createElement(child.tag)) - .attr(child.attrs) - .appendTo($into); - self.render_children(child, $child); - } - }); - } -}); - - /** - * Registry of form widgets, called by :js:`openerp.web.FormView` + * Registry of form widgets, called by :js:`instance.web.FormView` */ -openerp.web.form.widgets = new openerp.web.Registry({ - 'frame' : 'openerp.web.form.WidgetFrame', - 'group' : 'openerp.web.form.WidgetGroup', - 'notebook' : 'openerp.web.form.WidgetNotebook', - 'notebookpage' : 'openerp.web.form.WidgetNotebookPage', - 'separator' : 'openerp.web.form.WidgetSeparator', - 'label' : 'openerp.web.form.WidgetLabel', - 'button' : 'openerp.web.form.WidgetButton', - 'char' : 'openerp.web.form.FieldChar', - 'id' : 'openerp.web.form.FieldID', - 'email' : 'openerp.web.form.FieldEmail', - 'url' : 'openerp.web.form.FieldUrl', - 'text' : 'openerp.web.form.FieldText', - 'date' : 'openerp.web.form.FieldDate', - 'datetime' : 'openerp.web.form.FieldDatetime', - 'selection' : 'openerp.web.form.FieldSelection', - 'many2one' : 'openerp.web.form.FieldMany2One', - 'many2many' : 'openerp.web.form.FieldMany2Many', - 'one2many' : 'openerp.web.form.FieldOne2Many', - 'one2many_list' : 'openerp.web.form.FieldOne2Many', - 'reference' : 'openerp.web.form.FieldReference', - 'boolean' : 'openerp.web.form.FieldBoolean', - 'float' : 'openerp.web.form.FieldFloat', - 'integer': 'openerp.web.form.FieldFloat', - 'float_time': 'openerp.web.form.FieldFloat', - 'progressbar': 'openerp.web.form.FieldProgressBar', - 'image': 'openerp.web.form.FieldBinaryImage', - 'binary': 'openerp.web.form.FieldBinaryFile', - 'statusbar': 'openerp.web.form.FieldStatus', - 'html': 'openerp.web.form.WidgetHtml' +instance.web.form.widgets = new instance.web.Registry({ + 'char' : 'instance.web.form.FieldChar', + 'id' : 'instance.web.form.FieldID', + 'email' : 'instance.web.form.FieldEmail', + 'url' : 'instance.web.form.FieldUrl', + 'text' : 'instance.web.form.FieldText', + 'date' : 'instance.web.form.FieldDate', + 'datetime' : 'instance.web.form.FieldDatetime', + 'selection' : 'instance.web.form.FieldSelection', + 'many2one' : 'instance.web.form.FieldMany2One', + 'many2many' : 'instance.web.form.FieldMany2Many', + 'one2many' : 'instance.web.form.FieldOne2Many', + 'one2many_list' : 'instance.web.form.FieldOne2Many', + 'reference' : 'instance.web.form.FieldReference', + 'boolean' : 'instance.web.form.FieldBoolean', + 'float' : 'instance.web.form.FieldFloat', + 'integer': 'instance.web.form.FieldFloat', + 'float_time': 'instance.web.form.FieldFloat', + 'progressbar': 'instance.web.form.FieldProgressBar', + 'image': 'instance.web.form.FieldBinaryImage', + 'binary': 'instance.web.form.FieldBinaryFile', + 'statusbar': 'instance.web.form.FieldStatus' }); -var instanciate_widget = function(claz, view, node, o1, o2) { - var widget = new (claz)(view, node, o1, o2); - widget.element_class = (['formview', view.view_id, widget.element_name, - view.widgets_counter++].join("_")).replace(/[^\r\n\f0-9A-Za-z_-]/g, "_"); - view.widgets[widget.element_class] = widget; - return widget; -} - +instance.web.form.tags = new instance.web.Registry({ + 'button' : 'instance.web.form.WidgetButton', +}); }; |
