summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/views/form_view.js
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/src/js/views/form_view.js')
-rw-r--r--addons/web/static/src/js/views/form_view.js1587
1 files changed, 1587 insertions, 0 deletions
diff --git a/addons/web/static/src/js/views/form_view.js b/addons/web/static/src/js/views/form_view.js
new file mode 100644
index 00000000000..ac7ea1d7888
--- /dev/null
+++ b/addons/web/static/src/js/views/form_view.js
@@ -0,0 +1,1587 @@
+odoo.define('web.FormView', ['web.core', 'web.crash_manager', 'web.data', 'web.Dialog', 'web.form_common', 'web.Model', 'web.Sidebar', 'web.utils', 'web.View'], function (require) {
+"use strict";
+
+var core = require('web.core');
+var crash_manager = require('web.crash_manager');
+var data = require('web.data');
+var Dialog = require('web.Dialog');
+var common = require('web.form_common');
+var Model = require('web.Model');
+var Sidebar = require('web.Sidebar');
+var utils = require('web.utils');
+var View = require('web.View');
+
+var _t = core._t;
+var _lt = core._lt;
+var QWeb = core.qweb;
+var Class = core.Class;
+
+/** @namespace */
+// instance.web.form = instance.web.form || {};
+
+/**
+ * Properties:
+ * - actual_mode: always "view", "edit" or "create". Read-only property. Determines
+ * the mode used by the view.
+ */
+var FormView = View.extend(common.FieldManagerMixin, {
+ /**
+ * Indicates that this view is not searchable, and thus that no search
+ * view should be displayed (if there is one active).
+ */
+ searchable: false,
+ template: "FormView",
+ display_name: _lt('Form'),
+ view_type: "form",
+ /**
+ * @constructs instance.web.FormView
+ * @extends instance.web.View
+ *
+ * @param {instance.web.Session} session the current openerp session
+ * @param {instance.web.DataSet} dataset the dataset this view will work with
+ * @param {String} view_id the identifier of the OpenERP view object
+ *
+ * @property {instance.web.Registry} registry=instance.web.form.widgets widgets registry for this form view instance
+ */
+ init: function(parent, dataset, view_id, options) {
+ var self = this;
+ this._super(parent);
+ this.ViewManager = parent;
+ this.set_default_options(options);
+ this.dataset = dataset;
+ this.model = dataset.model;
+ this.view_id = view_id || false;
+ this.fields_view = {};
+ this.fields = {};
+ this.fields_order = [];
+ this.datarecord = {};
+ this._onchange_specs = {};
+ this.onchanges_mutex = new utils.Mutex();
+ this.default_focus_field = null;
+ this.default_focus_button = null;
+ this.fields_registry = core.form_widget_registry;
+ this.tags_registry = core.form_tag_registry;
+ this.widgets_registry = core.form_custom_registry;
+ this.has_been_loaded = $.Deferred();
+ this.translatable_fields = [];
+ _.defaults(this.options, {
+ "not_interactible_on_create": false,
+ "initial_mode": "view",
+ "disable_autofocus": false,
+ "footer_to_buttons": false,
+ });
+ this.is_initialized = $.Deferred();
+ this.mutating_mutex = new utils.Mutex();
+ this.save_list = [];
+ this.render_value_defs = [];
+ this.reload_mutex = new utils.Mutex();
+ this.__clicked_inside = false;
+ this.__blur_timeout = null;
+ this.rendering_engine = new FormRenderingEngine(this);
+ self.set({actual_mode: self.options.initial_mode});
+ this.has_been_loaded.done(function() {
+ self._build_onchange_specs();
+ self.on("change:actual_mode", self, self.check_actual_mode);
+ self.check_actual_mode();
+ self.on("change:actual_mode", self, self.init_pager);
+ self.init_pager();
+ });
+ self.on("load_record", self, self.load_record);
+ core.bus.on('clear_uncommitted_changes', this, function(e) {
+ if (!this.can_be_discarded()) {
+ e.preventDefault();
+ }
+ });
+ },
+ view_loading: function(r) {
+ return this.load_form(r);
+ },
+ destroy: function() {
+ _.each(this.get_widgets(), function(w) {
+ w.off('focused blurred');
+ w.destroy();
+ });
+ if (this.$el) {
+ this.$el.off('.formBlur');
+ }
+ this._super();
+ },
+ load_form: function(data) {
+ var self = this;
+ if (!data) {
+ throw new Error(_t("No data provided."));
+ }
+ if (this.arch) {
+ throw "Form view does not support multiple calls to load_form";
+ }
+ this.fields_order = [];
+ this.fields_view = data;
+
+ this.rendering_engine.set_fields_registry(this.fields_registry);
+ this.rendering_engine.set_tags_registry(this.tags_registry);
+ this.rendering_engine.set_widgets_registry(this.widgets_registry);
+ this.rendering_engine.set_fields_view(data);
+ var $dest = this.$el.hasClass("oe_form_container") ? this.$el : this.$el.find('.oe_form_container');
+ this.rendering_engine.render_to($dest);
+
+ this.$el.on('mousedown.formBlur', function () {
+ self.__clicked_inside = true;
+ });
+
+ this.$buttons = $(QWeb.render("FormView.buttons", {'widget':self}));
+ if (this.options.$buttons) {
+ this.$buttons.appendTo(this.options.$buttons);
+ } else {
+ this.$el.find('.oe_form_buttons').replaceWith(this.$buttons);
+ }
+ this.$buttons.on('click', '.oe_form_button_create',
+ this.guard_active(this.on_button_create));
+ this.$buttons.on('click', '.oe_form_button_edit',
+ this.guard_active(this.on_button_edit));
+ this.$buttons.on('click', '.oe_form_button_save',
+ this.guard_active(this.on_button_save));
+ this.$buttons.on('click', '.oe_form_button_cancel',
+ this.guard_active(this.on_button_cancel));
+ if (this.options.footer_to_buttons) {
+ this.$el.find('footer').appendTo(this.$buttons);
+ }
+
+ this.$sidebar = this.options.$sidebar || this.$el.find('.oe_form_sidebar');
+ if (!this.sidebar && this.options.$sidebar) {
+ this.sidebar = new Sidebar(this);
+ this.sidebar.appendTo(this.$sidebar);
+ if (this.fields_view.toolbar) {
+ this.sidebar.add_toolbar(this.fields_view.toolbar);
+ }
+ this.sidebar.add_items('other', _.compact([
+ self.is_action_enabled('delete') && { label: _t('Delete'), callback: self.on_button_delete },
+ self.is_action_enabled('create') && { label: _t('Duplicate'), callback: self.on_button_duplicate }
+ ]));
+ }
+
+ this.has_been_loaded.resolve();
+
+ // Add bounce effect on button 'Edit' when click on readonly page view.
+ this.$el.find(".oe_form_group_row,.oe_form_field,label,h1,.oe_title,.oe_notebook_page, .oe_list_content").on('click', function (e) {
+ if(self.get("actual_mode") == "view") {
+ var $button = self.options.$buttons.find(".oe_form_button_edit");
+ $button.openerpBounce();
+ e.stopPropagation();
+ core.bus.trigger('click', e);
+ }
+ });
+ //bounce effect on red button when click on statusbar.
+ this.$el.find(".oe_form_field_status:not(.oe_form_status_clickable)").on('click', function (e) {
+ if((self.get("actual_mode") == "view")) {
+ var $button = self.$el.find(".oe_highlight:not(.oe_form_invisible)").css({'float':'left','clear':'none'});
+ $button.openerpBounce();
+ e.stopPropagation();
+ }
+ });
+ this.trigger('form_view_loaded', data);
+ return $.when();
+ },
+ widgetFocused: function() {
+ // Clear click flag if used to focus a widget
+ this.__clicked_inside = false;
+ if (this.__blur_timeout) {
+ clearTimeout(this.__blur_timeout);
+ this.__blur_timeout = null;
+ }
+ },
+ widgetBlurred: function() {
+ if (this.__clicked_inside) {
+ // clicked in an other section of the form (than the currently
+ // focused widget) => just ignore the blurring entirely?
+ this.__clicked_inside = false;
+ return;
+ }
+ var self = this;
+ // clear timeout, if any
+ this.widgetFocused();
+ this.__blur_timeout = setTimeout(function () {
+ self.trigger('blurred');
+ }, 0);
+ },
+
+ do_load_state: function(state, warm) {
+ if (state.id && this.datarecord.id != state.id) {
+ if (this.dataset.get_id_index(state.id) === null) {
+ this.dataset.ids.push(state.id);
+ }
+ this.dataset.select_id(state.id);
+ this.do_show();
+ }
+ },
+ /**
+ *
+ * @param {Object} [options]
+ * @param {Boolean} [mode=undefined] If specified, switch the form to specified mode. Can be "edit" or "view".
+ * @param {Boolean} [reload=true] whether the form should reload its content on show, or use the currently loaded record
+ * @return {$.Deferred}
+ */
+ do_show: function (options) {
+ var self = this;
+ options = options || {};
+ if (this.sidebar) {
+ this.sidebar.$el.show();
+ }
+ if (this.$buttons) {
+ this.$buttons.show();
+ }
+ this.$el.show().css({
+ opacity: '0',
+ filter: 'alpha(opacity = 0)'
+ });
+ this.$el.add(this.$buttons).removeClass('oe_form_dirty');
+
+ var shown = this.has_been_loaded;
+ if (options.reload !== false) {
+ shown = shown.then(function() {
+ if (self.dataset.index === null) {
+ // null index means we should start a new record
+ return self.on_button_new();
+ }
+ var fields = _.keys(self.fields_view.fields);
+ fields.push('display_name');
+ return self.dataset.read_index(fields, {
+ context: { 'bin_size': true, 'future_display_name' : true }
+ }).then(function(r) {
+ self.trigger('load_record', r);
+ });
+ });
+ }
+ return $.when(shown, this._super()).then(function() {
+ self._actualize_mode(options.mode || self.options.initial_mode);
+ self.$el.css({
+ opacity: '1',
+ filter: 'alpha(opacity = 100)'
+ });
+ core.bus.trigger('form_view_shown', self);
+ });
+ },
+ do_hide: function () {
+ if (this.sidebar) {
+ this.sidebar.$el.hide();
+ }
+ if (this.$pager) {
+ this.$pager.hide();
+ }
+ this._super();
+ },
+ load_record: function(record) {
+ var self = this, set_values = [];
+ if (!record) {
+ this.set({ 'title' : undefined });
+ this.do_warn(_t("Form"), _t("The record could not be found in the database."), true);
+ return $.Deferred().reject();
+ }
+ this.datarecord = record;
+ this._actualize_mode();
+ this.set({ 'title' : record.id ? record.display_name : _t("New") });
+
+ _(this.fields).each(function (field, f) {
+ field._dirty_flag = false;
+ field._inhibit_on_change_flag = true;
+ var result = field.set_value(self.datarecord[f] || false);
+ field._inhibit_on_change_flag = false;
+ set_values.push(result);
+ });
+ return $.when.apply(null, set_values).then(function() {
+ if (!record.id) {
+ // trigger onchanges
+ self.do_onchange(null);
+ }
+ self.on_form_changed();
+ self.rendering_engine.init_fields();
+ self.is_initialized.resolve();
+ self.do_update_pager(record.id === null || record.id === undefined);
+ if (self.sidebar) {
+ self.sidebar.do_attachement_update(self.dataset, self.datarecord.id);
+ }
+ if (record.id) {
+ self.do_push_state({id:record.id});
+ } else {
+ self.do_push_state({});
+ }
+ self.$el.add(self.$buttons).removeClass('oe_form_dirty');
+ self.autofocus();
+ });
+ },
+ /**
+ * Loads and sets up the default values for the model as the current
+ * record
+ *
+ * @return {$.Deferred}
+ */
+ load_defaults: function () {
+ var self = this;
+ var keys = _.keys(this.fields_view.fields);
+ if (keys.length) {
+ return this.dataset.default_get(keys).then(function(r) {
+ self.trigger('load_record', r);
+ });
+ }
+ return self.trigger('load_record', {});
+ },
+ on_form_changed: function() {
+ this.trigger("view_content_has_changed");
+ },
+ do_notify_change: function() {
+ this.$el.add(this.$buttons).addClass('oe_form_dirty');
+ },
+ execute_pager_action: function(action) {
+ if (this.can_be_discarded()) {
+ switch (action) {
+ case 'first':
+ this.dataset.index = 0;
+ break;
+ case 'previous':
+ this.dataset.previous();
+ break;
+ case 'next':
+ this.dataset.next();
+ break;
+ case 'last':
+ this.dataset.index = this.dataset.ids.length - 1;
+ break;
+ }
+ var def = this.reload();
+ this.trigger('pager_action_executed');
+ return def;
+ }
+ return $.when();
+ },
+ init_pager: function() {
+ var self = this;
+ if (this.$pager)
+ this.$pager.remove();
+ if (this.get("actual_mode") === "create")
+ return;
+ this.$pager = $(QWeb.render("FormView.pager", {'widget':self}));
+ if (this.options.$pager) {
+ this.$pager.appendTo(this.options.$pager);
+ } else {
+ this.$el.find('.oe_form_pager').replaceWith(this.$pager);
+ }
+ this.$pager.on('click','a[data-pager-action]',function() {
+ var $el = $(this);
+ if ($el.attr("disabled"))
+ return;
+ var action = $el.data('pager-action');
+ var def = $.when(self.execute_pager_action(action));
+ $el.attr("disabled");
+ def.always(function() {
+ $el.removeAttr("disabled");
+ });
+ });
+ this.do_update_pager();
+ },
+ do_update_pager: function(hide_index) {
+ this.$pager.toggle(this.dataset.ids.length > 1);
+ if (hide_index) {
+ $(".oe_form_pager_state", this.$pager).html("");
+ } else {
+ $(".oe_form_pager_state", this.$pager).html(_.str.sprintf(_t("%d / %d"), this.dataset.index + 1, this.dataset.ids.length));
+ }
+ },
+
+ _build_onchange_specs: function() {
+ var self = this;
+ var find = function(field_name, root) {
+ var fields = [root];
+ while (fields.length) {
+ var node = fields.pop();
+ if (!node) {
+ continue;
+ }
+ if (node.tag === 'field' && node.attrs.name === field_name) {
+ return node.attrs.on_change || "";
+ }
+ fields = _.union(fields, node.children);
+ }
+ return "";
+ };
+
+ self._onchange_fields = [];
+ self._onchange_specs = {};
+ _.each(this.fields, function(field, name) {
+ self._onchange_fields.push(name);
+ self._onchange_specs[name] = find(name, field.node);
+ _.each(field.field.views, function(view) {
+ _.each(view.fields, function(_, subname) {
+ self._onchange_specs[name + '.' + subname] = find(subname, view.arch);
+ });
+ });
+ });
+ },
+ _get_onchange_values: function() {
+ var field_values = this.get_fields_values();
+ if (field_values.id.toString().match(data.BufferedDataSet.virtual_id_regex)) {
+ delete field_values.id;
+ }
+ if (this.dataset.parent_view) {
+ // this belongs to a parent view: add parent field if possible
+ var parent_view = this.dataset.parent_view;
+ var child_name = this.dataset.child_name;
+ var parent_name = parent_view.get_field_desc(child_name).relation_field;
+ if (parent_name) {
+ // consider all fields except the inverse of the parent field
+ var parent_values = parent_view.get_fields_values();
+ delete parent_values[child_name];
+ field_values[parent_name] = parent_values;
+ }
+ }
+ return field_values;
+ },
+
+ do_onchange: function(widget) {
+ var self = this;
+ var onchange_specs = self._onchange_specs;
+ try {
+ var def = $.when({});
+ var change_spec = widget ? onchange_specs[widget.name] : null;
+ if (!widget || (!_.isEmpty(change_spec) && change_spec !== "0")) {
+ var ids = [],
+ trigger_field_name = widget ? widget.name : self._onchange_fields,
+ values = self._get_onchange_values(),
+ context = new data.CompoundContext(self.dataset.get_context());
+
+ if (widget && widget.build_context()) {
+ context.add(widget.build_context());
+ }
+ if (self.dataset.parent_view) {
+ var parent_name = self.dataset.parent_view.get_field_desc(self.dataset.child_name).relation_field;
+ context.add({field_parent: parent_name});
+ }
+
+ if (self.datarecord.id && !data.BufferedDataSet.virtual_id_regex.test(self.datarecord.id)) {
+ // In case of a o2m virtual id, we should pass an empty ids list
+ ids.push(self.datarecord.id);
+ }
+ def = self.alive(new Model(self.dataset.model).call(
+ "onchange", [ids, values, trigger_field_name, onchange_specs, context]));
+ }
+ this.onchanges_mutex.exec(function(){
+ return def.then(function(response) {
+ var fields = {};
+ if (widget){
+ fields[widget.name] = widget.field;
+ }
+ else{
+ fields = self.fields_view.fields;
+ }
+ var defs = [];
+ _.each(fields, function(field, fieldname){
+ if (field && field.change_default) {
+ var value_;
+ if (response.value && (fieldname in response.value)) {
+ // Use value from onchange if onchange executed
+ value_ = response.value[fieldname];
+ } else {
+ // otherwise get form value for field
+ value_ = self.fields[fieldname].get_value();
+ }
+ var condition = fieldname + '=' + value_;
+
+ if (value_) {
+ defs.push(self.alive(new Model('ir.values').call(
+ 'get_defaults', [self.model, condition]
+ )).then(function (results) {
+ if (!results.length) {
+ return response;
+ }
+ if (!response.value) {
+ response.value = {};
+ }
+ for(var i=0; i<results.length; ++i) {
+ // [whatever, key, value]
+ var triplet = results[i];
+ response.value[triplet[1]] = triplet[2];
+ }
+ return response;
+ }));
+ }
+ }
+ });
+ return _.isEmpty(defs) ? response : $.when.apply(null, defs);
+ }).then(function(response) {
+ return self.on_processed_onchange(response);
+ });
+ });
+ return this.onchanges_mutex.def;
+ } catch(e) {
+ console.error(e);
+ crash_manager.show_message(e);
+ return $.Deferred().reject();
+ }
+ },
+ on_processed_onchange: function(result) {
+ try {
+ var fields = this.fields;
+ _(result.domain).each(function (domain, fieldname) {
+ var field = fields[fieldname];
+ if (!field) { return; }
+ field.node.attrs.domain = domain;
+ });
+
+ if (!_.isEmpty(result.value)) {
+ this._internal_set_values(result.value);
+ }
+ // FIXME XXX a list of warnings?
+ if (!_.isEmpty(result.warning)) {
+ new Dialog(this, {
+ size: 'medium',
+ title:result.warning.title,
+ buttons: [
+ {text: _t("Ok"), click: function() { this.parents('.modal').modal('hide'); }}
+ ]
+ }, QWeb.render("CrashManager.warning", result.warning)).open();
+ }
+
+ return $.Deferred().resolve();
+ } catch(e) {
+ console.error(e);
+ crash_manager.show_message(e);
+ return $.Deferred().reject();
+ }
+ },
+ _process_operations: function() {
+ var self = this;
+ return this.mutating_mutex.exec(function() {
+ function iterate() {
+
+ var mutex = new utils.Mutex();
+ _.each(self.fields, function(field) {
+ self.onchanges_mutex.def.then(function(){
+ mutex.exec(function(){
+ return field.commit_value();
+ });
+ });
+ });
+
+ var args = _.toArray(arguments);
+ return $.when.apply(null, [mutex.def, self.onchanges_mutex.def]).then(function() {
+ var save_obj = self.save_list.pop();
+ if (save_obj) {
+ return self._process_save(save_obj).then(function() {
+ save_obj.ret = _.toArray(arguments);
+ return iterate();
+ }, function() {
+ save_obj.error = true;
+ });
+ }
+ return $.when();
+ }).fail(function() {
+ self.save_list.pop();
+ return $.when();
+ });
+ }
+ return iterate();
+ });
+ },
+ _internal_set_values: function(values) {
+ for (var f in values) {
+ if (!values.hasOwnProperty(f)) { continue; }
+ var field = this.fields[f];
+ // If field is not defined in the view, just ignore it
+ if (field) {
+ var value_ = values[f];
+ if (field.get_value() != value_) {
+ field._inhibit_on_change_flag = true;
+ field.set_value(value_);
+ field._inhibit_on_change_flag = false;
+ field._dirty_flag = true;
+ }
+ }
+ }
+ this.on_form_changed();
+ },
+ set_values: function(values) {
+ var self = this;
+ return this.mutating_mutex.exec(function() {
+ self._internal_set_values(values);
+ });
+ },
+ /**
+ * Ask the view to switch to view mode if possible. The view may not do it
+ * if the current record is not yet saved. It will then stay in create mode.
+ */
+ to_view_mode: function() {
+ this._actualize_mode("view");
+ },
+ /**
+ * Ask the view to switch to edit mode if possible. The view may not do it
+ * if the current record is not yet saved. It will then stay in create mode.
+ */
+ to_edit_mode: function() {
+ this.onchanges_mutex = new utils.Mutex();
+ this._actualize_mode("edit");
+ },
+ /**
+ * Ask the view to switch to a precise mode if possible. The view is free to
+ * not respect this command if the state of the dataset is not compatible with
+ * the new mode. For example, it is not possible to switch to edit mode if
+ * the current record is not yet saved in database.
+ *
+ * @param {string} [new_mode] Can be "edit", "view", "create" or undefined. If
+ * undefined the view will test the actual mode to check if it is still consistent
+ * with the dataset state.
+ */
+ _actualize_mode: function(switch_to) {
+ var mode = switch_to || this.get("actual_mode");
+ if (! this.datarecord.id) {
+ mode = "create";
+ } else if (mode === "create") {
+ mode = "edit";
+ }
+ this.render_value_defs = [];
+ this.set({actual_mode: mode});
+ },
+ check_actual_mode: function(source, options) {
+ var self = this;
+ if(this.get("actual_mode") === "view") {
+ self.$el.removeClass('oe_form_editable').addClass('oe_form_readonly');
+ self.$buttons.find('.oe_form_buttons_edit').hide();
+ self.$buttons.find('.oe_form_buttons_view').show();
+ self.$sidebar.show();
+ } else {
+ self.$el.removeClass('oe_form_readonly').addClass('oe_form_editable');
+ self.$buttons.find('.oe_form_buttons_edit').show();
+ self.$buttons.find('.oe_form_buttons_view').hide();
+ self.$sidebar.hide();
+ this.autofocus();
+ }
+ },
+ autofocus: function() {
+ if (this.get("actual_mode") !== "view" && !this.options.disable_autofocus) {
+ var fields_order = this.fields_order.slice(0);
+ if (this.default_focus_field) {
+ fields_order.unshift(this.default_focus_field.name);
+ }
+ for (var i = 0; i < fields_order.length; i += 1) {
+ var field = this.fields[fields_order[i]];
+ if (!field.get('effective_invisible') && !field.get('effective_readonly') && field.$label) {
+ if (field.focus() !== false) {
+ break;
+ }
+ }
+ }
+ }
+ },
+ on_button_save: function(e) {
+ var self = this;
+ $(e.target).attr("disabled", true);
+ return this.save().done(function(result) {
+ self.trigger("save", result);
+ self.reload().then(function() {
+ self.to_view_mode();
+ core.bus.trigger('do_reload_needaction');
+ core.bus.trigger('form_view_saved', self);
+ });
+ }).always(function(){
+ $(e.target).attr("disabled", false);
+ });
+ },
+ on_button_cancel: function(event) {
+ var self = this;
+ if (this.can_be_discarded()) {
+ if (this.get('actual_mode') === 'create') {
+ this.trigger('history_back');
+ } else {
+ this.to_view_mode();
+ $.when.apply(null, this.render_value_defs).then(function(){
+ self.trigger('load_record', self.datarecord);
+ });
+ }
+ }
+ this.trigger('on_button_cancel');
+ return false;
+ },
+ on_button_new: function() {
+ var self = this;
+ this.to_edit_mode();
+ return $.when(this.has_been_loaded).then(function() {
+ if (self.can_be_discarded()) {
+ return self.load_defaults();
+ }
+ });
+ },
+ on_button_edit: function() {
+ return this.to_edit_mode();
+ },
+ on_button_create: function() {
+ this.dataset.index = null;
+ this.do_show();
+ },
+ on_button_duplicate: function() {
+ var self = this;
+ return this.has_been_loaded.then(function() {
+ return self.dataset.call('copy', [self.datarecord.id, {}, self.dataset.context]).then(function(new_id) {
+ self.record_created(new_id);
+ self.to_edit_mode();
+ });
+ });
+ },
+ on_button_delete: function() {
+ var self = this;
+ var def = $.Deferred();
+ this.has_been_loaded.done(function() {
+ if (self.datarecord.id && confirm(_t("Do you really want to delete this record?"))) {
+ self.dataset.unlink([self.datarecord.id]).done(function() {
+ if (self.dataset.size()) {
+ self.execute_pager_action('next');
+ } else {
+ self.do_action('history_back');
+ }
+ def.resolve();
+ });
+ } else {
+ $.async_when().done(function () {
+ def.reject();
+ });
+ }
+ });
+ return def.promise();
+ },
+ can_be_discarded: function() {
+ if (this.$el.is('.oe_form_dirty')) {
+ if (!confirm(_t("Warning, the record has been modified, your changes will be discarded.\n\nAre you sure you want to leave this page ?"))) {
+ return false;
+ }
+ this.$el.removeClass('oe_form_dirty');
+ }
+ return true;
+ },
+ /**
+ * Triggers saving the form's record. Chooses between creating a new
+ * record or saving an existing one depending on whether the record
+ * already has an id property.
+ *
+ * @param {Boolean} [prepend_on_create=false] if ``save`` creates a new
+ * record, should that record be inserted at the start of the dataset (by
+ * default, records are added at the end)
+ */
+ save: function(prepend_on_create) {
+ var self = this;
+ var save_obj = {prepend_on_create: prepend_on_create, ret: null};
+ this.save_list.push(save_obj);
+ return self._process_operations().then(function() {
+ if (save_obj.error)
+ return $.Deferred().reject();
+ return $.when.apply($, save_obj.ret);
+ }).done(function(result) {
+ self.$el.removeClass('oe_form_dirty');
+ });
+ },
+ _process_save: function(save_obj) {
+ var self = this;
+ var prepend_on_create = save_obj.prepend_on_create;
+ try {
+ var form_invalid = false,
+ values = {},
+ first_invalid_field = null,
+ readonly_values = {};
+ for (var f in self.fields) {
+ if (!self.fields.hasOwnProperty(f)) { continue; }
+ f = self.fields[f];
+ if (!f.is_valid()) {
+ form_invalid = true;
+ if (!first_invalid_field) {
+ first_invalid_field = f;
+ }
+ } else if (f.name !== 'id' && (!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
+ if (!f.get("readonly")) {
+ values[f.name] = f.get_value(true);
+ } else {
+ readonly_values[f.name] = f.get_value(true);
+ }
+ }
+ }
+ 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) {
+ // Creation save
+ save_deferral = self.dataset.create(values, {readonly_fields: readonly_values}).then(function(r) {
+ return self.record_created(r, prepend_on_create);
+ }, null);
+ } else if (_.isEmpty(values)) {
+ // Not dirty, noop save
+ save_deferral = $.Deferred().resolve({}).promise();
+ } else {
+ // Write save
+ save_deferral = self.dataset.write(self.datarecord.id, values, {readonly_fields: readonly_values}).then(function(r) {
+ return self.record_saved(r);
+ }, null);
+ }
+ return save_deferral;
+ }
+ } catch (e) {
+ console.error(e);
+ return $.Deferred().reject();
+ }
+ },
+ on_invalid: function() {
+ var warnings = _(this.fields).chain()
+ .filter(function (f) { return !f.is_valid(); })
+ .map(function (f) {
+ return _.str.sprintf('<li>%s</li>',
+ _.escape(f.string));
+ }).value();
+ warnings.unshift('<ul>');
+ warnings.push('</ul>');
+ this.do_warn(_t("The following fields are invalid:"), warnings.join(''));
+ },
+ /**
+ * Reload the form after saving
+ *
+ * @param {Object} r result of the write function.
+ */
+ record_saved: function(r) {
+ this.trigger('record_saved', r);
+ if (!r) {
+ // should not happen in the server, but may happen for internal purpose
+ return $.Deferred().reject();
+ }
+ return r;
+ },
+ /**
+ * Updates the form' dataset to contain the new record:
+ *
+ * * Adds the newly created record to the current dataset (at the end by
+ * default)
+ * * Selects that record (sets the dataset's index to point to the new
+ * record's id).
+ * * Updates the pager and sidebar displays
+ *
+ * @param {Object} r
+ * @param {Boolean} [prepend_on_create=false] adds the newly created record
+ * at the beginning of the dataset instead of the end
+ */
+ record_created: function(r, prepend_on_create) {
+ var self = this;
+ if (!r) {
+ // should not happen in the server, but may happen for internal purpose
+ this.trigger('record_created', r);
+ return $.Deferred().reject();
+ } else {
+ this.datarecord.id = r;
+ if (!prepend_on_create) {
+ this.dataset.alter_ids(this.dataset.ids.concat([this.datarecord.id]));
+ this.dataset.index = this.dataset.ids.length - 1;
+ } else {
+ this.dataset.alter_ids([this.datarecord.id].concat(this.dataset.ids));
+ this.dataset.index = 0;
+ }
+ this.do_update_pager();
+ if (this.sidebar) {
+ this.sidebar.do_attachement_update(this.dataset, this.datarecord.id);
+ }
+ //openerp.log("The record has been created with id #" + this.datarecord.id);
+ return $.when(this.reload()).then(function () {
+ self.trigger('record_created', r);
+ return _.extend(r, {created: true});
+ });
+ }
+ },
+ on_action: function (action) {
+ console.debug('Executing action', action);
+ },
+ reload: function() {
+ var self = this;
+ return this.reload_mutex.exec(function() {
+ if (self.dataset.index === null || self.dataset.index === undefined) {
+ self.trigger("previous_view");
+ return $.Deferred().reject().promise();
+ }
+ if (self.dataset.index < 0) {
+ return $.when(self.on_button_new());
+ } else {
+ var fields = _.keys(self.fields_view.fields);
+ fields.push('display_name');
+ return self.dataset.read_index(fields,
+ {
+ context: {
+ 'bin_size': true,
+ 'future_display_name': true
+ },
+ check_access_rule: true
+ }).then(function(r) {
+ self.trigger('load_record', r);
+ }).fail(function (){
+ self.do_action('history_back');
+ });
+ }
+ });
+ },
+ get_widgets: function() {
+ return _.filter(this.getChildren(), function(obj) {
+ return obj instanceof common.FormWidget;
+ });
+ },
+ get_fields_values: function() {
+ var values = {};
+ var ids = this.get_selected_ids();
+ values["id"] = ids.length > 0 ? ids[0] : false;
+ _.each(this.fields, function(value_, key) {
+ values[key] = value_.get_value();
+ });
+ return values;
+ },
+ get_selected_ids: function() {
+ var id = this.dataset.ids[this.dataset.index];
+ return id ? [id] : [];
+ },
+ recursive_save: function() {
+ var self = this;
+ return $.when(this.save()).then(function(res) {
+ if (self.dataset.parent_view)
+ return self.dataset.parent_view.recursive_save();
+ });
+ },
+ recursive_reload: function() {
+ var self = this;
+ var pre = $.when();
+ if (self.dataset.parent_view)
+ pre = self.dataset.parent_view.recursive_reload();
+ return pre.then(function() {
+ return self.reload();
+ });
+ },
+ is_dirty: function() {
+ return _.any(this.fields, function (value_) {
+ return value_._dirty_flag;
+ });
+ },
+ is_interactible_record: function() {
+ var id = this.datarecord.id;
+ if (!id) {
+ if (this.options.not_interactible_on_create)
+ return false;
+ } else if (typeof(id) === "string") {
+ if(data.BufferedDataSet.virtual_id_regex.test(id))
+ return false;
+ }
+ return true;
+ },
+ sidebar_eval_context: function () {
+ return $.when(this.build_eval_context());
+ },
+ open_defaults_dialog: function () {
+ var self = this;
+ var display = function (field, value) {
+ var FieldSelection = core.form_widget_registry.get('selection'),
+ FieldMany2One = core.form_widget_registry.get('many2one');
+
+ if (!value) { return value; }
+ if (field instanceof FieldSelection) {
+ return _(field.get('values')).find(function (option) {
+ return option[0] === value;
+ })[1];
+ } else if (field instanceof FieldMany2One) {
+ return field.get_displayed();
+ }
+ return value;
+ };
+ var fields = _.chain(this.fields)
+ .map(function (field) {
+ var value = field.get_value();
+ // ignore fields which are empty, invisible, readonly, o2m
+ // or m2m
+ if (!value
+ || field.get('invisible')
+ || field.get("readonly")
+ || field.field.type === 'one2many'
+ || field.field.type === 'many2many'
+ || field.field.type === 'binary'
+ || field.password) {
+ return false;
+ }
+
+ return {
+ name: field.name,
+ string: field.string,
+ value: value,
+ displayed: display(field, value),
+ };
+ })
+ .compact()
+ .sortBy(function (field) { return field.string; })
+ .value();
+ var conditions = _.chain(self.fields)
+ .filter(function (field) { return field.field.change_default; })
+ .map(function (field) {
+ var value = field.get_value();
+ return {
+ name: field.name,
+ string: field.string,
+ value: value,
+ displayed: display(field, value),
+ };
+ })
+ .value();
+ var d = new Dialog(this, {
+ title: _t("Set Default"),
+ args: {
+ fields: fields,
+ conditions: conditions
+ },
+ buttons: [
+ {text: _t("Close"), click: function () { d.close(); }},
+ {text: _t("Save default"), click: function () {
+ var $defaults = d.$el.find('#formview_default_fields');
+ var field_to_set = $defaults.val();
+ if (!field_to_set) {
+ $defaults.parent().addClass('oe_form_invalid');
+ return;
+ }
+ var condition = d.$el.find('#formview_default_conditions').val(),
+ all_users = d.$el.find('#formview_default_all').is(':checked');
+ new data.DataSet(self, 'ir.values').call(
+ 'set_default', [
+ self.dataset.model,
+ field_to_set,
+ self.fields[field_to_set].get_value(),
+ all_users,
+ true,
+ condition || false
+ ]).done(function () { d.close(); });
+ }}
+ ]
+ });
+ d.template = 'FormView.set_default';
+ d.open();
+ },
+ register_field: function(field, name) {
+ this.fields[name] = field;
+ this.fields_order.push(name);
+ if (JSON.parse(field.node.attrs.default_focus || "0")) {
+ this.default_focus_field = field;
+ }
+
+ field.on('focused', null, this.proxy('widgetFocused'))
+ .on('blurred', null, this.proxy('widgetBlurred'));
+ if (this.get_field_desc(name).translate) {
+ this.translatable_fields.push(field);
+ }
+ field.on('changed_value', this, function() {
+ if (field.is_syntax_valid()) {
+ this.trigger('field_changed:' + name);
+ }
+ if (field._inhibit_on_change_flag) {
+ return;
+ }
+ field._dirty_flag = true;
+ if (field.is_syntax_valid()) {
+ this.do_onchange(field);
+ this.on_form_changed(true);
+ this.do_notify_change();
+ }
+ });
+ },
+ get_field_desc: function(field_name) {
+ return this.fields_view.fields[field_name];
+ },
+ get_field_value: function(field_name) {
+ return this.fields[field_name].get_value();
+ },
+ compute_domain: function(expression) {
+ return data.compute_domain(expression, this.fields);
+ },
+ _build_view_fields_values: function() {
+ var a_dataset = this.dataset;
+ var fields_values = this.get_fields_values();
+ var active_id = a_dataset.ids[a_dataset.index];
+ _.extend(fields_values, {
+ active_id: active_id || false,
+ active_ids: active_id ? [active_id] : [],
+ active_model: a_dataset.model,
+ parent: {}
+ });
+ if (a_dataset.parent_view) {
+ fields_values.parent = a_dataset.parent_view.get_fields_values();
+ }
+ return fields_values;
+ },
+ build_eval_context: function() {
+ var a_dataset = this.dataset;
+ return new data.CompoundContext(a_dataset.get_context(), this._build_view_fields_values());
+ },
+});
+
+/**
+ * Interface to be implemented by rendering engines for the form view.
+ */
+var FormRenderingEngineInterface = Class.extend({
+ set_fields_view: function(fields_view) {},
+ set_fields_registry: function(fields_registry) {},
+ render_to: function($el) {},
+});
+
+/**
+ * Default rendering engine for the form view.
+ *
+ * It is necessary to set the view using set_view() before usage.
+ */
+var FormRenderingEngine = FormRenderingEngineInterface.extend({
+ init: function(view) {
+ this.view = view;
+ },
+ set_fields_view: function(fvg) {
+ this.fvg = fvg;
+ this.version = parseFloat(this.fvg.arch.attrs.version);
+ if (isNaN(this.version)) {
+ this.version = 7.0;
+ }
+ },
+ set_tags_registry: function(tags_registry) {
+ this.tags_registry = tags_registry;
+ },
+ set_fields_registry: function(fields_registry) {
+ this.fields_registry = fields_registry;
+ },
+ set_widgets_registry: function(widgets_registry) {
+ this.widgets_registry = widgets_registry;
+ },
+ // Backward compatibility tools, current default version: v7
+ process_version: function() {
+ if (this.version < 7.0) {
+ this.$form.find('form:first').wrapInner('<group col="4"/>');
+ this.$form.find('page').each(function() {
+ if (!$(this).parents('field').length) {
+ $(this).wrapInner('<group col="4"/>');
+ }
+ });
+ }
+ },
+ get_arch_fragment: function() {
+ var doc = $.parseXML(utils.json_node_to_xml(this.fvg.arch)).documentElement;
+ // IE won't allow custom button@type and will revert it to spec default : 'submit'
+ $('button', doc).each(function() {
+ $(this).attr('data-button-type', $(this).attr('type')).attr('type', 'button');
+ });
+ // IE's html parser is also a css parser. How convenient...
+ $('board', doc).each(function() {
+ $(this).attr('layout', $(this).attr('style'));
+ });
+ return $('<div class="oe_form"/>').append(utils.xml_to_str(doc));
+ },
+ render_to: function($target) {
+ var self = this;
+ this.$target = $target;
+
+ this.$form = this.get_arch_fragment();
+
+ this.process_version();
+
+ this.fields_to_init = [];
+ this.tags_to_init = [];
+ this.widgets_to_init = [];
+ this.labels = {};
+ this.process(this.$form);
+
+ this.$form.appendTo(this.$target);
+
+ this.to_replace = [];
+
+ _.each(this.fields_to_init, function($elem) {
+ var name = $elem.attr("name");
+ if (!self.fvg.fields[name]) {
+ throw new Error(_.str.sprintf(_t("Field '%s' specified in view could not be found."), name));
+ }
+ var obj = self.fields_registry.get_any([$elem.attr('widget'), self.fvg.fields[name].type]);
+ if (!obj) {
+ throw new Error(_.str.sprintf(_t("Widget type '%s' is not implemented"), $elem.attr('widget')));
+ }
+ var w = new (obj)(self.view, utils.xml_to_json($elem[0]));
+ var $label = self.labels[$elem.attr("name")];
+ if ($label) {
+ w.set_input_id($label.attr("for"));
+ }
+ self.alter_field(w);
+ self.view.register_field(w, $elem.attr("name"));
+ self.to_replace.push([w, $elem]);
+ });
+ _.each(this.tags_to_init, function($elem) {
+ var tag_name = $elem[0].tagName.toLowerCase();
+ var obj = self.tags_registry.get(tag_name);
+ var w = new (obj)(self.view, utils.xml_to_json($elem[0]));
+ self.to_replace.push([w, $elem]);
+ });
+ _.each(this.widgets_to_init, function($elem) {
+ var widget_type = $elem.attr("type");
+ var obj = self.widgets_registry.get(widget_type);
+ var w = new (obj)(self.view, utils.xml_to_json($elem[0]));
+ self.to_replace.push([w, $elem]);
+ });
+ },
+ init_fields: function() {
+ var defs = [];
+ _.each(this.to_replace, function(el) {
+ defs.push(el[0].replace(el[1]));
+ if (el[1].children().length) {
+ el[0].$el.append(el[1].children());
+ }
+ });
+ this.to_replace = [];
+ return $.when.apply($, defs);
+ },
+ render_element: function(template /* dictionaries */) {
+ var dicts = [].slice.call(arguments).slice(1);
+ var dict = _.extend.apply(_, dicts);
+ dict['classnames'] = dict['class'] || ''; // class is a reserved word and might caused problem to Safari when used from QWeb
+ return $(QWeb.render(template, dict));
+ },
+ alter_field: function(field) {
+ },
+ toggle_layout_debugging: function() {
+ if (!this.$target.has('.oe_layout_debug_cell:first').length) {
+ this.$target.find('[title]').removeAttr('title');
+ this.$target.find('.oe_form_group_cell').each(function() {
+ var text = 'W:' + ($(this).attr('width') || '') + ' - C:' + $(this).attr('colspan');
+ $(this).attr('title', text);
+ });
+ }
+ this.$target.toggleClass('oe_layout_debugging');
+ },
+ process: function($tag) {
+ var self = this;
+ var tagname = $tag[0].nodeName.toLowerCase();
+ if (this.tags_registry.contains(tagname)) {
+ this.tags_to_init.push($tag);
+ return (tagname === 'button') ? this.process_button($tag) : $tag;
+ }
+ var fn = self['process_' + tagname];
+ if (fn) {
+ var args = [].slice.call(arguments);
+ args[0] = $tag;
+ return fn.apply(self, args);
+ } else {
+ // generic tag handling, just process children
+ $tag.children().each(function() {
+ self.process($(this));
+ });
+ self.handle_common_properties($tag, $tag);
+ $tag.removeAttr("modifiers");
+ return $tag;
+ }
+ },
+ process_button: function ($button) {
+ var self = this;
+ $button.children().each(function() {
+ self.process($(this));
+ });
+ return $button;
+ },
+ process_widget: function($widget) {
+ this.widgets_to_init.push($widget);
+ return $widget;
+ },
+ process_sheet: function($sheet) {
+ var $new_sheet = this.render_element('FormRenderingSheet', $sheet.getAttributes());
+ this.handle_common_properties($new_sheet, $sheet);
+ var $dst = $new_sheet.find('.oe_form_sheet');
+ $sheet.contents().appendTo($dst);
+ $sheet.before($new_sheet).remove();
+ this.process($new_sheet);
+ },
+ process_form: function($form) {
+ if ($form.find('> sheet').length === 0) {
+ $form.addClass('oe_form_nosheet');
+ }
+ var $new_form = this.render_element('FormRenderingForm', $form.getAttributes());
+ this.handle_common_properties($new_form, $form);
+ $form.contents().appendTo($new_form);
+ if ($form[0] === this.$form[0]) {
+ // If root element, replace it
+ this.$form = $new_form;
+ } else {
+ $form.before($new_form).remove();
+ }
+ this.process($new_form);
+ },
+ /*
+ * Used by direct <field> children of a <group> tag only
+ * This method will add the implicit <label...> for every field
+ * in the <group>
+ */
+ preprocess_field: function($field) {
+ var self = this;
+ var name = $field.attr('name'),
+ field_colspan = parseInt($field.attr('colspan'), 10),
+ field_modifiers = JSON.parse($field.attr('modifiers') || '{}');
+
+ if ($field.attr('nolabel') === '1')
+ return;
+ $field.attr('nolabel', '1');
+ var found = false;
+ this.$form.find('label[for="' + name + '"]').each(function(i ,el) {
+ $(el).parents().each(function(unused, tag) {
+ var name = tag.tagName.toLowerCase();
+ if (name === "field" || name in self.tags_registry.map)
+ found = true;
+ });
+ });
+ if (found)
+ return;
+
+ var $label = $('<label/>').attr({
+ 'for' : name,
+ "modifiers": JSON.stringify({invisible: field_modifiers.invisible}),
+ "string": $field.attr('string'),
+ "help": $field.attr('help'),
+ "class": $field.attr('class'),
+ });
+ $label.insertBefore($field);
+ if (field_colspan > 1) {
+ $field.attr('colspan', field_colspan - 1);
+ }
+ return $label;
+ },
+ process_field: function($field) {
+ if ($field.parent().is('group')) {
+ // No implicit labels for normal fields, only for <group> direct children
+ var $label = this.preprocess_field($field);
+ if ($label) {
+ this.process($label);
+ }
+ }
+ this.fields_to_init.push($field);
+ return $field;
+ },
+ process_group: function($group) {
+ var self = this;
+ $group.children('field').each(function() {
+ self.preprocess_field($(this));
+ });
+ var $new_group = this.render_element('FormRenderingGroup', $group.getAttributes());
+ var $table;
+ if ($new_group.first().is('table.oe_form_group')) {
+ $table = $new_group;
+ } else if ($new_group.filter('table.oe_form_group').length) {
+ $table = $new_group.filter('table.oe_form_group').first();
+ } else {
+ $table = $new_group.find('table.oe_form_group').first();
+ }
+
+ var $tr, $td,
+ cols = parseInt($group.attr('col') || 2, 10),
+ row_cols = cols;
+
+ var children = [];
+ $group.children().each(function() {
+ var $child = $(this);
+ var colspan = parseInt($child.attr('colspan') || 1, 10);
+ var tagName = $child[0].tagName.toLowerCase();
+ var $td = $('<td/>').addClass('oe_form_group_cell').attr('colspan', colspan);
+ var newline = tagName === 'newline';
+
+ // Note FME: those classes are used in layout debug mode
+ if ($tr && row_cols > 0 && (newline || row_cols < colspan)) {
+ $tr.addClass('oe_form_group_row_incomplete');
+ if (newline) {
+ $tr.addClass('oe_form_group_row_newline');
+ }
+ }
+ if (newline) {
+ $tr = null;
+ return;
+ }
+ if (!$tr || row_cols < colspan) {
+ $tr = $('<tr/>').addClass('oe_form_group_row').appendTo($table);
+ row_cols = cols;
+ } else if (tagName==='group') {
+ // When <group> <group/><group/> </group>, we need a spacing between the two groups
+ $td.addClass('oe_group_right');
+ }
+ row_cols -= colspan;
+
+ // invisibility transfer
+ var field_modifiers = JSON.parse($child.attr('modifiers') || '{}');
+ var invisible = field_modifiers.invisible;
+ 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();
+
+ $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');
+ if ($child.attr('cell-class')) {
+ $td.addClass($child.attr('cell-class'));
+ }
+ switch ($child[0].tagName.toLowerCase()) {
+ case 'separator':
+ break;
+ case 'label':
+ if ($child.attr('for')) {
+ $td.attr('width', '1%').addClass('oe_form_group_cell_label');
+ row_cols-= $td.attr('colspan') || 1;
+ total--;
+ }
+ break;
+ default:
+ var width = _.str.trim($child.attr('width') || ''),
+ iwidth = parseInt(width, 10);
+ if (iwidth) {
+ if (width.substr(-1) === '%') {
+ total -= iwidth;
+ width = iwidth + '%';
+ } else {
+ // Absolute width
+ $td.css('min-width', width + 'px');
+ }
+ $td.attr('width', width);
+ $child.removeAttr('width');
+ row_cols-= $td.attr('colspan') || 1;
+ } else {
+ to_compute.push($td);
+ }
+
+ }
+ });
+ if (row_cols) {
+ var unit = Math.floor(total / row_cols);
+ if (!$(this).is('.oe_form_group_row_incomplete')) {
+ _.each(to_compute, function($td) {
+ var width = parseInt($td.attr('colspan'), 10) * unit;
+ $td.attr('width', width + '%');
+ total -= width;
+ });
+ }
+ }
+ });
+ _.each(children, function(el) {
+ self.process($(el));
+ });
+ this.handle_common_properties($new_group, $group);
+ return $new_group;
+ },
+ process_notebook: function($notebook) {
+ var self = this;
+
+ // Extract useful info from the notebook xml declaration
+ var pages = [];
+ $notebook.find('> page').each(function() {
+ var $page = $(this);
+ var page_attrs = $page.getAttributes();
+ page_attrs.id = _.uniqueId('notebook_page_');
+ page_attrs.contents = $page.html();
+ page_attrs.ref = $page; // reference to the current page node in the xml declaration
+ pages.push(page_attrs);
+ });
+
+ // Render the notebook and replace $notebook with it
+ var $new_notebook = self.render_element('FormRenderingNotebook', {'pages': pages});
+ $notebook.before($new_notebook).remove();
+
+ // Bind the invisibility changers and find the page to display
+ var pageid_to_display;
+ _.each(pages, function(page) {
+ var $tab = $new_notebook.find('a[href=#' + page.id + ']').parent();
+ var $content = $new_notebook.find('#' + page.id);
+
+ // Case: <page autofocus="autofocus">;
+ // We attach the autofocus attribute to the node because it can be useful during the
+ // page execution
+ self.attach_node_attr($tab, page.ref, 'autofocus');
+ self.attach_node_attr($content, page.ref, 'autofocus');
+ if (!pageid_to_display && page.autofocus) {
+ // If multiple autofocus, keep the first one
+ pageid_to_display = page.id;
+ }
+
+ // Case: <page attrs="{'invisible': domain}">;
+ self.handle_common_properties($tab, page.ref, common.NotebookInvisibilityChanger);
+ self.handle_common_properties($content, page.ref, common.NotebookInvisibilityChanger);
+ });
+ if (!pageid_to_display) {
+ pageid_to_display = $new_notebook.find('div[role="tabpanel"]:not(.oe_form_invisible):first').attr('id');
+ }
+
+ // Display page. Note: we can't use bootstrap's show function because it is looking for
+ // document attached DOM, and the form view is only attached when everything is processed
+ $new_notebook.find('a[href=#' + pageid_to_display + ']').parent().addClass('active');
+ $new_notebook.find('#' + pageid_to_display).addClass('active');
+
+ self.process($new_notebook.children());
+ self.handle_common_properties($new_notebook, $notebook);
+
+ return $new_notebook;
+ },
+ process_separator: function($separator) {
+ var $new_separator = this.render_element('FormRenderingSeparator', $separator.getAttributes());
+ $separator.before($new_separator).remove();
+ this.handle_common_properties($new_separator, $separator);
+ return $new_separator;
+ },
+ process_label: function($label) {
+ 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', 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, InvisibilityChanger) {
+ var str_modifiers = $node.attr("modifiers") || "{}";
+ var modifiers = JSON.parse(str_modifiers);
+ var ic = null;
+ if (modifiers.invisible !== undefined) {
+ var InvisibilityChangerCls = InvisibilityChanger || common.InvisibilityChanger;
+ ic = new InvisibilityChangerCls(this.view, this.view, modifiers.invisible, $new_element);
+ }
+ $new_element.addClass($node.attr("class") || "");
+ $new_element.attr('style', $node.attr('style'));
+ return {invisibility_changer: ic,};
+ },
+ /**
+ * Attach a node attribute to an html element to be able to use it after
+ * the processing of the page.
+ */
+ attach_node_attr: function($new_element, $node, attr) {
+ $new_element.data(attr, $node.attr(attr));
+ }
+});
+
+core.view_registry.add('form', FormView);
+
+return FormView;
+
+});
+