summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/view_form.js
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/src/js/view_form.js')
-rw-r--r--addons/web/static/src/js/view_form.js6443
1 files changed, 0 insertions, 6443 deletions
diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js
deleted file mode 100644
index bdd1ecb6225..00000000000
--- a/addons/web/static/src/js/view_form.js
+++ /dev/null
@@ -1,6443 +0,0 @@
-(function() {
-
-var instance = openerp;
-var _t = instance.web._t,
- _lt = instance.web._lt;
-var QWeb = instance.web.qweb;
-
-/** @namespace */
-instance.web.form = {};
-
-/**
- * Interface implemented by the form view or any other object
- * able to provide the features necessary for the fields to work.
- *
- * Properties:
- * - display_invalid_fields : if true, all fields where is_valid() return true should
- * be displayed as invalid.
- * - actual_mode : the current mode of the field manager. Can be "view", "edit" or "create".
- * Events:
- * - view_content_has_changed : when the values of the fields have changed. When
- * this event is triggered all fields should reprocess their modifiers.
- * - field_changed:<field_name> : when the value of a field change, an event is triggered
- * named "field_changed:<field_name>" with <field_name> replaced by the name of the field.
- * This event is not related to the on_change mechanism of OpenERP and is always called
- * when the value of a field is setted or changed. This event is only triggered when the
- * value of the field is syntactically valid, but it can be triggered when the value
- * is sematically invalid (ie, when a required field is false). It is possible that an event
- * about a precise field is never triggered even if that field exists in the view, in that
- * case the value of the field is assumed to be false.
- */
-instance.web.form.FieldManagerMixin = {
- /**
- * Must return the asked field as in fields_get.
- */
- get_field_desc: function(field_name) {},
- /**
- * Returns the current value of a field present in the view. See the get_value() method
- * method in FieldInterface for further information.
- */
- get_field_value: function(field_name) {},
- /**
- Gives new values for the fields contained in the view. The new values could not be setted
- right after the call to this method. Setting new values can trigger on_changes.
-
- @param {Object} values A dictonary with key = field name and value = new value.
- @return {$.Deferred} Is resolved after all the values are setted.
- */
- set_values: function(values) {},
- /**
- Computes an OpenERP domain.
-
- @param {Array} expression An OpenERP domain.
- @return {boolean} The computed value of the domain.
- */
- compute_domain: function(expression) {},
- /**
- Builds an evaluation context for the resolution of the fields' contexts. Please note
- the field are only supposed to use this context to evualuate their own, they should not
- extend it.
-
- @return {CompoundContext} An OpenERP context.
- */
- build_eval_context: function() {},
-};
-
-instance.web.views.add('form', 'instance.web.FormView');
-/**
- * Properties:
- * - actual_mode: always "view", "edit" or "create". Read-only property. Determines
- * the mode used by the view.
- */
-instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerMixin, {
- /**
- * Indicates that this view is not searchable, and thus that no search
- * view should be displayed (if there is one active).
- */
- searchable: false,
- template: "FormView",
- display_name: _lt('Form'),
- view_type: "form",
- /**
- * @constructs instance.web.FormView
- * @extends instance.web.View
- *
- * @param {instance.web.Session} session the current openerp session
- * @param {instance.web.DataSet} dataset the dataset this view will work with
- * @param {String} view_id the identifier of the OpenERP view object
- *
- * @property {instance.web.Registry} registry=instance.web.form.widgets widgets registry for this form view instance
- */
- init: function(parent, dataset, view_id, options) {
- var self = this;
- this._super(parent);
- this.ViewManager = parent;
- this.set_default_options(options);
- this.dataset = dataset;
- this.model = dataset.model;
- this.view_id = view_id || false;
- this.fields_view = {};
- this.fields = {};
- this.fields_order = [];
- this.datarecord = {};
- this._onchange_specs = {};
- this.onchanges_mutex = new $.Mutex();
- this.default_focus_field = null;
- this.default_focus_button = null;
- this.fields_registry = instance.web.form.widgets;
- this.tags_registry = instance.web.form.tags;
- this.widgets_registry = instance.web.form.custom_widgets;
- this.has_been_loaded = $.Deferred();
- this.translatable_fields = [];
- _.defaults(this.options, {
- "not_interactible_on_create": false,
- "initial_mode": "view",
- "disable_autofocus": false,
- "footer_to_buttons": false,
- });
- this.is_initialized = $.Deferred();
- this.mutating_mutex = new $.Mutex();
- this.save_list = [];
- this.render_value_defs = [];
- this.reload_mutex = new $.Mutex();
- this.__clicked_inside = false;
- this.__blur_timeout = null;
- this.rendering_engine = new instance.web.form.FormRenderingEngine(this);
- self.set({actual_mode: self.options.initial_mode});
- this.has_been_loaded.done(function() {
- self._build_onchange_specs();
- self.on("change:actual_mode", self, self.check_actual_mode);
- self.check_actual_mode();
- self.on("change:actual_mode", self, self.init_pager);
- self.init_pager();
- });
- self.on("load_record", self, self.load_record);
- instance.web.bus.on('clear_uncommitted_changes', this, function(e) {
- if (!this.can_be_discarded()) {
- e.preventDefault();
- }
- });
- },
- view_loading: function(r) {
- return this.load_form(r);
- },
- destroy: function() {
- _.each(this.get_widgets(), function(w) {
- w.off('focused blurred');
- w.destroy();
- });
- if (this.$el) {
- this.$el.off('.formBlur');
- }
- this._super();
- },
- load_form: function(data) {
- var self = this;
- if (!data) {
- throw new Error(_t("No data provided."));
- }
- if (this.arch) {
- throw "Form view does not support multiple calls to load_form";
- }
- this.fields_order = [];
- this.fields_view = data;
-
- this.rendering_engine.set_fields_registry(this.fields_registry);
- this.rendering_engine.set_tags_registry(this.tags_registry);
- this.rendering_engine.set_widgets_registry(this.widgets_registry);
- this.rendering_engine.set_fields_view(data);
- var $dest = this.$el.hasClass("oe_form_container") ? this.$el : this.$el.find('.oe_form_container');
- this.rendering_engine.render_to($dest);
-
- this.$el.on('mousedown.formBlur', function () {
- self.__clicked_inside = true;
- });
-
- this.$buttons = $(QWeb.render("FormView.buttons", {'widget':self}));
- if (this.options.$buttons) {
- this.$buttons.appendTo(this.options.$buttons);
- } else {
- this.$el.find('.oe_form_buttons').replaceWith(this.$buttons);
- }
- this.$buttons.on('click', '.oe_form_button_create',
- this.guard_active(this.on_button_create));
- this.$buttons.on('click', '.oe_form_button_edit',
- this.guard_active(this.on_button_edit));
- this.$buttons.on('click', '.oe_form_button_save',
- this.guard_active(this.on_button_save));
- this.$buttons.on('click', '.oe_form_button_cancel',
- this.guard_active(this.on_button_cancel));
- if (this.options.footer_to_buttons) {
- this.$el.find('footer').appendTo(this.$buttons);
- }
-
- this.$sidebar = this.options.$sidebar || this.$el.find('.oe_form_sidebar');
- if (!this.sidebar && this.options.$sidebar) {
- this.sidebar = new instance.web.Sidebar(this);
- this.sidebar.appendTo(this.$sidebar);
- if (this.fields_view.toolbar) {
- this.sidebar.add_toolbar(this.fields_view.toolbar);
- }
- this.sidebar.add_items('other', _.compact([
- self.is_action_enabled('delete') && { label: _t('Delete'), callback: self.on_button_delete },
- self.is_action_enabled('create') && { label: _t('Duplicate'), callback: self.on_button_duplicate }
- ]));
- }
-
- this.has_been_loaded.resolve();
-
- // Add bounce effect on button 'Edit' when click on readonly page view.
- this.$el.find(".oe_form_group_row,.oe_form_field,label,h1,.oe_title,.oe_notebook_page, .oe_list_content").on('click', function (e) {
- if(self.get("actual_mode") == "view") {
- var $button = self.options.$buttons.find(".oe_form_button_edit");
- $button.openerpBounce();
- e.stopPropagation();
- instance.web.bus.trigger('click', e);
- }
- });
- //bounce effect on red button when click on statusbar.
- this.$el.find(".oe_form_field_status:not(.oe_form_status_clickable)").on('click', function (e) {
- if((self.get("actual_mode") == "view")) {
- var $button = self.$el.find(".oe_highlight:not(.oe_form_invisible)").css({'float':'left','clear':'none'});
- $button.openerpBounce();
- e.stopPropagation();
- }
- });
- this.trigger('form_view_loaded', data);
- return $.when();
- },
- widgetFocused: function() {
- // Clear click flag if used to focus a widget
- this.__clicked_inside = false;
- if (this.__blur_timeout) {
- clearTimeout(this.__blur_timeout);
- this.__blur_timeout = null;
- }
- },
- widgetBlurred: function() {
- if (this.__clicked_inside) {
- // clicked in an other section of the form (than the currently
- // focused widget) => just ignore the blurring entirely?
- this.__clicked_inside = false;
- return;
- }
- var self = this;
- // clear timeout, if any
- this.widgetFocused();
- this.__blur_timeout = setTimeout(function () {
- self.trigger('blurred');
- }, 0);
- },
-
- do_load_state: function(state, warm) {
- if (state.id && this.datarecord.id != state.id) {
- if (this.dataset.get_id_index(state.id) === null) {
- this.dataset.ids.push(state.id);
- }
- this.dataset.select_id(state.id);
- this.do_show();
- }
- },
- /**
- *
- * @param {Object} [options]
- * @param {Boolean} [mode=undefined] If specified, switch the form to specified mode. Can be "edit" or "view".
- * @param {Boolean} [reload=true] whether the form should reload its content on show, or use the currently loaded record
- * @return {$.Deferred}
- */
- do_show: function (options) {
- var self = this;
- options = options || {};
- if (this.sidebar) {
- this.sidebar.$el.show();
- }
- if (this.$buttons) {
- this.$buttons.show();
- }
- this.$el.show().css({
- opacity: '0',
- filter: 'alpha(opacity = 0)'
- });
- this.$el.add(this.$buttons).removeClass('oe_form_dirty');
-
- var shown = this.has_been_loaded;
- if (options.reload !== false) {
- shown = shown.then(function() {
- if (self.dataset.index === null) {
- // null index means we should start a new record
- return self.on_button_new();
- }
- var fields = _.keys(self.fields_view.fields);
- fields.push('display_name');
- return self.dataset.read_index(fields, {
- context: { 'bin_size': true, '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)'
- });
- instance.web.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(instance.web.BufferedDataSet.virtual_id_regex)) {
- delete field_values.id;
- }
- if (this.dataset.parent_view) {
- // this belongs to a parent view: add parent field if possible
- var parent_view = this.dataset.parent_view;
- var child_name = this.dataset.child_name;
- var parent_name = parent_view.get_field_desc(child_name).relation_field;
- if (parent_name) {
- // consider all fields except the inverse of the parent field
- var parent_values = parent_view.get_fields_values();
- delete parent_values[child_name];
- field_values[parent_name] = parent_values;
- }
- }
- return field_values;
- },
-
- do_onchange: function(widget) {
- var self = this;
- var onchange_specs = self._onchange_specs;
- try {
- var def = $.when({});
- var change_spec = widget ? onchange_specs[widget.name] : null;
- if (!widget || (!_.isEmpty(change_spec) && change_spec !== "0")) {
- var ids = [],
- trigger_field_name = widget ? widget.name : self._onchange_fields,
- values = self._get_onchange_values(),
- context = new instance.web.CompoundContext(self.dataset.get_context());
-
- if (widget && widget.build_context()) {
- context.add(widget.build_context());
- }
- if (self.dataset.parent_view) {
- var parent_name = self.dataset.parent_view.get_field_desc(self.dataset.child_name).relation_field;
- context.add({field_parent: parent_name});
- }
-
- if (self.datarecord.id && !instance.web.BufferedDataSet.virtual_id_regex.test(self.datarecord.id)) {
- // In case of a o2m virtual id, we should pass an empty ids list
- ids.push(self.datarecord.id);
- }
- def = self.alive(new instance.web.Model(self.dataset.model).call(
- "onchange", [ids, values, trigger_field_name, onchange_specs, context]));
- }
- this.onchanges_mutex.exec(function(){
- return def.then(function(response) {
- var fields = {};
- if (widget){
- fields[widget.name] = widget.field;
- }
- else{
- fields = self.fields_view.fields;
- }
- var defs = [];
- _.each(fields, function(field, fieldname){
- if (field && field.change_default) {
- var value_;
- if (response.value && (fieldname in response.value)) {
- // Use value from onchange if onchange executed
- value_ = response.value[fieldname];
- } else {
- // otherwise get form value for field
- value_ = self.fields[fieldname].get_value();
- }
- var condition = fieldname + '=' + value_;
-
- if (value_) {
- defs.push(self.alive(new instance.web.Model('ir.values').call(
- 'get_defaults', [self.model, condition]
- )).then(function (results) {
- if (!results.length) {
- return response;
- }
- if (!response.value) {
- response.value = {};
- }
- for(var i=0; i<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);
- instance.webclient.crashmanager.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 instance.web.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);
- instance.webclient.crashmanager.show_message(e);
- return $.Deferred().reject();
- }
- },
- _process_operations: function() {
- var self = this;
- return this.mutating_mutex.exec(function() {
- function iterate() {
-
- var mutex = new $.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 $.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();
- var menu = instance.webclient.menu;
- if (menu) {
- menu.do_reload_needaction();
- }
- instance.web.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 instance.web.form.FormWidget;
- });
- },
- get_fields_values: function() {
- var values = {};
- var ids = this.get_selected_ids();
- values["id"] = ids.length > 0 ? ids[0] : false;
- _.each(this.fields, function(value_, key) {
- values[key] = value_.get_value();
- });
- return values;
- },
- get_selected_ids: function() {
- var id = this.dataset.ids[this.dataset.index];
- return id ? [id] : [];
- },
- recursive_save: function() {
- var self = this;
- return $.when(this.save()).then(function(res) {
- if (self.dataset.parent_view)
- return self.dataset.parent_view.recursive_save();
- });
- },
- recursive_reload: function() {
- var self = this;
- var pre = $.when();
- if (self.dataset.parent_view)
- pre = self.dataset.parent_view.recursive_reload();
- return pre.then(function() {
- return self.reload();
- });
- },
- is_dirty: function() {
- return _.any(this.fields, function (value_) {
- return value_._dirty_flag;
- });
- },
- is_interactible_record: function() {
- var id = this.datarecord.id;
- if (!id) {
- if (this.options.not_interactible_on_create)
- return false;
- } else if (typeof(id) === "string") {
- if(instance.web.BufferedDataSet.virtual_id_regex.test(id))
- return false;
- }
- return true;
- },
- sidebar_eval_context: function () {
- return $.when(this.build_eval_context());
- },
- open_defaults_dialog: function () {
- var self = this;
- var display = function (field, value) {
- if (!value) { return value; }
- if (field instanceof instance.web.form.FieldSelection) {
- return _(field.get('values')).find(function (option) {
- return option[0] === value;
- })[1];
- } else if (field instanceof instance.web.form.FieldMany2One) {
- return field.get_displayed();
- }
- return value;
- };
- var fields = _.chain(this.fields)
- .map(function (field) {
- var value = field.get_value();
- // ignore fields which are empty, invisible, readonly, o2m
- // or m2m
- if (!value
- || field.get('invisible')
- || field.get("readonly")
- || field.field.type === 'one2many'
- || field.field.type === 'many2many'
- || field.field.type === 'binary'
- || field.password) {
- return false;
- }
-
- return {
- name: field.name,
- string: field.string,
- value: value,
- displayed: display(field, value),
- };
- })
- .compact()
- .sortBy(function (field) { return field.string; })
- .value();
- var conditions = _.chain(self.fields)
- .filter(function (field) { return field.field.change_default; })
- .map(function (field) {
- var value = field.get_value();
- return {
- name: field.name,
- string: field.string,
- value: value,
- displayed: display(field, value),
- };
- })
- .value();
- var d = new instance.web.Dialog(this, {
- title: _t("Set Default"),
- args: {
- fields: fields,
- conditions: conditions
- },
- buttons: [
- {text: _t("Close"), click: function () { d.close(); }},
- {text: _t("Save default"), click: function () {
- var $defaults = d.$el.find('#formview_default_fields');
- var field_to_set = $defaults.val();
- if (!field_to_set) {
- $defaults.parent().addClass('oe_form_invalid');
- return;
- }
- var condition = d.$el.find('#formview_default_conditions').val(),
- all_users = d.$el.find('#formview_default_all').is(':checked');
- new instance.web.DataSet(self, 'ir.values').call(
- 'set_default', [
- self.dataset.model,
- field_to_set,
- self.fields[field_to_set].get_value(),
- all_users,
- true,
- condition || false
- ]).done(function () { d.close(); });
- }}
- ]
- });
- d.template = 'FormView.set_default';
- d.open();
- },
- register_field: function(field, name) {
- this.fields[name] = field;
- this.fields_order.push(name);
- if (JSON.parse(field.node.attrs.default_focus || "0")) {
- this.default_focus_field = field;
- }
-
- field.on('focused', null, this.proxy('widgetFocused'))
- .on('blurred', null, this.proxy('widgetBlurred'));
- if (this.get_field_desc(name).translate) {
- this.translatable_fields.push(field);
- }
- field.on('changed_value', this, function() {
- if (field.is_syntax_valid()) {
- this.trigger('field_changed:' + name);
- }
- if (field._inhibit_on_change_flag) {
- return;
- }
- field._dirty_flag = true;
- if (field.is_syntax_valid()) {
- this.do_onchange(field);
- this.on_form_changed(true);
- this.do_notify_change();
- }
- });
- },
- get_field_desc: function(field_name) {
- return this.fields_view.fields[field_name];
- },
- get_field_value: function(field_name) {
- return this.fields[field_name].get_value();
- },
- compute_domain: function(expression) {
- return instance.web.form.compute_domain(expression, this.fields);
- },
- _build_view_fields_values: function() {
- var a_dataset = this.dataset;
- var fields_values = this.get_fields_values();
- var active_id = a_dataset.ids[a_dataset.index];
- _.extend(fields_values, {
- active_id: active_id || false,
- active_ids: active_id ? [active_id] : [],
- active_model: a_dataset.model,
- parent: {}
- });
- if (a_dataset.parent_view) {
- fields_values.parent = a_dataset.parent_view.get_fields_values();
- }
- return fields_values;
- },
- build_eval_context: function() {
- var a_dataset = this.dataset;
- return new instance.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values());
- },
-});
-
-/**
- * Interface to be implemented by rendering engines for the form view.
- */
-instance.web.form.FormRenderingEngineInterface = instance.web.Class.extend({
- set_fields_view: function(fields_view) {},
- set_fields_registry: function(fields_registry) {},
- render_to: function($el) {},
-});
-
-/**
- * Default rendering engine for the form view.
- *
- * It is necessary to set the view using set_view() before usage.
- */
-instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInterface.extend({
- init: function(view) {
- this.view = view;
- },
- set_fields_view: function(fvg) {
- this.fvg = fvg;
- this.version = parseFloat(this.fvg.arch.attrs.version);
- if (isNaN(this.version)) {
- this.version = 7.0;
- }
- },
- set_tags_registry: function(tags_registry) {
- this.tags_registry = tags_registry;
- },
- set_fields_registry: function(fields_registry) {
- this.fields_registry = fields_registry;
- },
- set_widgets_registry: function(widgets_registry) {
- this.widgets_registry = widgets_registry;
- },
- // Backward compatibility tools, current default version: v7
- process_version: function() {
- if (this.version < 7.0) {
- this.$form.find('form:first').wrapInner('<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(instance.web.json_node_to_xml(this.fvg.arch)).documentElement;
- // IE won't allow custom button@type and will revert it to spec default : 'submit'
- $('button', doc).each(function() {
- $(this).attr('data-button-type', $(this).attr('type')).attr('type', 'button');
- });
- // IE's html parser is also a css parser. How convenient...
- $('board', doc).each(function() {
- $(this).attr('layout', $(this).attr('style'));
- });
- return $('<div class="oe_form"/>').append(instance.web.xml_to_str(doc));
- },
- render_to: function($target) {
- var self = this;
- this.$target = $target;
-
- this.$form = this.get_arch_fragment();
-
- this.process_version();
-
- this.fields_to_init = [];
- this.tags_to_init = [];
- this.widgets_to_init = [];
- this.labels = {};
- this.process(this.$form);
-
- this.$form.appendTo(this.$target);
-
- this.to_replace = [];
-
- _.each(this.fields_to_init, function($elem) {
- var name = $elem.attr("name");
- if (!self.fvg.fields[name]) {
- throw new Error(_.str.sprintf(_t("Field '%s' specified in view could not be found."), name));
- }
- var obj = self.fields_registry.get_any([$elem.attr('widget'), self.fvg.fields[name].type]);
- if (!obj) {
- throw new Error(_.str.sprintf(_t("Widget type '%s' is not implemented"), $elem.attr('widget')));
- }
- var w = new (obj)(self.view, instance.web.xml_to_json($elem[0]));
- var $label = self.labels[$elem.attr("name")];
- if ($label) {
- w.set_input_id($label.attr("for"));
- }
- self.alter_field(w);
- self.view.register_field(w, $elem.attr("name"));
- self.to_replace.push([w, $elem]);
- });
- _.each(this.tags_to_init, function($elem) {
- var tag_name = $elem[0].tagName.toLowerCase();
- var obj = self.tags_registry.get_object(tag_name);
- var w = new (obj)(self.view, instance.web.xml_to_json($elem[0]));
- self.to_replace.push([w, $elem]);
- });
- _.each(this.widgets_to_init, function($elem) {
- var widget_type = $elem.attr("type");
- var obj = self.widgets_registry.get_object(widget_type);
- var w = new (obj)(self.view, instance.web.xml_to_json($elem[0]));
- self.to_replace.push([w, $elem]);
- });
- },
- init_fields: function() {
- var defs = [];
- _.each(this.to_replace, function(el) {
- defs.push(el[0].replace(el[1]));
- if (el[1].children().length) {
- el[0].$el.append(el[1].children());
- }
- });
- this.to_replace = [];
- return $.when.apply($, defs);
- },
- render_element: function(template /* dictionaries */) {
- var dicts = [].slice.call(arguments).slice(1);
- var dict = _.extend.apply(_, dicts);
- dict['classnames'] = dict['class'] || ''; // class is a reserved word and might caused problem to Safari when used from QWeb
- return $(QWeb.render(template, dict));
- },
- alter_field: function(field) {
- },
- toggle_layout_debugging: function() {
- if (!this.$target.has('.oe_layout_debug_cell:first').length) {
- this.$target.find('[title]').removeAttr('title');
- this.$target.find('.oe_form_group_cell').each(function() {
- var text = 'W:' + ($(this).attr('width') || '') + ' - C:' + $(this).attr('colspan');
- $(this).attr('title', text);
- });
- }
- this.$target.toggleClass('oe_layout_debugging');
- },
- process: function($tag) {
- var self = this;
- var tagname = $tag[0].nodeName.toLowerCase();
- if (this.tags_registry.contains(tagname)) {
- this.tags_to_init.push($tag);
- return (tagname === 'button') ? this.process_button($tag) : $tag;
- }
- var fn = self['process_' + tagname];
- if (fn) {
- var args = [].slice.call(arguments);
- args[0] = $tag;
- return fn.apply(self, args);
- } else {
- // generic tag handling, just process children
- $tag.children().each(function() {
- self.process($(this));
- });
- self.handle_common_properties($tag, $tag);
- $tag.removeAttr("modifiers");
- return $tag;
- }
- },
- process_button: function ($button) {
- var self = this;
- $button.children().each(function() {
- self.process($(this));
- });
- return $button;
- },
- process_widget: function($widget) {
- this.widgets_to_init.push($widget);
- return $widget;
- },
- process_sheet: function($sheet) {
- var $new_sheet = this.render_element('FormRenderingSheet', $sheet.getAttributes());
- this.handle_common_properties($new_sheet, $sheet);
- var $dst = $new_sheet.find('.oe_form_sheet');
- $sheet.contents().appendTo($dst);
- $sheet.before($new_sheet).remove();
- this.process($new_sheet);
- },
- process_form: function($form) {
- if ($form.find('> sheet').length === 0) {
- $form.addClass('oe_form_nosheet');
- }
- var $new_form = this.render_element('FormRenderingForm', $form.getAttributes());
- this.handle_common_properties($new_form, $form);
- $form.contents().appendTo($new_form);
- if ($form[0] === this.$form[0]) {
- // If root element, replace it
- this.$form = $new_form;
- } else {
- $form.before($new_form).remove();
- }
- this.process($new_form);
- },
- /*
- * Used by direct <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(a,b,c) {
- 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, i) {
- 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, instance.web.form.NotebookInvisibilityChanger);
- self.handle_common_properties($content, page.ref, instance.web.form.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 || instance.web.form.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));
- }
-});
-
-/**
- Welcome.
-
- If you read this documentation, it probably means that you were asked to use a form view widget outside of
- a form view. Before going further, you must understand that those fields were never really created for
- that usage. Don't think that this class will hold the answer to all your problems, at best it will allow
- you to hack the system with more style.
-*/
-instance.web.form.DefaultFieldManager = instance.web.Widget.extend({
- init: function(parent, eval_context) {
- this._super(parent);
- this.field_descs = {};
- this.eval_context = eval_context || {};
- this.set({
- display_invalid_fields: false,
- actual_mode: 'create',
- });
- },
- get_field_desc: function(field_name) {
- if (this.field_descs[field_name] === undefined) {
- this.field_descs[field_name] = {
- string: field_name,
- };
- }
- return this.field_descs[field_name];
- },
- extend_field_desc: function(fields) {
- var self = this;
- _.each(fields, function(v, k) {
- _.extend(self.get_field_desc(k), v);
- });
- },
- get_field_value: function(field_name) {
- return false;
- },
- set_values: function(values) {
- // nothing
- },
- compute_domain: function(expression) {
- return instance.web.form.compute_domain(expression, {});
- },
- build_eval_context: function() {
- return new instance.web.CompoundContext(this.eval_context);
- },
-});
-
-instance.web.form.compute_domain = function(expr, fields) {
- if (! (expr instanceof Array))
- return !! expr;
- var stack = [];
- for (var i = expr.length - 1; i >= 0; i--) {
- var ex = expr[i];
- if (ex.length == 1) {
- var top = stack.pop();
- switch (ex) {
- case '|':
- stack.push(stack.pop() || top);
- continue;
- case '&':
- stack.push(stack.pop() && top);
- continue;
- case '!':
- stack.push(!top);
- continue;
- default:
- throw new Error(_.str.sprintf(
- _t("Unknown operator %s in domain %s"),
- ex, JSON.stringify(expr)));
- }
- }
-
- var field = fields[ex[0]];
- if (!field) {
- throw new Error(_.str.sprintf(
- _t("Unknown field %s in domain %s"),
- ex[0], JSON.stringify(expr)));
- }
- var field_value = field.get_value ? field.get_value() : field.value;
- var op = ex[1];
- var val = ex[2];
-
- switch (op.toLowerCase()) {
- case '=':
- case '==':
- stack.push(_.isEqual(field_value, val));
- break;
- case '!=':
- case '<>':
- stack.push(!_.isEqual(field_value, val));
- break;
- case '<':
- stack.push(field_value < val);
- break;
- case '>':
- stack.push(field_value > val);
- break;
- case '<=':
- stack.push(field_value <= val);
- break;
- case '>=':
- stack.push(field_value >= val);
- break;
- case 'in':
- if (!_.isArray(val)) val = [val];
- stack.push(_(val).contains(field_value));
- break;
- case 'not in':
- if (!_.isArray(val)) val = [val];
- stack.push(!_(val).contains(field_value));
- break;
- default:
- console.warn(
- _t("Unsupported operator %s in domain %s"),
- op, JSON.stringify(expr));
- }
- }
- return _.all(stack, _.identity);
-};
-
-instance.web.form.is_bin_size = function(v) {
- return (/^\d+(\.\d*)? \w+$/).test(v);
-};
-
-/**
- * Must be applied over an class already possessing the PropertiesMixin.
- *
- * Apply the result of the "invisible" domain to this.$el.
- */
-instance.web.form.InvisibilityChangerMixin = {
- init: function(field_manager, invisible_domain) {
- var self = this;
- 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 = self._ic_invisible_modifier === undefined ? false :
- self._ic_field_manager.compute_domain(self._ic_invisible_modifier);
- self.set({"invisible": result});
- });
- this.set({invisible: this._ic_invisible_modifier === true, force_invisible: false});
- var check = function() {
- if (self.get("invisible") || self.get('force_invisible')) {
- self.set({"effective_invisible": true});
- } else {
- self.set({"effective_invisible": false});
- }
- };
- this.on('change:invisible', this, check);
- this.on('change:force_invisible', this, check);
- check.call(this);
- },
- start: function() {
- this.on("change:effective_invisible", this, this._check_visibility);
- this._check_visibility();
- },
- _check_visibility: function() {
- this.$el.toggleClass('oe_form_invisible', this.get("effective_invisible"));
- },
-};
-
-instance.web.form.InvisibilityChanger = instance.web.Class.extend(instance.web.PropertiesMixin, instance.web.form.InvisibilityChangerMixin, {
- init: function(parent, field_manager, invisible_domain, $el) {
- this.setParent(parent);
- instance.web.PropertiesMixin.init.call(this);
- instance.web.form.InvisibilityChangerMixin.init.call(this, field_manager, invisible_domain);
- this.$el = $el;
- this.start();
- },
-});
-
-// Specialization of InvisibilityChanger for the `notebook` form element to handle more
-// elegantly its special cases (i.e. activate the closest visible sibling)
-instance.web.form.NotebookInvisibilityChanger = instance.web.form.InvisibilityChanger.extend({
- // Override start so that it does not call _check_visibility since it will be
- // called again when view content will be loaded (event view_content_has_changed)
- start: function() {
- this.on("change:effective_invisible", this, this._check_visibility);
- },
- _check_visibility: function() {
- this._super();
- if (this.get("effective_invisible") === true) {
- // Switch to invisible
- // Remove this element as active and set a visible sibling active (if there is one)
- if (this.$el.hasClass('active')) {
- this.$el.removeClass('active');
- var visible_siblings = this.$el.siblings(':not(.oe_form_invisible)');
- if (visible_siblings.length) {
- $(visible_siblings[0]).addClass('active');
- }
- }
- } else {
- // Switch to visible
- // If there is no visible active sibling, set this element as active,
- // otherwise if that sibling hasn't autofocus and if we are in edit mode,
- // remove that sibling as active and set this element as active
- var visible_active_sibling = this.$el.siblings(':not(.oe_form_invisible).active');
- if (!(visible_active_sibling.length)) {
- this.$el.addClass('active');
- } else if (!$(visible_active_sibling[0]).data('autofocus') && this._ic_field_manager.get('actual_mode') === "edit") {
- this.$el.addClass('active');
- $(visible_active_sibling[0]).removeClass('active');
- }
- }
- },
-});
-
-
-/**
- Base class for all fields, custom widgets and buttons to be displayed in the form view.
-
- Properties:
- - effective_readonly: when it is true, the widget is displayed as readonly. Vary depending
- the values of the "readonly" property and the "mode" property on the field manager.
-*/
-instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.InvisibilityChangerMixin, {
- /**
- * @constructs instance.web.form.FormWidget
- * @extends instance.web.Widget
- *
- * @param field_manager
- * @param node
- */
- init: function(field_manager, node) {
- this._super(field_manager);
- this.field_manager = field_manager;
- if (this.field_manager instanceof instance.web.FormView)
- this.view = this.field_manager;
- this.node = node;
- this.modifiers = JSON.parse(this.node.attrs.modifiers || '{}');
- instance.web.form.InvisibilityChangerMixin.init.call(this, this.field_manager, this.modifiers.invisible);
-
- this.field_manager.on("view_content_has_changed", this, this.process_modifiers);
-
- this.set({
- required: false,
- readonly: false,
- });
- // some events to make the property "effective_readonly" sync automatically with "readonly" and
- // "mode" on field_manager
- var self = this;
- var test_effective_readonly = function() {
- self.set({"effective_readonly": self.get("readonly") || self.field_manager.get("actual_mode") === "view"});
- };
- this.on("change:readonly", this, test_effective_readonly);
- this.field_manager.on("change:actual_mode", this, test_effective_readonly);
- test_effective_readonly.call(this);
- },
- renderElement: function() {
- this.process_modifiers();
- this._super();
- this.$el.addClass(this.node.attrs["class"] || "");
- },
- destroy: function() {
- $.fn.tooltip('destroy');
- this._super.apply(this, arguments);
- },
- /**
- * Sets up blur/focus forwarding from DOM elements to a widget (`this`).
- *
- * This method is an utility method that is meant to be called by child classes.
- *
- * @param {jQuery} $e jQuery object of elements to bind focus/blur on
- */
- setupFocus: function ($e) {
- var self = this;
- $e.on({
- focus: function () { self.trigger('focused'); },
- blur: function () { self.trigger('blurred'); }
- });
- },
- process_modifiers: function() {
- var to_set = {};
- for (var a in this.modifiers) {
- if (!this.modifiers.hasOwnProperty(a)) { continue; }
- if (!_.include(["invisible"], a)) {
- var val = this.field_manager.compute_domain(this.modifiers[a]);
- to_set[a] = val;
- }
- }
- this.set(to_set);
- },
- do_attach_tooltip: function(widget, trigger, options) {
- widget = widget || this;
- trigger = trigger || this.$el;
- options = _.extend({
- delay: { show: 500, hide: 0 },
- title: function() {
- var template = widget.template + '.tooltip';
- if (!QWeb.has_template(template)) {
- template = 'WidgetLabel.tooltip';
- }
- return QWeb.render(template, {
- debug: instance.session.debug,
- widget: widget
- });
- },
- }, options || {});
- //only show tooltip if we are in debug or if we have a help to show, otherwise it will display
- //as empty
- if (instance.session.debug || widget.node.attrs.help || (widget.field && widget.field.help)){
- $(trigger).tooltip(options);
- }
- },
- /**
- * Builds a new context usable for operations related to fields by merging
- * the fields'context with the action's context.
- */
- build_context: function() {
- // only use the model's context if there is not context on the node
- var v_context = this.node.attrs.context;
- if (! v_context) {
- v_context = (this.field || {}).context || {};
- }
-
- if (v_context.__ref || true) { //TODO: remove true
- var fields_values = this.field_manager.build_eval_context();
- v_context = new instance.web.CompoundContext(v_context).set_eval_context(fields_values);
- }
- return v_context;
- },
- build_domain: function() {
- var f_domain = this.field.domain || [];
- var n_domain = this.node.attrs.domain || null;
- // if there is a domain on the node, overrides the model's domain
- var final_domain = n_domain !== null ? n_domain : f_domain;
- if (!(final_domain instanceof Array) || true) { //TODO: remove true
- var fields_values = this.field_manager.build_eval_context();
- final_domain = new instance.web.CompoundDomain(final_domain).set_eval_context(fields_values);
- }
- return final_domain;
- }
-});
-
-instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({
- template: 'WidgetButton',
- init: function(field_manager, node) {
- node.attrs.type = node.attrs['data-button-type'];
- this.is_stat_button = /\boe_stat_button\b/.test(node.attrs['class']);
- this.icon_class = node.attrs.icon && "stat_button_icon fa " + node.attrs.icon + " fa-fw";
- this._super(field_manager, node);
- this.force_disabled = false;
- this.string = (this.node.attrs.string || '').replace(/_/g, '');
- if (JSON.parse(this.node.attrs.default_focus || "0")) {
- // TODO fme: provide enter key binding to widgets
- this.view.default_focus_button = this;
- }
- if (this.node.attrs.icon && (! /\//.test(this.node.attrs.icon))) {
- this.node.attrs.icon = '/web/static/src/img/icons/' + this.node.attrs.icon + '.png';
- }
- },
- start: function() {
- this._super.apply(this, arguments);
- this.view.on('view_content_has_changed', this, this.check_disable);
- this.check_disable();
- this.$el.click(this.on_click);
- if (this.node.attrs.help || instance.session.debug) {
- this.do_attach_tooltip();
- }
- this.setupFocus(this.$el);
- },
- on_click: function() {
- var self = this;
- this.force_disabled = true;
- this.check_disable();
- this.execute_action().always(function() {
- self.force_disabled = false;
- self.check_disable();
- });
- },
- execute_action: function() {
- var self = this;
- var exec_action = function() {
- if (self.node.attrs.confirm) {
- var def = $.Deferred();
- var dialog = new instance.web.Dialog(this, {
- title: _t('Confirm'),
- buttons: [
- {text: _t("Cancel"), click: function() {
- this.parents('.modal').modal('hide');
- }
- },
- {text: _t("Ok"), click: function() {
- var self2 = this;
- self.on_confirmed().always(function() {
- self2.parents('.modal').modal('hide');
- });
- }
- }
- ],
- }, $('<div/>').text(self.node.attrs.confirm)).open();
- dialog.on("closing", null, function() {def.resolve();});
- return def.promise();
- } else {
- return self.on_confirmed();
- }
- };
- if (!this.node.attrs.special) {
- return this.view.recursive_save().then(exec_action);
- } else {
- return exec_action();
- }
- },
- on_confirmed: function() {
- var self = this;
-
- var context = this.build_context();
- return this.view.do_execute_action(
- _.extend({}, this.node.attrs, {context: context}),
- this.view.dataset, this.view.datarecord.id, function (reason) {
- if (!_.isObject(reason)) {
- self.view.recursive_reload();
- }
- });
- },
- check_disable: function() {
- var disabled = (this.force_disabled || !this.view.is_interactible_record());
- this.$el.prop('disabled', disabled);
- this.$el.css('color', disabled ? 'grey' : '');
- }
-});
-
-/**
- * Interface to be implemented by fields.
- *
- * Events:
- * - changed_value: triggered when the value of the field has changed. This can be due
- * to a user interaction or a call to set_value().
- *
- */
-instance.web.form.FieldInterface = {
- /**
- * Constructor takes 2 arguments:
- * - field_manager: Implements FieldManagerMixin
- * - node: the "<field>" node in json form
- */
- init: function(field_manager, node) {},
- /**
- * Called by the form view to indicate the value of the field.
- *
- * Multiple calls to set_value() can occur at any time and must be handled correctly by the implementation,
- * regardless of any asynchronous operation currently running. Calls to set_value() can and will also occur
- * before the widget is inserted into the DOM.
- *
- * set_value() must be able, at any moment, to handle the syntax returned by the "read" method of the
- * osv class in the OpenERP server as well as the syntax used by the set_value() (see below). It must
- * also be able to handle any other format commonly used in the _defaults key on the models in the addons
- * 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_) {},
- /**
- * Get the current value of the widget.
- *
- * Must always return a syntactically correct value to be passed to the "write" method of the osv class in
- * the OpenERP server, although it is not assumed to respect the constraints applied to the field.
- * For example if the field is marked as "required", a call to get_value() can return false.
- *
- * get_value() can also be called *before* a call to set_value() and, in that case, is supposed to
- * return a default value according to the type of field.
- *
- * This method is always assumed to perform synchronously, it can not return a promise.
- *
- * If there was no user interaction to modify the value of the field, it is always assumed that
- * get_value() return the same semantic value than the one passed in the last call to set_value(),
- * although the syntax can be different. This can be the case for type of fields that have a different
- * 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 syntactically correct, ignoring
- * the potential semantic restrictions applied to the field.
- */
- is_syntax_valid: function() {},
- /**
- * Must set the focus on the field. Return false if field is not focusable.
- */
- focus: function() {},
- /**
- * Called when the translate button is clicked.
- */
- on_translate: function() {},
- /**
- This method is called by the form view before reading on_change values and before saving. It tells
- the field to save its value before reading it using get_value(). Must return a promise.
- */
- commit_value: function() {},
-};
-
-/**
- * Abstract class for classes implementing FieldInterface.
- *
- * Properties:
- * - 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.
- *
- */
-instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.web.form.FieldInterface, {
- /**
- * @constructs instance.web.form.AbstractField
- * @extends instance.web.form.FormWidget
- *
- * @param field_manager
- * @param node
- */
- init: function(field_manager, node) {
- var self = this;
- this._super(field_manager, node);
- this.name = this.node.attrs.name;
- this.field = this.field_manager.get_field_desc(this.name);
- this.widget = this.node.attrs.widget;
- this.string = this.node.attrs.string || this.field.string || this.name;
- this.options = instance.web.py_eval(this.node.attrs.options || '{}');
- this.set({'value': false});
-
- this.on("change:value", this, function() {
- this.trigger('changed_value');
- this._check_css_flags();
- });
- },
- renderElement: function() {
- var self = this;
- this._super();
- if (this.field.translate && this.view) {
- this.$el.addClass('oe_form_field_translatable');
- this.$el.find('.oe_field_translate').click(this.on_translate);
- }
- this.$label = this.view ? this.view.$el.find('label[for=' + this.id_for_label + ']') : $();
- this.do_attach_tooltip(this, this.$label[0] || this.$el);
- if (instance.session.debug) {
- this.$label.off('dblclick').on('dblclick', function() {
- console.log("Field '%s' of type '%s' in View: %o", self.name, (self.node.attrs.widget || self.field.type), self.view);
- window.w = self;
- console.log("window.w =", window.w);
- });
- }
- if (!this.disable_utility_classes) {
- this.off("change:required", this, this._set_required);
- this.on("change:required", this, this._set_required);
- this._set_required();
- }
- this._check_visibility();
- this.field_manager.off("change:display_invalid_fields", this, this._check_css_flags);
- this.field_manager.on("change:display_invalid_fields", this, this._check_css_flags);
- this._check_css_flags();
- },
- start: function() {
- var tmp = this._super();
- this.on("change:value", this, function() {
- if (! this.no_rerender)
- this.render_value();
- });
- this.render_value();
- },
- /**
- * Private. Do not use.
- */
- _set_required: function() {
- this.$el.toggleClass('oe_form_required', this.get("required"));
- },
- set_value: function(value_) {
- this.set({'value': value_});
- },
- get_value: function() {
- return this.get('value');
- },
- /**
- Utility method that all implementations should use to change the
- value without triggering a re-rendering.
- */
- internal_set_value: function(value_) {
- var tmp = this.no_rerender;
- this.no_rerender = true;
- this.set({'value': value_});
- this.no_rerender = tmp;
- },
- /**
- This method is called each time the value is modified.
- */
- render_value: function() {},
- is_valid: function() {
- return this.is_syntax_valid() && !(this.get('required') && this.is_false());
- },
- is_syntax_valid: function() {
- return true;
- },
- /**
- * 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;
- },
- _check_css_flags: function() {
- if (this.field.translate) {
- this.$el.find('.oe_field_translate').toggle(this.field_manager.get('actual_mode') !== "create");
- }
- if (!this.disable_utility_classes) {
- if (this.field_manager.get('display_invalid_fields')) {
- this.$el.toggleClass('oe_form_invalid', !this.is_valid());
- }
- }
- },
- focus: function() {
- return false;
- },
- set_input_id: function(id) {
- this.id_for_label = id;
- },
- on_translate: function() {
- var self = this;
- var trans = new instance.web.DataSet(this, 'ir.translation');
- return trans.call_button('translate_fields', [this.view.dataset.model, this.view.datarecord.id, this.name, this.view.dataset.get_context()]).done(function(r) {
- self.do_action(r);
- });
- },
-
- set_dimensions: function (height, width) {
- this.$el.css({
- width: width,
- minHeight: height
- });
- },
- commit_value: function() {
- return $.when();
- },
-});
-
-/**
- * A mixin to apply on any FormWidget that has to completely re-render when its readonly state
- * switch.
- */
-instance.web.form.ReinitializeWidgetMixin = {
- /**
- * Default implementation of, you should not override it, use initialize_field() instead.
- */
- start: function() {
- this.initialize_field();
- this._super();
- },
- initialize_field: function() {
- this.on("change:effective_readonly", this, this.reinitialize);
- this.initialize_content();
- },
- reinitialize: function() {
- this.destroy_content();
- this.renderElement();
- this.initialize_content();
- },
- /**
- * 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() {},
-};
-
-/**
- * A mixin to apply on any field that has to completely re-render when its readonly state
- * switch.
- */
-instance.web.form.ReinitializeFieldMixin = _.extend({}, instance.web.form.ReinitializeWidgetMixin, {
- reinitialize: function() {
- instance.web.form.ReinitializeWidgetMixin.reinitialize.call(this);
- var res = this.render_value();
- if (this.view && this.view.render_value_defs){
- this.view.render_value_defs.push(res);
- }
- },
-});
-
-/**
- Some hack to make placeholders work in ie9.
-*/
-if (!('placeholder' in document.createElement('input'))) {
- document.addEventListener("DOMNodeInserted",function(event){
- var nodename = event.target.nodeName.toLowerCase();
- if ( nodename === "input" || nodename == "textarea" ) {
- $(event.target).placeholder();
- }
- });
-}
-
-instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
- template: 'FieldChar',
- widget_class: 'oe_form_field_char',
- events: {
- 'change input': 'store_dom_value',
- },
- init: function (field_manager, node) {
- this._super(field_manager, node);
- this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1';
- },
- initialize_content: function() {
- this.setupFocus(this.$('input'));
- },
- store_dom_value: function () {
- if (!this.get('effective_readonly')
- && this.$('input').length
- && this.is_syntax_valid()) {
- this.internal_set_value(
- this.parse_value(
- this.$('input').val()));
- }
- },
- commit_value: function () {
- this.store_dom_value();
- return this._super();
- },
- render_value: function() {
- var show_value = this.format_value(this.get('value'), '');
- if (!this.get("effective_readonly")) {
- this.$el.find('input').val(show_value);
- } else {
- if (this.password) {
- show_value = new Array(show_value.length + 1).join('*');
- }
- this.$(".oe_form_char_content").text(show_value);
- }
- },
- is_syntax_valid: function() {
- if (!this.get("effective_readonly") && this.$("input").size() > 0) {
- try {
- this.parse_value(this.$('input').val(), '');
- return true;
- } catch(e) {
- return false;
- }
- }
- return true;
- },
- parse_value: function(val, def) {
- return instance.web.parse_value(val, this, def);
- },
- format_value: function(val, def) {
- return instance.web.format_value(val, this, def);
- },
- is_false: function() {
- return this.get('value') === '' || this._super();
- },
- focus: function() {
- var input = this.$('input:first')[0];
- return input ? input.focus() : false;
- },
- set_dimensions: function (height, width) {
- this._super(height, width);
- this.$('input').css({
- height: height,
- width: width
- });
- }
-});
-
-instance.web.form.KanbanSelection = instance.web.form.FieldChar.extend({
- init: function (field_manager, node) {
- this._super(field_manager, node);
- },
- start: function () {
- var self = this;
- this.states = [];
- this._super.apply(this, arguments);
- // hook on form view content changed: recompute the states, because it may be related to the current stage
- this.getParent().on('view_content_has_changed', self, function () {
- self.render_value();
- });
- },
- prepare_dropdown_selection: function() {
- var self = this;
- var data = [];
- var selection = this.field.selection || [];
- var stage_id = _.isArray(this.view.datarecord.stage_id) ? this.view.datarecord.stage_id[0] : this.view.datarecord.stage_id;
- var legend_field = this.options && this.options.states_legend_field || false;
- var fields_to_read = _.map(
- this.options && this.options.states_legend || {},
- function (value, key, list) { return value; });
- if (legend_field && fields_to_read && stage_id) {
- var fetch_stage = new openerp.web.DataSet(
- this,
- self.view.fields[legend_field].field.relation).read_ids([stage_id],
- fields_to_read);
- }
- else { var fetch_stage = $.Deferred().resolve(false); }
- return $.when(fetch_stage).then(function (stage_data) {
- _.map(selection, function(res) {
- var value = {
- 'name': res[0],
- 'tooltip': res[1],
- 'state_name': res[1],
- }
- if (stage_data && stage_data[0][self.options.states_legend[res[0]]]) {
- value['state_name'] = stage_data[0][self.options.states_legend[res[0]]];
- value['tooltip'] = stage_data[0][self.options.states_legend[res[0]]];
- }
- if (res[0] == 'normal') { value['state_class'] = 'oe_kanban_status'; }
- else if (res[0] == 'done') { value['state_class'] = 'oe_kanban_status oe_kanban_status_green'; }
- else { value['state_class'] = 'oe_kanban_status oe_kanban_status_red'; }
- data.push(value);
- });
- return data;
- });
- },
- render_value: function() {
- var self = this;
- var dd_fetched = this.prepare_dropdown_selection();;
- return $.when(dd_fetched).then(function (states) {
- self.states = states;
- self.$el.html(QWeb.render("KanbanSelection", {'widget': self}));
- self.$el.find('li').on('click', self.set_kanban_selection.bind(self));
- })
- },
- /* setting the value: in view mode, perform an asynchronous call and reload
- the form view; in edit mode, use set_value to save the new value that will
- be written when saving the record. */
- set_kanban_selection: function (ev) {
- var self = this;
- var li = $(ev.target).closest('li');
- if (li.length) {
- var value = String(li.data('value'));
- if (this.view.get('actual_mode') == 'view') {
- var write_values = {}
- write_values[self.name] = value;
- return this.view.dataset._model.call(
- 'write', [
- [this.view.datarecord.id],
- write_values,
- self.view.dataset.get_context()
- ]).done(self.reload_record.bind(self));
- }
- else {
- return this.set_value(value);
- }
- }
- },
- reload_record: function() {
- this.view.reload();
- },
-});
-
-instance.web.form.Priority = instance.web.form.FieldChar.extend({
- init: function (field_manager, node) {
- this._super(field_manager, node);
- },
- prepare_priority: function() {
- var self = this;
- var selection = this.field.selection || [];
- var init_value = selection && selection[0][0] || 0;
- var data = _.map(selection.slice(1), function(element, index) {
- var value = {
- 'value': element[0],
- 'name': element[1],
- 'click_value': element[0],
- }
- if (index == 0 && self.get('value') == element[0]) {
- value['click_value'] = init_value;
- }
- return value;
- });
- return data;
- },
- render_value: function() {
- var self = this;
- this.priorities = this.prepare_priority();
- this.$el.html(QWeb.render("Priority", {'widget': this}));
- if (!this.get('readonly')){
- this.$el.find('li').on('click', this.set_priority.bind(this));
- }
- },
- /* setting the value: in view mode, perform an asynchronous call and reload
- the form view; in edit mode, use set_value to save the new value that will
- be written when saving the record. */
- set_priority: function (ev) {
- var self = this;
- var li = $(ev.target).closest('li');
- if (li.length) {
- var value = String(li.data('value'));
- if (this.view.get('actual_mode') == 'view') {
- var write_values = {}
- write_values[self.name] = value;
- return this.view.dataset._model.call(
- 'write', [
- [this.view.datarecord.id],
- write_values,
- self.view.dataset.get_context()
- ]).done(self.reload_record.bind(self));
- }
- else {
- return this.set_value(value);
- }
- }
-
- },
- reload_record: function() {
- this.view.reload();
- },
-});
-
-instance.web.form.FieldID = instance.web.form.FieldChar.extend({
- process_modifiers: function () {
- this._super();
- this.set({ readonly: true });
- },
-});
-
-instance.web.form.FieldEmail = instance.web.form.FieldChar.extend({
- template: 'FieldEmail',
- initialize_content: function() {
- this._super();
- var $button = this.$el.find('button');
- $button.click(this.on_button_clicked);
- this.setupFocus($button);
- },
- render_value: function() {
- if (!this.get("effective_readonly")) {
- this._super();
- } else {
- this.$el.find('a')
- .attr('href', 'mailto:' + this.get('value'))
- .text(this.get('value') || '');
- }
- },
- on_button_clicked: function() {
- if (!this.get('value') || !this.is_syntax_valid()) {
- this.do_warn(_t("E-mail Error"), _t("Can't send email to invalid e-mail address"));
- } else {
- location.href = 'mailto:' + this.get('value');
- }
- }
-});
-
-instance.web.form.FieldUrl = instance.web.form.FieldChar.extend({
- template: 'FieldUrl',
- initialize_content: function() {
- this._super();
- var $button = this.$el.find('button');
- $button.click(this.on_button_clicked);
- this.setupFocus($button);
- },
- render_value: function() {
- if (!this.get("effective_readonly")) {
- this._super();
- } else {
- var tmp = this.get('value');
- var s = /(\w+):(.+)|^\.{0,2}\//.exec(tmp);
- if (!s) {
- tmp = "http://" + this.get('value');
- }
- var text = this.get('value') ? this.node.attrs.text || tmp : '';
- this.$el.find('a').attr('href', tmp).text(text);
- }
- },
- on_button_clicked: function() {
- if (!this.get('value')) {
- this.do_warn(_t("Resource Error"), _t("This resource is empty"));
- } else {
- var url = $.trim(this.get('value'));
- if(/^www\./i.test(url))
- url = 'http://'+url;
- window.open(url);
- }
- }
-});
-
-instance.web.form.FieldFloat = instance.web.form.FieldChar.extend({
- is_field_number: true,
- widget_class: 'oe_form_field_float',
- init: function (field_manager, node) {
- this._super(field_manager, node);
- this.internal_set_value(0);
- if (this.node.attrs.digits) {
- this.digits = this.node.attrs.digits;
- } else {
- this.digits = this.field.digits;
- }
- },
- set_value: function(value_) {
- if (value_ === false || value_ === undefined) {
- // As in GTK client, floats default to 0
- value_ = 0;
- }
- if (this.digits !== undefined && this.digits.length === 2) {
- value_ = instance.web.round_decimals(value_, this.digits[1]);
- }
- this._super.apply(this, [value_]);
- },
- focus: function () {
- var $input = this.$('input:first');
- return $input.length ? $input.select() : false;
- }
-});
-
-
-instance.web.form.FieldCharDomain = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
- render_value: function() {
- var self = this;
- this.$el.html(instance.web.qweb.render("FieldCharDomain", {widget: this}));
- if (this.get('value')) {
- var model = this.options.model || this.field_manager.get_field_value(this.options.model_field);
- var domain = instance.web.pyeval.eval('domain', this.get('value'));
- var ds = new instance.web.DataSetStatic(self, model, self.build_context());
- ds.call('search_count', [domain]).then(function (results) {
- $('.oe_domain_count', self.$el).text(results + ' records selected');
- if (self.get('effective_readonly')) {
- $('button span', self.$el).text(' See selection');
- }
- else {
- $('button span', self.$el).text(' Change selection');
- }
- });
- } else {
- $('.oe_domain_count', this.$el).text('0 record selected');
- $('button span', this.$el).text(' Select records');
- };
- this.$('.select_records').on('click', self.on_click);
- },
- on_click: function(event) {
- event.preventDefault();
- var self = this;
- var model = this.options.model || this.field_manager.get_field_value(this.options.model_field);
-
- var options = {
- title: this.get('effective_readonly') ? 'Selected records' : 'Select records...',
- readonly: this.get('effective_readonly'),
- disable_multiple_selection: this.get('effective_readonly'),
- no_create: this.get('effective_readonly'),
- };
- var domain = this.get('value');
- popup = new instance.web.form.DomainEditorPopup(this);
- popup.select_element(model, options, domain, {});
- popup.on("elements_selected", self, function(selected_ids) {
- if (!self.get('effective_readonly')) {
- self.set_value(popup.get_domain(selected_ids));
- }
- });
- },
-});
-
-
-instance.web.DateTimeWidget = instance.web.Widget.extend({
- template: "web.datepicker",
- type_of_date: "datetime",
- events: {
- 'dp.change .oe_datepicker_main': 'change_datetime',
- 'dp.show .oe_datepicker_main': 'set_datetime_default',
- 'keypress .oe_datepicker_master': 'change_datetime',
- },
- init: function(parent) {
- this._super(parent);
- this.name = parent.name;
- },
- start: function() {
- var self = this;
- var l10n = _t.database.parameters;
- var options = {
- pickTime: true,
- useSeconds: true,
- startDate: moment({ y: 1900 }),
- endDate: moment().add(200, "y"),
- calendarWeeks: true,
- icons : {
- time: 'fa fa-clock-o',
- date: 'fa fa-calendar',
- up: 'fa fa-chevron-up',
- down: 'fa fa-chevron-down'
- },
- language : moment.locale(),
- format : instance.web.normalize_format(l10n.date_format +' '+ l10n.time_format),
- };
- this.$input = this.$el.find('input.oe_datepicker_master');
- if (this.type_of_date === 'date') {
- options['pickTime'] = false;
- options['useSeconds'] = false;
- options['format'] = instance.web.normalize_format(l10n.date_format);
- }
- this.picker = this.$('.oe_datepicker_main').datetimepicker(options);
- this.set_readonly(false);
- this.set({'value': false});
- },
- set_value: function(value_) {
- this.set({'value': value_});
- this.$input.val(value_ ? this.format_client(value_) : '');
- },
- get_value: function() {
- return this.get('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);
- },
- is_valid_: function() {
- var value_ = this.$input.val();
- if (value_ === "") {
- return true;
- } else {
- try {
- this.parse_client(value_);
- return true;
- } catch(e) {
- return false;
- }
- }
- },
- parse_client: function(v) {
- return instance.web.parse_value(v, {"widget": this.type_of_date});
- },
- format_client: function(v) {
- return instance.web.format_value(v, {"widget": this.type_of_date});
- },
- set_datetime_default: function(){
- //when opening datetimepicker the date and time by default should be the one from
- //the input field if any or the current day otherwise
- if (this.type_of_date === 'datetime') {
- value = moment().second(0);
- if (this.$input.val().length !== 0 && this.is_valid_()){
- var value = this.$input.val();
- }
- this.$('.oe_datepicker_main').data('DateTimePicker').setValue(value);
- }
- },
- change_datetime: function(e) {
- if ((e.type !== "keypress" || e.which === 13) && this.is_valid_()) {
- this.set_value_from_ui_();
- this.trigger("datetime_changed");
- }
- },
- commit_value: function () {
- this.change_datetime();
- },
-});
-
-instance.web.DateWidget = instance.web.DateTimeWidget.extend({
- type_of_date: "date"
-});
-
-instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
- template: "FieldDatetime",
- build_widget: function() {
- return new instance.web.DateTimeWidget(this);
- },
- 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('datetime_changed', this, _.bind(function() {
- this.internal_set_value(this.datewidget.get_value());
- }, this));
- this.datewidget.appendTo(this.$el);
- this.setupFocus(this.datewidget.$input);
- }
- },
- render_value: function() {
- if (!this.get("effective_readonly")) {
- this.datewidget.set_value(this.get('value'));
- } else {
- this.$el.text(instance.web.format_value(this.get('value'), this, ''));
- }
- },
- is_syntax_valid: function() {
- if (!this.get("effective_readonly") && this.datewidget) {
- return this.datewidget.is_valid_();
- }
- return true;
- },
- is_false: function() {
- return this.get('value') === '' || this._super();
- },
- focus: function() {
- var input = this.datewidget && this.datewidget.$input[0];
- return input ? input.focus() : false;
- },
- set_dimensions: function (height, width) {
- this._super(height, width);
- if (!this.get("effective_readonly")) {
- this.datewidget.$input.css('height', height);
- }
- }
-});
-
-instance.web.form.FieldDate = instance.web.form.FieldDatetime.extend({
- template: "FieldDate",
- build_widget: function() {
- return new instance.web.DateWidget(this);
- }
-});
-
-instance.web.form.FieldText = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
- template: 'FieldText',
- events: {
- 'keyup': function (e) {
- if (e.which === $.ui.keyCode.ENTER) {
- e.stopPropagation();
- }
- },
- 'keypress': function (e) {
- if (e.which === $.ui.keyCode.ENTER) {
- e.stopPropagation();
- }
- },
- 'change textarea': 'store_dom_value',
- },
- initialize_content: function() {
- var self = this;
- if (! this.get("effective_readonly")) {
- this.$textarea = this.$el.find('textarea');
- this.auto_sized = false;
- this.default_height = this.$textarea.css('height');
- if (this.default_height === '0px') this.default_height = '90px';
- if (this.get("effective_readonly")) {
- this.$textarea.attr('disabled', 'disabled');
- }
- this.setupFocus(this.$textarea);
- } else {
- this.$textarea = undefined;
- }
- },
- commit_value: function () {
- if (! this.get("effective_readonly") && this.$textarea) {
- this.store_dom_value();
- }
- return this._super();
- },
- store_dom_value: function () {
- this.internal_set_value(instance.web.parse_value(this.$textarea.val(), this));
- },
- render_value: function() {
- if (! this.get("effective_readonly")) {
- var show_value = instance.web.format_value(this.get('value'), this, '');
- if (show_value === '') {
- this.$textarea.css('height', parseInt(this.default_height, 10)+"px");
- }
- this.$textarea.val(show_value);
- if (! this.auto_sized) {
- this.auto_sized = true;
- this.$textarea.autosize();
- } else {
- this.$textarea.trigger("autosize");
- }
- } else {
- var txt = this.get("value") || '';
- this.$(".oe_form_text_content").text(txt);
- }
- },
- is_syntax_valid: function() {
- if (!this.get("effective_readonly") && this.$textarea) {
- try {
- instance.web.parse_value(this.$textarea.val(), this, '');
- return true;
- } catch(e) {
- return false;
- }
- }
- return true;
- },
- is_false: function() {
- return this.get('value') === '' || this._super();
- },
- focus: function($el) {
- var input = !this.get("effective_readonly") && this.$textarea && this.$textarea[0];
- return input ? input.focus() : false;
- },
- set_dimensions: function (height, width) {
- this._super(height, width);
- if (!this.get("effective_readonly") && this.$textarea) {
- this.$textarea.css({
- width: width,
- minHeight: height
- });
- }
- },
-});
-
-/**
- * FieldTextHtml Widget
- * Intended for FieldText widgets meant to display HTML content. This
- * widget will instantiate the CLEditor (see cleditor in static/src/lib)
- * To find more information about CLEditor configutation: go to
- * http://premiumsoftware.net/cleditor/docs/GettingStarted.html
- */
-instance.web.form.FieldTextHtml = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
- template: 'FieldTextHtml',
- init: function() {
- this._super.apply(this, arguments);
- },
- initialize_content: function() {
- var self = this;
- if (! this.get("effective_readonly")) {
- self._updating_editor = false;
- this.$textarea = this.$el.find('textarea');
- var width = ((this.node.attrs || {}).editor_width || 'calc(100% - 4px)');
- var height = ((this.node.attrs || {}).editor_height || 250);
- this.$textarea.cleditor({
- width: width, // width not including margins, borders or padding
- height: height, // height not including margins, borders or padding
- controls: // controls to add to the toolbar
- "bold italic underline strikethrough " +
- "| removeformat | bullets numbering | outdent " +
- "indent | link unlink | source",
- bodyStyle: // style to assign to document body contained within the editor
- "margin:4px; color:#4c4c4c; font-size:13px; font-family:'Lucida Grande',Helvetica,Verdana,Arial,sans-serif; cursor:text"
- });
- this.$cleditor = this.$textarea.cleditor()[0];
- this.$cleditor.change(function() {
- if (! self._updating_editor) {
- self.$cleditor.updateTextArea();
- self.internal_set_value(self.$textarea.val());
- }
- });
- if (this.field.translate) {
- var $img = $('<img class="oe_field_translate oe_input_icon" src="/web/static/src/img/icons/terp-translate.png" width="16" height="16" border="0"/>')
- .click(this.on_translate);
- this.$cleditor.$toolbar.append($img);
- }
- }
- },
- render_value: function() {
- if (! this.get("effective_readonly")) {
- this.$textarea.val(this.get('value') || '');
- this._updating_editor = true;
- this.$cleditor.updateFrame();
- this._updating_editor = false;
- } else {
- this.$el.html(this.get('value'));
- }
- },
-});
-
-instance.web.form.FieldBoolean = instance.web.form.AbstractField.extend({
- template: 'FieldBoolean',
- start: function() {
- var self = this;
- this.$checkbox = $("input", this.$el);
- this.setupFocus(this.$checkbox);
- this.$el.click(_.bind(function() {
- this.internal_set_value(this.$checkbox.is(':checked'));
- }, this));
- var check_readonly = function() {
- self.$checkbox.prop('disabled', self.get("effective_readonly"));
- self.click_disabled_boolean();
- };
- this.on("change:effective_readonly", this, check_readonly);
- check_readonly.call(this);
- this._super.apply(this, arguments);
- },
- render_value: function() {
- this.$checkbox[0].checked = this.get('value');
- },
- focus: function() {
- var input = this.$checkbox && this.$checkbox[0];
- return input ? input.focus() : false;
- },
- click_disabled_boolean: function(){
- var $disabled = this.$el.find('input[type=checkbox]:disabled');
- $disabled.each(function (){
- $(this).next('div').remove();
- $(this).closest("span").append($('<div class="boolean"></div>'));
- });
- }
-});
-
-/**
- The progressbar field expect a float from 0 to 100.
-*/
-instance.web.form.FieldProgressBar = instance.web.form.AbstractField.extend({
- template: 'FieldProgressBar',
- render_value: function() {
- this.$el.progressbar({
- value: this.get('value') || 0,
- disabled: this.get("effective_readonly")
- });
- var formatted_value = instance.web.format_value(this.get('value') || 0, { type : 'float' });
- this.$('span').html(formatted_value + '%');
- }
-});
-
-/**
- The PercentPie field expect a float from 0 to 100.
-*/
-instance.web.form.FieldPercentPie = instance.web.form.AbstractField.extend({
- template: 'FieldPercentPie',
-
- render_value: function() {
- var value = this.get('value'),
- formatted_value = Math.round(value || 0) + '%',
- svg = this.$('svg')[0];
-
- svg.innerHTML = "";
- nv.addGraph(function() {
- var width = 42, height = 42;
- var chart = nv.models.pieChart()
- .width(width)
- .height(height)
- .margin({top: 0, right: 0, bottom: 0, left: 0})
- .donut(true)
- .showLegend(false)
- .showLabels(false)
- .tooltips(false)
- .color(['#7C7BAD','#DDD'])
- .donutRatio(0.62);
-
- d3.select(svg)
- .datum([{'x': 'value', 'y': value}, {'x': 'complement', 'y': 100 - value}])
- .transition()
- .call(chart)
- .attr('style', 'width: ' + width + 'px; height:' + height + 'px;');
-
- d3.select(svg)
- .append("text")
- .attr({x: width/2, y: height/2 + 3, 'text-anchor': 'middle'})
- .style({"font-size": "10px", "font-weight": "bold"})
- .text(formatted_value);
-
- return chart;
- });
-
- }
-});
-
-/**
- The FieldBarChart expectsa list of values (indeed)
-*/
-instance.web.form.FieldBarChart = instance.web.form.AbstractField.extend({
- template: 'FieldBarChart',
-
- render_value: function() {
- var value = JSON.parse(this.get('value'));
- var svg = this.$('svg')[0];
- svg.innerHTML = "";
- nv.addGraph(function() {
- var width = 34, height = 34;
- var chart = nv.models.discreteBarChart()
- .x(function (d) { return d.tooltip })
- .y(function (d) { return d.value })
- .width(width)
- .height(height)
- .margin({top: 0, right: 0, bottom: 0, left: 0})
- .tooltips(false)
- .showValues(false)
- .transitionDuration(350)
- .showXAxis(false)
- .showYAxis(false);
-
- d3.select(svg)
- .datum([{key: 'values', values: value}])
- .transition()
- .call(chart)
- .attr('style', 'width: ' + (width + 4) + 'px; height: ' + (height + 8) + 'px;');
-
- nv.utils.windowResize(chart.update);
-
- return chart;
- });
-
- }
-});
-
-
-instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
- template: 'FieldSelection',
- events: {
- 'change select': 'store_dom_value',
- },
- init: function(field_manager, node) {
- var self = this;
- this._super(field_manager, node);
- this.set("value", false);
- this.set("values", []);
- this.records_orderer = new instance.web.DropMisordered();
- this.field_manager.on("view_content_has_changed", this, function() {
- var domain = new openerp.web.CompoundDomain(this.build_domain()).eval();
- if (! _.isEqual(domain, this.get("domain"))) {
- this.set("domain", domain);
- }
- });
- },
- initialize_field: function() {
- instance.web.form.ReinitializeFieldMixin.initialize_field.call(this);
- this.on("change:domain", this, this.query_values);
- this.set("domain", new openerp.web.CompoundDomain(this.build_domain()).eval());
- this.on("change:values", this, this.render_value);
- },
- query_values: function() {
- var self = this;
- var def;
- if (this.field.type === "many2one") {
- var model = new openerp.Model(openerp.session, this.field.relation);
- def = model.call("name_search", ['', this.get("domain")], {"context": this.build_context()});
- } else {
- var values = _.reject(this.field.selection, function (v) { return v[0] === false && v[1] === ''; });
- def = $.when(values);
- }
- this.records_orderer.add(def).then(function(values) {
- if (! _.isEqual(values, self.get("values"))) {
- self.set("values", values);
- }
- });
- },
- 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
- // selection, we should just let the value change and not let the
- // event broadcast further (e.g. to validating the current state of
- // the form in editable list view, which would lead to saving the
- // current row or switching to the next one)
- // * If the user presses [RETURN] with a select closed (side-effect:
- // also if the user opened the select and pressed [RETURN] without
- // changing the selected value), takes the action as validating the
- // row
- var ischanging = false;
- var $select = this.$el.find('select')
- .change(function () { ischanging = true; })
- .click(function () { ischanging = false; })
- .keyup(function (e) {
- if (e.which !== 13 || !ischanging) { return; }
- e.stopPropagation();
- ischanging = false;
- });
- this.setupFocus($select);
- },
- commit_value: function () {
- this.store_dom_value();
- return this._super();
- },
- store_dom_value: function () {
- if (!this.get('effective_readonly') && this.$('select').length) {
- var val = JSON.parse(this.$('select').val());
- this.internal_set_value(val);
- }
- },
- set_value: function(value_) {
- value_ = value_ === null ? false : value_;
- value_ = value_ instanceof Array ? value_[0] : value_;
- this._super(value_);
- },
- render_value: function() {
- var values = this.get("values");
- values = [[false, this.node.attrs.placeholder || '']].concat(values);
- var found = _.find(values, function(el) { return el[0] === this.get("value"); }, this);
- if (! found) {
- found = [this.get("value"), _t('Unknown')];
- values = [found].concat(values);
- }
- if (! this.get("effective_readonly")) {
- this.$().html(QWeb.render("FieldSelectionSelect", {widget: this, values: values}));
- this.$("select").val(JSON.stringify(found[0]));
- } else {
- this.$el.text(found[1]);
- }
- },
- focus: function() {
- var input = this.$('select:first')[0];
- return input ? input.focus() : false;
- },
- set_dimensions: function (height, width) {
- this._super(height, width);
- this.$('select').css({
- height: height,
- width: width
- });
- }
-});
-
-instance.web.form.FieldRadio = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
- template: 'FieldRadio',
- events: {
- 'click input': 'click_change_value'
- },
- init: function(field_manager, node) {
- /* Radio button widget: Attributes options:
- * - "horizontal" to display in column
- * - "no_radiolabel" don't display text values
- */
- this._super(field_manager, node);
- this.selection = _.clone(this.field.selection) || [];
- this.domain = false;
- this.uniqueId = _.uniqueId("radio");
- },
- initialize_content: function () {
- this.on("change:effective_readonly", this, this.render_value);
- this.field_manager.on("view_content_has_changed", this, this.get_selection);
- this.get_selection();
- },
- click_change_value: function (event) {
- var val = $(event.target).val();
- val = this.field.type == "selection" ? val : +val;
- if (val !== this.get_value()) {
- this.set_value(val);
- }
- },
- /** Get the selection and render it
- * selection: [[identifier, value_to_display], ...]
- * For selection fields: this is directly given by this.field.selection
- * For many2one fields: perform a search on the relation of the many2one field
- */
- get_selection: function() {
- var self = this;
- var selection = [];
- var def = $.Deferred();
- if (self.field.type == "many2one") {
- var domain = instance.web.pyeval.eval('domain', this.build_domain()) || [];
- if (! _.isEqual(self.domain, domain)) {
- self.domain = domain;
- var ds = new instance.web.DataSetStatic(self, self.field.relation, self.build_context());
- ds.call('search', [self.domain])
- .then(function (records) {
- ds.name_get(records).then(function (records) {
- selection = records;
- def.resolve();
- });
- });
- } else {
- selection = self.selection;
- def.resolve();
- }
- }
- else if (self.field.type == "selection") {
- selection = self.field.selection || [];
- def.resolve();
- }
- return def.then(function () {
- if (! _.isEqual(selection, self.selection)) {
- self.selection = _.clone(selection);
- self.renderElement();
- self.render_value();
- }
- });
- },
- set_value: function (value_) {
- if (this.field.type == "selection") {
- value_ = _.find(this.field.selection, function (sel) { return sel[0] == value_;});
- }
- else if (!this.selection.length) {
- this.selection = [value_];
- }
-
- this._super(value_);
- },
- get_value: function () {
- var value = this.get('value');
- return value instanceof Array ? value[0] : value;
- },
- render_value: function () {
- var self = this;
- this.$el.toggleClass("oe_readonly", this.get('effective_readonly'));
- this.$("input").filter(function () {return this.value == self.get_value();}).prop("checked", true);
- this.$(".oe_radio_readonly").text(this.get('value') ? this.get('value')[1] : "");
- }
-});
-
-// jquery autocomplete tweak to allow html and classnames
-(function() {
- var proto = $.ui.autocomplete.prototype,
- initSource = proto._initSource;
-
- 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() );
- });
- }
-
- $.extend( proto, {
- _initSource: function() {
- if ( this.options.html && $.isArray(this.options.source) ) {
- this.source = function( request, response ) {
- response( filter( this.options.source, request.term ) );
- };
- } else {
- initSource.call( this );
- }
- },
-
- _renderItem: function( ul, item) {
- return $( "<li></li>" )
- .data( "item.autocomplete", item )
- .append( $( "<a></a>" )[ this.options.html ? "html" : "text" ]( item.label ) )
- .appendTo( ul )
- .addClass(item.classname);
- }
- });
-})();
-
-/**
- A mixin containing some useful methods to handle completion inputs.
-
- The widget containing this option can have these arguments in its widget options:
- - no_quick_create: if true, it will disable the quick create
-*/
-instance.web.form.CompletionFieldMixin = {
- init: function() {
- this.limit = 7;
- this.orderer = new instance.web.DropMisordered();
- this.can_create = this.node.attrs.can_create || true;
- this.can_write = this.node.attrs.can_write || true;
- },
- /**
- * Call this method to search using a string.
- */
- get_search_result: function(search_val) {
- var self = this;
-
- var dataset = new instance.web.DataSet(this, this.field.relation, self.build_context());
- this.last_query = search_val;
- var exclusion_domain = [], ids_blacklist = this.get_search_blacklist();
- if (!_(ids_blacklist).isEmpty()) {
- exclusion_domain.push(['id', 'not in', ids_blacklist]);
- }
-
- return this.orderer.add(dataset.name_search(
- search_val, new instance.web.CompoundDomain(self.build_domain(), exclusion_domain),
- 'ilike', this.limit + 1, self.build_context())).then(function(data) {
- self.last_search = data;
- // possible selections for the m2o
- var values = _.map(data, function(x) {
- x[1] = x[1].split("\n")[0];
- return {
- label: _.str.escapeHTML(x[1]),
- value: x[1],
- name: x[1],
- id: x[0],
- };
- });
-
- // search more... if more results that max
- if (values.length > self.limit) {
- values = values.slice(0, self.limit);
- values.push({
- label: _t("Search More..."),
- action: function() {
- dataset.name_search(search_val, self.build_domain(), 'ilike', 160).done(function(data) {
- self._search_create_popup("search", data);
- });
- },
- classname: 'oe_m2o_dropdown_option'
- });
- }
- // quick create
- var raw_result = _(data.result).map(function(x) {return x[1];});
- if (search_val.length > 0 && !_.include(raw_result, search_val) &&
- ! (self.options && (self.options.no_create || self.options.no_quick_create))) {
- self.can_create && values.push({
- label: _.str.sprintf(_t('Create "<strong>%s</strong>"'),
- $('<span />').text(search_val).html()),
- action: function() {
- self._quick_create(search_val);
- },
- classname: 'oe_m2o_dropdown_option'
- });
- }
- // create...
- if (!(self.options && (self.options.no_create || self.options.no_create_edit)) && self.can_create){
- values.push({
- label: _t("Create and Edit..."),
- action: function() {
- self._search_create_popup("form", undefined, self._create_context(search_val));
- },
- classname: 'oe_m2o_dropdown_option'
- });
- }
- else if (values.length == 0)
- values.push({
- label: _t("No results to show..."),
- action: function() {},
- classname: 'oe_m2o_dropdown_option'
- });
-
- return values;
- });
- },
- get_search_blacklist: function() {
- return [];
- },
- _quick_create: function(name) {
- var self = this;
- var slow_create = function () {
- self._search_create_popup("form", undefined, self._create_context(name));
- };
- if (self.options.quick_create === undefined || self.options.quick_create) {
- new instance.web.DataSet(this, this.field.relation, self.build_context())
- .name_create(name).done(function(data) {
- if (!self.get('effective_readonly'))
- self.add_id(data[0]);
- }).fail(function(error, event) {
- event.preventDefault();
- slow_create();
- });
- } else
- slow_create();
- },
- // all search/create popup handling
- _search_create_popup: function(view, ids, context) {
- var self = this;
- var pop = new instance.web.form.SelectCreatePopup(this);
- pop.select_element(
- self.field.relation,
- {
- title: (view === 'search' ? _t("Search: ") : _t("Create: ")) + this.string,
- initial_ids: ids ? _.map(ids, function(x) {return x[0];}) : undefined,
- initial_view: view,
- disable_multiple_selection: true
- },
- self.build_domain(),
- new instance.web.CompoundContext(self.build_context(), context || {})
- );
- pop.on("elements_selected", self, function(element_ids) {
- self.add_id(element_ids[0]);
- self.focus();
- });
- },
- /**
- * To implement.
- */
- add_id: function(id) {},
- _create_context: function(name) {
- var tmp = {};
- var field = (this.options || {}).create_name_field;
- if (field === undefined)
- field = "name";
- if (field !== false && name && (this.options || {}).quick_create !== false)
- tmp["default_" + field] = name;
- return tmp;
- },
-};
-
-instance.web.form.M2ODialog = instance.web.Dialog.extend({
- template: "M2ODialog",
- init: function(parent) {
- this.name = parent.string;
- this._super(parent, {
- title: _.str.sprintf(_t("Create a %s"), parent.string),
- size: 'medium',
- });
- },
- start: function() {
- var self = this;
- var text = _.str.sprintf(_t("You are creating a new %s, are you sure it does not exist yet?"), self.name);
- this.$("p").text( text );
- this.$buttons.html(QWeb.render("M2ODialog.buttons"));
- this.$("input").val(this.getParent().$input.val());
- this.$buttons.find(".oe_form_m2o_qc_button").click(function(e){
- if (self.$("input").val() != ''){
- self.getParent()._quick_create(self.$("input").val());
- self.destroy();
- } else{
- e.preventDefault();
- self.$("input").focus();
- }
- });
- this.$buttons.find(".oe_form_m2o_sc_button").click(function(){
- self.getParent()._search_create_popup("form", undefined, self.getParent()._create_context(self.$("input").val()));
- self.destroy();
- });
- this.$buttons.find(".oe_form_m2o_cancel_button").click(function(){
- self.destroy();
- });
- },
-});
-
-instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, instance.web.form.ReinitializeFieldMixin, {
- template: "FieldMany2One",
- events: {
- 'keydown input': function (e) {
- switch (e.which) {
- case $.ui.keyCode.UP:
- case $.ui.keyCode.DOWN:
- e.stopPropagation();
- }
- },
- },
- init: function(field_manager, node) {
- this._super(field_manager, node);
- instance.web.form.CompletionFieldMixin.init.call(this);
- this.set({'value': false});
- this.display_value = {};
- this.display_value_backup = {};
- this.last_search = [];
- this.floating = false;
- this.current_display = null;
- this.is_started = false;
- this.ignore_focusout = false;
- },
- reinit_value: function(val) {
- this.internal_set_value(val);
- this.floating = false;
- if (this.is_started && !this.no_rerender)
- this.render_value();
- },
- initialize_field: function() {
- this.is_started = true;
- instance.web.bus.on('click', this, function() {
- if (!this.get("effective_readonly") && this.$input && this.$input.autocomplete('widget').is(':visible')) {
- this.$input.autocomplete("close");
- }
- });
- instance.web.form.ReinitializeFieldMixin.initialize_field.call(this);
- },
- initialize_content: function() {
- if (!this.get("effective_readonly"))
- this.render_editable();
- },
- destroy_content: function () {
- if (this.$drop_down) {
- this.$drop_down.off('click');
- delete this.$drop_down;
- }
- if (this.$input) {
- this.$input.closest(".modal .modal-content").off('scroll');
- this.$input.off('keyup blur autocompleteclose autocompleteopen ' +
- 'focus focusout change keydown');
- delete this.$input;
- }
- if (this.$follow_button) {
- this.$follow_button.off('blur focus click');
- delete this.$follow_button;
- }
- },
- destroy: function () {
- this.destroy_content();
- return this._super();
- },
- init_error_displayer: function() {
- // nothing
- },
- hide_error_displayer: function() {
- // doesn't work
- },
- show_error_displayer: function() {
- new instance.web.form.M2ODialog(this).open();
- },
- render_editable: function() {
- var self = this;
- this.$input = this.$el.find("input");
-
- this.init_error_displayer();
-
- self.$input.on('focus', function() {
- self.hide_error_displayer();
- });
-
- this.$drop_down = this.$el.find(".oe_m2o_drop_down_button");
- this.$follow_button = $(".oe_m2o_cm_button", this.$el);
-
- this.$follow_button.click(function(ev) {
- ev.preventDefault();
- if (!self.get('value')) {
- self.focus();
- return;
- }
- var pop = new instance.web.form.FormOpenPopup(self);
- var context = self.build_context().eval();
- var model_obj = new instance.web.Model(self.field.relation);
- model_obj.call('get_formview_id', [self.get("value"), context]).then(function(view_id){
- pop.show_element(
- self.field.relation,
- self.get("value"),
- self.build_context(),
- {
- title: _t("Open: ") + self.string,
- view_id: view_id,
- readonly: !self.can_write
- }
- );
- pop.on('write_completed', self, function(){
- self.display_value = {};
- self.display_value_backup = {};
- self.render_value();
- self.focus();
- self.trigger('changed_value');
- });
- });
- });
-
- // some behavior for input
- var input_changed = function() {
- if (self.current_display !== self.$input.val()) {
- self.current_display = self.$input.val();
- if (self.$input.val() === "") {
- self.internal_set_value(false);
- self.floating = false;
- } else {
- self.floating = true;
- }
- }
- };
- this.$input.keydown(input_changed);
- this.$input.change(input_changed);
- this.$drop_down.click(function() {
- self.$input.focus();
- if (self.$input.autocomplete("widget").is(":visible")) {
- self.$input.autocomplete("close");
- } else {
- if (self.get("value") && ! self.floating) {
- self.$input.autocomplete("search", "");
- } else {
- self.$input.autocomplete("search");
- }
- }
- });
-
- // Autocomplete close on dialog content scroll
- var close_autocomplete = _.debounce(function() {
- if (self.$input.autocomplete("widget").is(":visible")) {
- self.$input.autocomplete("close");
- }
- }, 50);
- this.$input.closest(".modal .modal-content").on('scroll', this, close_autocomplete);
-
- self.ed_def = $.Deferred();
- self.uned_def = $.Deferred();
- var ed_delay = 200;
- var ed_duration = 15000;
- var anyoneLoosesFocus = function (e) {
- if (self.ignore_focusout) { return; }
- var used = false;
- if (self.floating) {
- if (self.last_search.length > 0) {
- if (self.last_search[0][0] != self.get("value")) {
- self.display_value = {};
- self.display_value_backup = {};
- self.display_value["" + self.last_search[0][0]] = self.last_search[0][1];
- self.reinit_value(self.last_search[0][0]);
- self.last_search = []
- } else {
- used = true;
- self.render_value();
- }
- } else {
- used = true;
- }
- self.floating = false;
- }
- if (used && self.get("value") === false && ! self.no_ed && ! (self.options && (self.options.no_create || self.options.no_quick_create))) {
- self.ed_def.reject();
- self.uned_def.reject();
- self.ed_def = $.Deferred();
- self.ed_def.done(function() {
- self.can_create && self.show_error_displayer();
- ignore_blur = false;
- self.trigger('focused');
- });
- ignore_blur = true;
- setTimeout(function() {
- self.ed_def.resolve();
- self.uned_def.reject();
- self.uned_def = $.Deferred();
- self.uned_def.done(function() {
- self.hide_error_displayer();
- });
- setTimeout(function() {self.uned_def.resolve();}, ed_duration);
- }, ed_delay);
- } else {
- self.no_ed = false;
- self.ed_def.reject();
- }
- };
- var ignore_blur = false;
- this.$input.on({
- focusout: anyoneLoosesFocus,
- focus: function () { self.trigger('focused'); },
- autocompleteopen: function () { ignore_blur = true; },
- autocompleteclose: function () { setTimeout(function() {ignore_blur = false;},0); },
- blur: function () {
- // autocomplete open
- if (ignore_blur) { $(this).focus(); return; }
- if (_(self.getChildren()).any(function (child) {
- return child instanceof instance.web.form.AbstractFormPopup;
- })) { return; }
- self.trigger('blurred');
- }
- });
-
- var isSelecting = false;
- // autocomplete
- this.$input.autocomplete({
- source: function(req, resp) {
- self.get_search_result(req.term).done(function(result) {
- resp(result);
- });
- },
- select: function(event, ui) {
- isSelecting = true;
- var item = ui.item;
- if (item.id) {
- self.display_value = {};
- self.display_value_backup = {};
- self.display_value["" + item.id] = item.name;
- self.reinit_value(item.id);
- } else if (item.action) {
- item.action();
- // Cancel widget blurring, to avoid form blur event
- self.trigger('focused');
- return false;
- }
- },
- focus: function(e, ui) {
- e.preventDefault();
- },
- html: true,
- // disabled to solve a bug, but may cause others
- //close: anyoneLoosesFocus,
- minLength: 0,
- delay: 200,
- });
- var appendTo = this.$input.parents('.oe-view-manager-content, .modal-dialog').last();
- if (appendTo.length === 0) {
- appendTo = '.oe_application > *';
- }
- this.$input.autocomplete({
- appendTo: appendTo
- });
- // set position for list of suggestions box
- this.$input.autocomplete( "option", "position", { my : "left top", at: "left bottom" } );
- this.$input.autocomplete("widget").openerpClass();
- // used to correct a bug when selecting an element by pushing 'enter' in an editable list
- this.$input.keyup(function(e) {
- if (e.which === 13) { // ENTER
- if (isSelecting)
- e.stopPropagation();
- }
- isSelecting = false;
- });
- this.setupFocus(this.$follow_button);
- },
- render_value: function(no_recurse) {
- var self = this;
- if (! this.get("value")) {
- this.display_string("");
- return;
- }
- var display = this.display_value["" + this.get("value")];
- if (display) {
- this.display_string(display);
- return;
- }
- if (! no_recurse) {
- var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.build_context());
- var def = this.alive(dataset.name_get([self.get("value")])).done(function(data) {
- if (!data[0]) {
- self.do_warn(_t("Render"), _t("No value found for the field "+self.field.string+" for value "+self.get("value")));
- return;
- }
- self.display_value["" + self.get("value")] = data[0][1];
- self.render_value(true);
- }).fail( function (data, event) {
- // avoid displaying crash errors as many2One should be name_get compliant
- event.preventDefault();
- self.display_value["" + self.get("value")] = self.display_value_backup["" + self.get("value")];
- self.render_value(true);
- });
- if (this.view && this.view.render_value_defs){
- this.view.render_value_defs.push(def);
- }
- }
- },
- display_string: function(str) {
- var self = this;
- if (!this.get("effective_readonly")) {
- this.$input.val(str.split("\n")[0]);
- this.current_display = this.$input.val();
- if (this.is_false()) {
- this.$('.oe_m2o_cm_button').css({'display':'none'});
- } else {
- this.$('.oe_m2o_cm_button').css({'display':'inline'});
- }
- } else {
- var lines = _.escape(str).split("\n");
- var link = "";
- var follow = "";
- link = lines[0];
- follow = _.rest(lines).join("<br />");
- if (follow)
- link += "<br />";
- var $link = this.$el.find('.oe_form_uri')
- .unbind('click')
- .html(link);
- if (! this.options.no_open)
- $link.click(function () {
- var context = self.build_context().eval();
- var model_obj = new instance.web.Model(self.field.relation);
- model_obj.call('get_formview_action', [self.get("value"), context]).then(function(action){
- self.do_action(action);
- });
- return false;
- });
- $(".oe_form_m2o_follow", this.$el).html(follow);
- }
- },
- set_value: function(value_) {
- var self = this;
- if (value_ instanceof Array) {
- this.display_value = {};
- this.display_value_backup = {};
- if (! this.options.always_reload) {
- this.display_value["" + value_[0]] = value_[1];
- }
- else {
- this.display_value_backup["" + value_[0]] = value_[1];
- }
- value_ = value_[0];
- }
- value_ = value_ || false;
- this.reinit_value(value_);
- },
- get_displayed: function() {
- return this.display_value["" + this.get("value")];
- },
- add_id: function(id) {
- this.display_value = {};
- this.display_value_backup = {};
- this.reinit_value(id);
- },
- is_false: function() {
- return ! this.get("value");
- },
- focus: function () {
- var input = !this.get('effective_readonly') && this.$input && this.$input[0];
- return input ? input.focus() : false;
- },
- _quick_create: function() {
- this.no_ed = true;
- this.ed_def.reject();
- return instance.web.form.CompletionFieldMixin._quick_create.apply(this, arguments);
- },
- _search_create_popup: function() {
- this.no_ed = true;
- this.ed_def.reject();
- this.ignore_focusout = true;
- this.reinit_value(false);
- var res = instance.web.form.CompletionFieldMixin._search_create_popup.apply(this, arguments);
- this.ignore_focusout = false;
- this.no_ed = false;
- return res;
- },
- set_dimensions: function (height, width) {
- this._super(height, width);
- if (!this.get("effective_readonly") && this.$input)
- this.$input.css('height', height);
- }
-});
-
-instance.web.form.Many2OneButton = instance.web.form.AbstractField.extend({
- template: 'Many2OneButton',
- init: function(field_manager, node) {
- this._super.apply(this, arguments);
- },
- start: function() {
- this._super.apply(this, arguments);
- this.set_button();
- },
- set_button: function() {
- var self = this;
- if (this.$button) {
- this.$button.remove();
- }
- this.string = '';
- this.node.attrs.icon = this.get('value') ? '/web/static/src/img/icons/gtk-yes.png' : '/web/static/src/img/icons/gtk-no.png';
- this.$button = $(QWeb.render('WidgetButton', {'widget': this}));
- this.$button.addClass('oe_link').css({'padding':'4px'});
- this.$el.append(this.$button);
- this.$button.on('click', self.on_click);
- },
- on_click: function(ev) {
- var self = this;
- this.popup = new instance.web.form.FormOpenPopup(this);
- this.popup.show_element(
- this.field.relation,
- this.get('value'),
- this.build_context(),
- {title: this.string}
- );
- this.popup.on('create_completed', self, function(r) {
- self.set_value(r);
- });
- },
- set_value: function(value_) {
- var self = this;
- if (value_ instanceof Array) {
- value_ = value_[0];
- }
- value_ = value_ || false;
- this.set('value', value_);
- this.set_button();
- },
-});
-
-/**
- * Abstract-ish ListView.List subclass adding an "Add an item" row to replace
- * the big ugly button in the header.
- *
- * Requires the implementation of a ``is_readonly`` method (usually a proxy to
- * the corresponding field's readonly or effective_readonly property) to
- * decide whether the special row should or should not be inserted.
- *
- * Optionally an ``_add_row_class`` attribute can be set for the class(es) to
- * set on the insertion row.
- */
-instance.web.form.AddAnItemList = instance.web.ListView.List.extend({
- pad_table_to: function (count) {
- if (!this.view.is_action_enabled('create') || this.is_readonly()) {
- this._super(count);
- return;
- }
-
- this._super(count > 0 ? count - 1 : 0);
-
- var self = this;
- var columns = _(this.columns).filter(function (column) {
- return column.invisible !== '1';
- }).length;
- if (this.options.selectable) { columns++; }
- if (this.options.deletable) { columns++; }
-
- var $cell = $('<td>', {
- colspan: columns,
- 'class': this._add_row_class || ''
- }).append(
- $('<a>', {href: '#'}).text(_t("Add an item"))
- .mousedown(function () {
- // FIXME: needs to be an official API somehow
- if (self.view.editor.is_editing()) {
- self.view.__ignore_blur = true;
- }
- })
- .click(function (e) {
- e.preventDefault();
- e.stopPropagation();
- // FIXME: there should also be an API for that one
- if (self.view.editor.form.__blur_timeout) {
- clearTimeout(self.view.editor.form.__blur_timeout);
- self.view.editor.form.__blur_timeout = false;
- }
- self.view.ensure_saved().done(function () {
- self.view.do_add_record();
- });
- }));
-
- var $padding = this.$current.find('tr:not([data-id]):first');
- var $newrow = $('<tr>').append($cell);
- if ($padding.length) {
- $padding.before($newrow);
- } else {
- this.$current.append($newrow)
- }
- }
-});
-
-/*
-# Values: (0, 0, { fields }) create
-# (1, ID, { fields }) update
-# (2, ID) remove (delete)
-# (3, ID) unlink one (target id or target of relation)
-# (4, ID) link
-# (5) unlink all (only valid for one2many)
-*/
-var commands = {
- // (0, _, {values})
- CREATE: 0,
- 'create': function (values) {
- return [commands.CREATE, false, values];
- },
- // (1, id, {values})
- UPDATE: 1,
- 'update': function (id, values) {
- return [commands.UPDATE, id, values];
- },
- // (2, id[, _])
- DELETE: 2,
- 'delete': function (id) {
- return [commands.DELETE, id, false];
- },
- // (3, id[, _]) removes relation, but not linked record itself
- FORGET: 3,
- 'forget': function (id) {
- return [commands.FORGET, id, false];
- },
- // (4, id[, _])
- LINK_TO: 4,
- 'link_to': function (id) {
- return [commands.LINK_TO, id, false];
- },
- // (5[, _[, _]])
- DELETE_ALL: 5,
- 'delete_all': function () {
- return [5, false, false];
- },
- // (6, _, ids) replaces all linked records with provided ids
- REPLACE_WITH: 6,
- 'replace_with': function (ids) {
- return [6, false, ids];
- }
-};
-instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
- multi_selection: false,
- 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.form_last_update = $.Deferred();
- this.init_form_last_update = this.form_last_update;
- this.is_started = false;
- 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;
- var self = this;
- this.dataset.on('dataset_changed', this, function() {
- self.trigger_on_change();
- });
- this.set_value([]);
- },
- start: function() {
- this._super.apply(this, arguments);
- this.$el.addClass('oe_form_field oe_form_field_one2many');
-
- var self = this;
-
- self.load_views();
- var destroy = function() {
- self.is_loaded = self.is_loaded.then(function() {
- self.viewmanager.destroy();
- return $.when(self.load_views()).done(function() {
- self.reload_current_view();
- });
- });
- };
- this.is_loaded.done(function() {
- self.on("change:effective_readonly", self, destroy);
- });
- this.view.on("on_button_cancel", self, destroy);
- this.is_started = true;
- this.reload_current_view();
- },
- trigger_on_change: function() {
- this.trigger('changed_value');
- },
- load_views: function() {
- var self = this;
-
- var modes = this.node.attrs.mode;
- modes = !!modes ? modes.split(",") : ["tree"];
- var views = [];
- _.each(modes, function(mode) {
- if (! _.include(["list", "tree", "graph", "kanban"], mode)) {
- throw new Error(_.str.sprintf(_t("View type '%s' is not supported in One2Many."), mode));
- }
- var view = {
- view_id: false,
- view_type: mode == "tree" ? "list" : mode,
- options: {}
- };
- if (self.field.views && self.field.views[mode]) {
- view.embedded_view = self.field.views[mode];
- }
- if(view.view_type === "list") {
- _.extend(view.options, {
- addable: null,
- selectable: self.multi_selection,
- sortable: true,
- import_enabled: false,
- deletable: true
- });
- if (self.get("effective_readonly")) {
- _.extend(view.options, {
- deletable: null,
- reorderable: false,
- });
- }
- } else if (view.view_type === "form") {
- if (self.get("effective_readonly")) {
- view.view_type = 'form';
- }
- _.extend(view.options, {
- not_interactible_on_create: true,
- });
- } else if (view.view_type === "kanban") {
- _.extend(view.options, {
- confirm_on_delete: false,
- });
- if (self.get("effective_readonly")) {
- _.extend(view.options, {
- action_buttons: false,
- quick_creatable: false,
- creatable: false,
- read_only_mode: true,
- });
- }
- }
- views.push(view);
- });
- this.views = views;
-
- this.viewmanager = new instance.web.form.One2ManyViewManager(this, this.dataset, views, {});
- this.viewmanager.o2m = self;
- var once = $.Deferred().done(function() {
- self.init_form_last_update.resolve();
- });
- var def = $.Deferred().done(function() {
- self.initial_is_loaded.resolve();
- });
- this.viewmanager.on("controller_inited", self, function(view_type, controller) {
- controller.o2m = self;
- if (view_type == "list") {
- if (self.get("effective_readonly")) {
- controller.on('edit:before', self, function (e) {
- e.cancel = true;
- });
- _(controller.columns).find(function (column) {
- if (!(column instanceof instance.web.list.Handle)) {
- return false;
- }
- column.modifiers.invisible = true;
- return true;
- });
- }
- } else if (view_type === "form") {
- if (self.get("effective_readonly")) {
- $(".oe_form_buttons", controller.$el).children().remove();
- }
- controller.on("load_record", self, function(){
- once.resolve();
- });
- controller.on('pager_action_executed',self,self.save_any_view);
- } else if (view_type == "graph") {
- self.reload_current_view();
- }
- def.resolve();
- });
- this.viewmanager.on("switch_mode", self, function(n_mode, b, c, d, e) {
- $.when(self.save_any_view()).done(function() {
- if (n_mode === "list") {
- $.async_when().done(function() {
- self.reload_current_view();
- });
- }
- });
- });
- $.async_when().done(function () {
- self.viewmanager.appendTo(self.$el);
- });
- return def;
- },
- reload_current_view: function() {
- var self = this;
- self.is_loaded = self.is_loaded.then(function() {
- var view = self.get_active_view();
- if (view.type === "list") {
- return view.controller.reload_content();
- } else if (view.type === "form") {
- if (self.dataset.index === null && self.dataset.ids.length >= 1) {
- self.dataset.index = 0;
- }
- var act = function() {
- return view.controller.do_show();
- };
- self.form_last_update = self.form_last_update.then(act, act);
- return self.form_last_update;
- } else if (view.controller.do_search) {
- return view.controller.do_search(self.build_domain(), self.dataset.get_context(), []);
- }
- }, undefined);
- return self.is_loaded;
- },
- get_active_view: function () {
- /**
- * Returns the current active view if any.
- */
- return (this.viewmanager && this.viewmanager.active_view);
- },
- set_value: function(value_) {
- value_ = value_ || [];
- var self = this;
- var view = this.get_active_view();
- this.dataset.reset_ids([]);
- var ids;
- if(value_.length >= 1 && value_[0] instanceof Array) {
- ids = [];
- _.each(value_, function(command) {
- var obj = {values: command[2]};
- switch (command[0]) {
- case commands.CREATE:
- obj['id'] = _.uniqueId(self.dataset.virtual_id_prefix);
- obj.defaults = {};
- self.dataset.to_create.push(obj);
- self.dataset.cache.push(_.extend(_.clone(obj), {values: _.clone(command[2])}));
- ids.push(obj.id);
- return;
- case commands.UPDATE:
- obj['id'] = command[1];
- self.dataset.to_write.push(obj);
- self.dataset.cache.push(_.extend(_.clone(obj), {values: _.clone(command[2])}));
- ids.push(obj.id);
- return;
- case commands.DELETE:
- self.dataset.to_delete.push({id: command[1]});
- return;
- case commands.LINK_TO:
- ids.push(command[1]);
- return;
- case commands.DELETE_ALL:
- self.dataset.delete_all = true;
- return;
- }
- });
- this._super(ids);
- this.dataset.set_ids(ids);
- } else if (value_.length >= 1 && typeof(value_[0]) === "object") {
- ids = [];
- this.dataset.delete_all = true;
- _.each(value_, function(command) {
- var obj = {values: command};
- obj['id'] = _.uniqueId(self.dataset.virtual_id_prefix);
- obj.defaults = {};
- self.dataset.to_create.push(obj);
- self.dataset.cache.push(_.clone(obj));
- ids.push(obj.id);
- });
- this._super(ids);
- this.dataset.set_ids(ids);
- } else {
- this._super(value_);
- this.dataset.reset_ids(value_);
- }
- if (this.dataset.index === null && this.dataset.ids.length > 0) {
- this.dataset.index = 0;
- }
- this.trigger_on_change();
- if (this.is_started && !this.no_rerender) {
- return self.reload_current_view();
- } else {
- return $.when();
- }
- },
- get_value: function() {
- var self = this;
- if (!this.dataset)
- return [];
- var val = this.dataset.delete_all ? [commands.delete_all()] : [];
- val = val.concat(_.map(this.dataset.ids, function(id) {
- var alter_order = _.detect(self.dataset.to_create, function(x) {return x.id === id;});
- if (alter_order) {
- return commands.create(alter_order.values);
- }
- alter_order = _.detect(self.dataset.to_write, function(x) {return x.id === id;});
- if (alter_order) {
- return commands.update(alter_order.id, alter_order.values);
- }
- return commands.link_to(id);
- }));
- return val.concat(_.map(
- this.dataset.to_delete, function(x) {
- return commands['delete'](x.id);}));
- },
- commit_value: function() {
- return this.save_any_view();
- },
- save_any_view: function() {
- var view = this.get_active_view();
- if (view) {
- if (this.viewmanager.active_view.type === "form") {
- if (view.controller.is_initialized.state() !== 'resolved') {
- return $.when(false);
- }
- return $.when(view.controller.save());
- } else if (this.viewmanager.active_view.type === "list") {
- return $.when(view.controller.ensure_saved());
- }
- }
- return $.when(false);
- },
- is_syntax_valid: function() {
- var view = this.get_active_view();
- if (!view){
- return true;
- }
- switch (this.viewmanager.active_view.type) {
- case 'form':
- return _(view.controller.fields).chain()
- .invoke('is_valid')
- .all(_.identity)
- .value();
- case 'list':
- return view.controller.is_valid();
- }
- return true;
- },
-});
-
-instance.web.form.One2ManyViewManager = instance.web.ViewManager.extend({
- template: 'One2Many.viewmanager',
- init: function(parent, dataset, views, flags) {
- this._super(parent, dataset, views, _.extend({}, flags, {$sidebar: false}));
- this.registry = instance.web.views.extend({
- list: 'instance.web.form.One2ManyListView',
- form: 'instance.web.form.One2ManyFormView',
- });
- this.__ignore_blur = false;
- },
- switch_mode: function(mode, unused) {
- if (mode !== 'form') {
- return this._super(mode, unused);
- }
- var self = this;
- var id = self.o2m.dataset.index !== null ? self.o2m.dataset.ids[self.o2m.dataset.index] : null;
- var pop = new instance.web.form.FormOpenPopup(this);
- pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(), {
- title: _t("Open: ") + self.o2m.string,
- create_function: function(data, options) {
- return self.o2m.dataset.create(data, options).done(function(r) {
- self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r]));
- self.o2m.dataset.trigger("dataset_changed", r);
- });
- },
- write_function: function(id, data, options) {
- return self.o2m.dataset.write(id, data, {}).done(function() {
- self.o2m.reload_current_view();
- });
- },
- 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("elements_selected", self, function() {
- self.o2m.reload_current_view();
- });
- },
-});
-
-instance.web.form.One2ManyDataSet = instance.web.BufferedDataSet.extend({
- get_context: function() {
- this.context = this.o2m.build_context();
- return this.context;
- }
-});
-
-instance.web.form.One2ManyListView = instance.web.ListView.extend({
- _template: 'One2Many.listview',
- init: function (parent, dataset, view_id, options) {
- this._super(parent, dataset, view_id, _.extend(options || {}, {
- GroupsType: instance.web.form.One2ManyGroups,
- ListType: instance.web.form.One2ManyList
- }));
- this.on('edit:after', this, this.proxy('_after_edit'));
- this.on('save:before cancel:before', this, this.proxy('_before_unedit'));
-
- this.records
- .bind('add', this.proxy("changed_records"))
- .bind('remove', this.proxy("changed_records"));
- this.on('save:after', this, this.proxy("changed_records"));
- },
- start: function () {
- var ret = this._super();
- this.$el
- .off('mousedown.handleButtons')
- .on('mousedown.handleButtons', 'table button, div a.oe_m2o_cm_button', this.proxy('_button_down'));
- return ret;
- },
- changed_records: function () {
- this.o2m.trigger_on_change();
- },
- is_valid: function () {
- var self = this;
- if (!this.fields_view || !this.editable()){
- return true;
- }
- if (_.isEmpty(this.records.records)){
- return true;
- }
- current_values = {};
- _.each(this.editor.form.fields, function(field){
- field._inhibit_on_change_flag = true;
- field.no_rerender = true;
- current_values[field.name] = field.get('value');
- });
- var valid = _.every(this.records.records, function(record){
- _.each(self.editor.form.fields, function(field){
- field.set_value(record.attributes[field.name]);
- });
- return _.every(self.editor.form.fields, function(field){
- field.process_modifiers();
- field._check_css_flags();
- return field.is_valid();
- });
- });
- _.each(this.editor.form.fields, function(field){
- field.set('value', current_values[field.name]);
- field._inhibit_on_change_flag = false;
- field.no_rerender = false;
- });
- return valid;
- },
- do_add_record: function () {
- if (this.editable()) {
- this._super.apply(this, arguments);
- } else {
- var self = this;
- var pop = new instance.web.form.SelectCreatePopup(this);
- pop.select_element(
- self.o2m.field.relation,
- {
- title: _t("Create: ") + self.o2m.string,
- initial_view: "form",
- alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined,
- create_function: function(data, options) {
- return self.o2m.dataset.create(data, options).done(function(r) {
- self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r]));
- self.o2m.dataset.trigger("dataset_changed", r);
- });
- },
- read_function: function() {
- return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments);
- },
- parent_view: self.o2m.view,
- child_name: self.o2m.name,
- form_view_options: {'not_interactible_on_create':true}
- },
- self.o2m.build_domain(),
- self.o2m.build_context()
- );
- pop.on("elements_selected", self, function() {
- self.o2m.reload_current_view();
- });
- }
- },
- do_activate_record: function(index, id) {
- var self = this;
- var pop = new instance.web.form.FormOpenPopup(self);
- pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(), {
- title: _t("Open: ") + self.o2m.string,
- write_function: function(id, data) {
- return self.o2m.dataset.write(id, data, {}).done(function() {
- self.o2m.reload_current_view();
- });
- },
- 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: !this.is_action_enabled('edit') || self.o2m.get("effective_readonly")
- });
- },
- do_button_action: function (name, id, callback) {
- if (!_.isNumber(id)) {
- instance.webclient.notification.warn(
- _t("Action Button"),
- _t("The o2m record must be saved before an action can be used"));
- return;
- }
- var parent_form = this.o2m.view;
- var self = this;
- this.ensure_saved().then(function () {
- if (parent_form)
- return parent_form.save();
- else
- return $.when();
- }).done(function () {
- var ds = self.o2m.dataset;
- var cached_records = _.any([ds.to_create, ds.to_delete, ds.to_write], function(value) {
- return value.length;
- });
- if (!self.o2m.options.reload_on_button && !cached_records) {
- self.handle_button(name, id, callback);
- }else {
- self.handle_button(name, id, function(){
- self.o2m.view.reload();
- });
- }
- });
- },
-
- _after_edit: function () {
- this.__ignore_blur = false;
- this.editor.form.on('blurred', this, this._on_form_blur);
-
- // The form's blur thing may be jiggered during the edition setup,
- // potentially leading to the o2m instasaving the row. Cancel any
- // blurring triggered the edition startup here
- this.editor.form.widgetFocused();
- },
- _before_unedit: function () {
- this.editor.form.off('blurred', this, this._on_form_blur);
- },
- _button_down: function () {
- // If a button is clicked (usually some sort of action button), it's
- // the button's responsibility to ensure the editable list is in the
- // correct state -> ignore form blurring
- this.__ignore_blur = true;
- },
- /**
- * Handles blurring of the nested form (saves the currently edited row),
- * unless the flag to ignore the event is set to ``true``
- *
- * Makes the internal form go away
- */
- _on_form_blur: function () {
- if (this.__ignore_blur) {
- this.__ignore_blur = false;
- return;
- }
- // FIXME: why isn't there an API for this?
- if (this.editor.form.$el.hasClass('oe_form_dirty')) {
- this.ensure_saved();
- return;
- }
- this.cancel_edition();
- },
- keypress_ENTER: function () {
- // blurring caused by hitting the [Return] key, should skip the
- // autosave-on-blur and let the handler for [Return] do its thing (save
- // the current row *anyway*, then create a new one/edit the next one)
- this.__ignore_blur = true;
- this._super.apply(this, arguments);
- },
- do_delete: function (ids) {
- var confirm = window.confirm;
- window.confirm = function () { return true; };
- try {
- return this._super(ids);
- } finally {
- window.confirm = confirm;
- }
- },
- reload_record: function (record, options) {
- if (!options || !options['do_not_evict']) {
- // Evict record.id from cache to ensure it will be reloaded correctly
- this.dataset.evict_record(record.get('id'));
- }
-
- return this._super(record);
- }
-});
-instance.web.form.One2ManyGroups = instance.web.ListView.Groups.extend({
- setup_resequence_rows: function () {
- if (!this.view.o2m.get('effective_readonly')) {
- this._super.apply(this, arguments);
- }
- }
-});
-instance.web.form.One2ManyList = instance.web.form.AddAnItemList.extend({
- _add_row_class: 'oe_form_field_one2many_list_row_add',
- is_readonly: function () {
- return this.view.o2m.get('effective_readonly');
- },
-});
-
-instance.web.form.One2ManyFormView = instance.web.FormView.extend({
- form_template: 'One2Many.formview',
- load_form: function(data) {
- this._super(data);
- var self = this;
- this.$buttons.find('button.oe_form_button_create').click(function() {
- self.save().done(self.on_button_new);
- });
- },
- do_notify_change: function() {
- if (this.dataset.parent_view) {
- this.dataset.parent_view.do_notify_change();
- } else {
- this._super.apply(this, arguments);
- }
- }
-});
-
-instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, instance.web.form.ReinitializeFieldMixin, {
- template: "FieldMany2ManyTags",
- tag_template: "FieldMany2ManyTag",
- init: function() {
- this._super.apply(this, arguments);
- instance.web.form.CompletionFieldMixin.init.call(this);
- this.set({"value": []});
- this._display_orderer = new instance.web.DropMisordered();
- this._drop_shown = false;
- },
- initialize_texttext: function(){
- var self = this;
- return {
- plugins : 'tags arrow autocomplete',
- autocomplete: {
- render: function(suggestion) {
- return $('<span class="text-label"/>').
- data('index', suggestion['index']).html(suggestion['label']);
- }
- },
- ext: {
- autocomplete: {
- selectFromDropdown: function() {
- this.trigger('hideDropdown');
- var index = Number(this.selectedSuggestionElement().children().children().data('index'));
- var data = self.search_result[index];
- if (data.id) {
- self.add_id(data.id);
- } else {
- self.ignore_blur = true;
- data.action();
- }
- this.trigger('setSuggestions', {result : []});
- },
- },
- tags: {
- isTagAllowed: function(tag) {
- return !!tag.name;
-
- },
- removeTag: function(tag) {
- var id = tag.data("id");
- self.set({"value": _.without(self.get("value"), id)});
- },
- renderTag: function(stuff) {
- return $.fn.textext.TextExtTags.prototype.renderTag.
- call(this, stuff).data("id", stuff.id);
- },
- },
- itemManager: {
- itemToString: function(item) {
- return item.name;
- },
- },
- core: {
- onSetInputData: function(e, data) {
- if (data === '') {
- this._plugins.autocomplete._suggestions = null;
- }
- this.input().val(data);
- },
- },
- },
- }
- },
- initialize_content: function() {
- if (this.get("effective_readonly"))
- return;
- var self = this;
- self.ignore_blur = false;
- self.$text = this.$("textarea");
- self.$text.textext(self.initialize_texttext()).bind('getSuggestions', function(e, data) {
- var _this = this;
- var str = !!data ? data.query || '' : '';
- self.get_search_result(str).done(function(result) {
- self.search_result = result;
- $(_this).trigger('setSuggestions', {result : _.map(result, function(el, i) {
- return _.extend(el, {index:i});
- })});
- });
- }).bind('hideDropdown', function() {
- self._drop_shown = false;
- }).bind('showDropdown', function() {
- self._drop_shown = true;
- });
- self.tags = self.$text.textext()[0].tags();
- self.$text
- .focusin(function () {
- self.trigger('focused');
- self.ignore_blur = false;
- })
- .focusout(function() {
- self.$text.trigger("setInputData", "");
- if (!self.ignore_blur) {
- self.trigger('blurred');
- }
- }).keydown(function(e) {
- if (e.which === $.ui.keyCode.TAB && self._drop_shown) {
- self.$text.textext()[0].autocomplete().selectFromDropdown();
- }
- });
- },
- // WARNING: duplicated in 4 other M2M widgets
- set_value: function(value_) {
- value_ = value_ || [];
- if (value_.length >= 1 && value_[0] instanceof Array) {
- // value_ is a list of m2m commands. We only process
- // LINK_TO and REPLACE_WITH in this context
- var val = [];
- _.each(value_, function (command) {
- if (command[0] === commands.LINK_TO) {
- val.push(command[1]); // (4, id[, _])
- } else if (command[0] === commands.REPLACE_WITH) {
- val = command[2]; // (6, _, ids)
- }
- });
- value_ = val;
- }
- this._super(value_);
- },
- is_false: function() {
- return _(this.get("value")).isEmpty();
- },
- get_value: function() {
- var tmp = [commands.replace_with(this.get("value"))];
- return tmp;
- },
- get_search_blacklist: function() {
- return this.get("value");
- },
- map_tag: function(data){
- return _.map(data, function(el) {return {name: el[1], id:el[0]};})
- },
- get_render_data: function(ids){
- var self = this;
- var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.build_context());
- return dataset.name_get(ids);
- },
- render_tag: function(data) {
- var self = this;
- if (! self.get("effective_readonly")) {
- self.tags.containerElement().children().remove();
- self.$('textarea').css("padding-left", "3px");
- self.tags.addTags(self.map_tag(data));
- } else {
- self.$el.html(QWeb.render(self.tag_template, {elements: data}));
- }
- },
- render_value: function() {
- var self = this;
- var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.build_context());
- var values = self.get("value");
- var handle_names = function(data) {
- if (self.isDestroyed())
- return;
- var indexed = {};
- _.each(data, function(el) {
- indexed[el[0]] = el;
- });
- data = _.map(values, function(el) { return indexed[el]; });
- self.render_tag(data);
- }
- if (! values || values.length > 0) {
- return this._display_orderer.add(self.get_render_data(values)).done(handle_names);
- } else {
- handle_names([]);
- }
- },
- add_id: function(id) {
- this.set({'value': _.uniq(this.get('value').concat([id]))});
- },
- focus: function () {
- var input = this.$text && this.$text[0];
- return input ? input.focus() : false;
- },
- set_dimensions: function (height, width) {
- this._super(height, width);
- this.$("textarea").css({
- width: width,
- minHeight: height
- });
- },
- _search_create_popup: function() {
- self.ignore_blur = true;
- return instance.web.form.CompletionFieldMixin._search_create_popup.apply(this, arguments);
- },
-});
-
-/**
- widget options:
- - reload_on_button: Reload the whole form view if click on a button in a list view.
- If you see this options, do not use it, it's basically a dirty hack to make one
- precise o2m to behave the way we want.
-*/
-instance.web.form.FieldMany2Many = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
- multi_selection: false,
- disable_utility_classes: true,
- init: function(field_manager, node) {
- this._super(field_manager, node);
- this.is_loaded = $.Deferred();
- this.dataset = new instance.web.form.Many2ManyDataSet(this, this.field.relation);
- this.dataset.m2m = this;
- var self = this;
- this.dataset.on('unlink', self, function(ids) {
- self.dataset_changed();
- });
- this.set_value([]);
- this.list_dm = new instance.web.DropMisordered();
- this.render_value_dm = new instance.web.DropMisordered();
- },
- initialize_content: function() {
- var self = this;
-
- this.$el.addClass('oe_form_field oe_form_field_many2many');
-
- this.list_view = new instance.web.form.Many2ManyListView(this, this.dataset, false, {
- 'addable': null,
- 'deletable': this.get("effective_readonly") ? false : true,
- 'selectable': this.multi_selection,
- 'sortable': false,
- 'reorderable': false,
- 'import_enabled': false,
- });
- var embedded = (this.field.views || {}).tree;
- if (embedded) {
- this.list_view.set_embedded_view(embedded);
- }
- this.list_view.m2m_field = this;
- var loaded = $.Deferred();
- this.list_view.on("list_view_loaded", this, function() {
- loaded.resolve();
- });
- this.list_view.appendTo(this.$el);
-
- var old_def = self.is_loaded;
- self.is_loaded = $.Deferred().done(function() {
- old_def.resolve();
- });
- this.list_dm.add(loaded).then(function() {
- self.is_loaded.resolve();
- });
- },
- destroy_content: function() {
- this.list_view.destroy();
- this.list_view = undefined;
- },
- // WARNING: duplicated in 4 other M2M widgets
- set_value: function(value_) {
- value_ = value_ || [];
- if (value_.length >= 1 && value_[0] instanceof Array) {
- // value_ is a list of m2m commands. We only process
- // LINK_TO and REPLACE_WITH in this context
- var val = [];
- _.each(value_, function (command) {
- if (command[0] === commands.LINK_TO) {
- val.push(command[1]); // (4, id[, _])
- } else if (command[0] === commands.REPLACE_WITH) {
- val = command[2]; // (6, _, ids)
- }
- });
- value_ = val;
- }
- this._super(value_);
- },
- get_value: function() {
- return [commands.replace_with(this.get('value'))];
- },
- is_false: function () {
- return _(this.get("value")).isEmpty();
- },
- render_value: function() {
- var self = this;
- this.dataset.set_ids(this.get("value"));
- this.render_value_dm.add(this.is_loaded).then(function() {
- return self.list_view.reload_content();
- });
- },
- dataset_changed: function() {
- this.internal_set_value(this.dataset.ids);
- },
-});
-
-instance.web.form.Many2ManyDataSet = instance.web.DataSetStatic.extend({
- get_context: function() {
- this.context = this.m2m.build_context();
- return this.context;
- }
-});
-
-/**
- * @class
- * @extends instance.web.ListView
- */
-instance.web.form.Many2ManyListView = instance.web.ListView.extend(/** @lends instance.web.form.Many2ManyListView# */{
- init: function (parent, dataset, view_id, options) {
- this._super(parent, dataset, view_id, _.extend(options || {}, {
- ListType: instance.web.form.Many2ManyList,
- }));
- },
- do_add_record: function () {
- var pop = new instance.web.form.SelectCreatePopup(this);
- pop.select_element(
- this.model,
- {
- title: _t("Add: ") + this.m2m_field.string,
- alternative_form_view: this.m2m_field.field.views ? this.m2m_field.field.views["form"] : undefined,
- no_create: this.m2m_field.options.no_create,
- },
- 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("elements_selected", self, function(element_ids) {
- var reload = false;
- _(element_ids).each(function (id) {
- if(! _.detect(self.dataset.ids, function(x) {return x == id;})) {
- self.dataset.set_ids(self.dataset.ids.concat([id]));
- self.m2m_field.dataset_changed();
- reload = true;
- }
- });
- if (reload) {
- self.reload_content();
- }
- });
- },
- do_activate_record: function(index, id) {
- var self = 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.m2m_field.string,
- alternative_form_view: this.m2m_field.field.views ? this.m2m_field.field.views["form"] : undefined,
- readonly: this.getParent().get("effective_readonly")
- });
- pop.on('write_completed', self, self.reload_content);
- },
- do_button_action: function(name, id, callback) {
- var self = this;
- var _sup = _.bind(this._super, this);
- if (! this.m2m_field.options.reload_on_button) {
- return _sup(name, id, callback);
- } else {
- return this.m2m_field.view.save().then(function() {
- return _sup(name, id, function() {
- self.m2m_field.view.reload();
- });
- });
- }
- },
- is_action_enabled: function () { return true; },
-});
-instance.web.form.Many2ManyList = instance.web.form.AddAnItemList.extend({
- _add_row_class: 'oe_form_field_many2many_list_row_add',
- is_readonly: function () {
- return this.view.m2m_field.get('effective_readonly');
- }
-});
-
-instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, {
- disable_utility_classes: true,
- init: function(field_manager, node) {
- this._super(field_manager, node);
- instance.web.form.CompletionFieldMixin.init.call(this);
- m2m_kanban_lazy_init();
- this.is_loaded = $.Deferred();
- this.initial_is_loaded = this.is_loaded;
-
- var self = this;
- this.dataset = new instance.web.form.Many2ManyDataSet(this, this.field.relation);
- this.dataset.m2m = this;
- this.dataset.on('unlink', self, function(ids) {
- self.dataset_changed();
- });
- },
- start: function() {
- this._super.apply(this, arguments);
-
- var self = this;
-
- self.load_view();
- self.on("change:effective_readonly", self, function() {
- self.is_loaded = self.is_loaded.then(function() {
- self.kanban_view.destroy();
- return $.when(self.load_view()).done(function() {
- self.render_value();
- });
- });
- });
- },
- // WARNING: duplicated in 4 other M2M widgets
- set_value: function(value_) {
- value_ = value_ || [];
- if (value_.length >= 1 && value_[0] instanceof Array) {
- // value_ is a list of m2m commands. We only process
- // LINK_TO and REPLACE_WITH in this context
- var val = [];
- _.each(value_, function (command) {
- if (command[0] === commands.LINK_TO) {
- val.push(command[1]); // (4, id[, _])
- } else if (command[0] === commands.REPLACE_WITH) {
- val = command[2]; // (6, _, ids)
- }
- });
- value_ = val;
- }
- this._super(value_);
- },
- get_value: function() {
- return [commands.replace_with(this.get('value'))];
- },
- load_view: function() {
- var self = this;
- this.kanban_view = new instance.web.form.Many2ManyKanbanView(this, this.dataset, false, {
- 'create_text': _t("Add"),
- 'creatable': self.get("effective_readonly") ? false : true,
- 'quick_creatable': self.get("effective_readonly") ? false : true,
- 'read_only_mode': self.get("effective_readonly") ? true : false,
- 'confirm_on_delete': false,
- });
- var embedded = (this.field.views || {}).kanban;
- if (embedded) {
- this.kanban_view.set_embedded_view(embedded);
- }
- this.kanban_view.m2m = this;
- var loaded = $.Deferred();
- this.kanban_view.on("kanban_view_loaded",self,function() {
- self.initial_is_loaded.resolve();
- loaded.resolve();
- });
- this.kanban_view.on('switch_mode', this, this.open_popup);
- $.async_when().done(function () {
- self.kanban_view.appendTo(self.$el);
- });
- return loaded;
- },
- render_value: function() {
- var self = this;
- this.dataset.set_ids(this.get("value"));
- this.is_loaded = this.is_loaded.then(function() {
- return self.kanban_view.do_search(self.build_domain(), self.dataset.get_context(), []);
- });
- },
- dataset_changed: function() {
- this.set({'value': this.dataset.ids});
- },
- open_popup: function(type, unused) {
- if (type !== "form")
- return;
- var self = this;
- var pop;
- if (this.dataset.index === null) {
- pop = new instance.web.form.SelectCreatePopup(this);
- pop.select_element(
- this.field.relation,
- {
- title: _t("Add: ") + this.string
- },
- new instance.web.CompoundDomain(this.build_domain(), ["!", ["id", "in", this.dataset.ids]]),
- this.build_context()
- );
- pop.on("elements_selected", self, function(element_ids) {
- _.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.dataset_changed();
- self.render_value();
- }
- });
- });
- } else {
- var id = self.dataset.ids[self.dataset.index];
- pop = new instance.web.form.FormOpenPopup(this);
- pop.show_element(self.field.relation, id, self.build_context(), {
- title: _t("Open: ") + self.string,
- write_function: function(id, data, options) {
- return self.dataset.write(id, data, {}).done(function() {
- self.render_value();
- });
- },
- alternative_form_view: self.field.views ? self.field.views["form"] : undefined,
- parent_view: self.view,
- child_name: self.name,
- readonly: self.get("effective_readonly")
- });
- }
- },
- add_id: function(id) {
- this.quick_create.add_id(id);
- },
-});
-
-function m2m_kanban_lazy_init() {
-if (instance.web.form.Many2ManyKanbanView)
- return;
-instance.web.form.Many2ManyKanbanView = instance.web_kanban.KanbanView.extend({
- quick_create_class: 'instance.web.form.Many2ManyQuickCreate',
- _is_quick_create_enabled: function() {
- return this._super() && ! this.group_by;
- },
-});
-instance.web.form.Many2ManyQuickCreate = instance.web.Widget.extend({
- template: 'Many2ManyKanban.quick_create',
-
- /**
- * close_btn: If true, the widget will display a "Close" button able to trigger
- * a "close" event.
- */
- init: function(parent, dataset, context, buttons) {
- this._super(parent);
- this.m2m = this.getParent().view.m2m;
- this.m2m.quick_create = this;
- this._dataset = dataset;
- this._buttons = buttons || false;
- this._context = context || {};
- },
- start: function () {
- var self = this;
- self.$text = this.$el.find('input').css("width", "200px");
- self.$text.textext({
- plugins : 'arrow autocomplete',
- autocomplete: {
- render: function(suggestion) {
- return $('<span class="text-label"/>').
- data('index', suggestion['index']).html(suggestion['label']);
- }
- },
- ext: {
- autocomplete: {
- selectFromDropdown: function() {
- $(this).trigger('hideDropdown');
- var index = Number(this.selectedSuggestionElement().children().children().data('index'));
- var data = self.search_result[index];
- if (data.id) {
- self.add_id(data.id);
- } else {
- data.action();
- }
- },
- },
- itemManager: {
- itemToString: function(item) {
- return item.name;
- },
- },
- },
- }).bind('getSuggestions', function(e, data) {
- var _this = this;
- var str = !!data ? data.query || '' : '';
- self.m2m.get_search_result(str).done(function(result) {
- self.search_result = result;
- $(_this).trigger('setSuggestions', {result : _.map(result, function(el, i) {
- return _.extend(el, {index:i});
- })});
- });
- });
- self.$text.focusout(function() {
- self.$text.val("");
- });
- },
- focus: function() {
- this.$text[0].focus();
- },
- add_id: function(id) {
- var self = this;
- self.$text.val("");
- self.trigger('added', id);
- this.m2m.dataset_changed();
- },
-});
-}
-
-/**
- * Class with everything which is common between FormOpenPopup and SelectCreatePopup.
- */
-instance.web.form.AbstractFormPopup = instance.web.Widget.extend({
- template: "AbstractFormPopup.render",
- /**
- * options:
- * -readonly: only applicable when not in creation mode, default to false
- * - alternative_form_view
- * - view_id
- * - write_function
- * - read_function
- * - create_function
- * - parent_view
- * - child_name
- * - form_view_options
- */
- init_popup: function(model, row_id, domain, context, options) {
- this.row_id = row_id;
- this.model = model;
- this.domain = domain || [];
- this.context = context || {};
- this.options = options;
- _.defaults(this.options, {});
- },
- init_dataset: function() {
- var self = this;
- this.created_elements = [];
- this.dataset = new instance.web.ProxyDataSet(this, this.model, this.context);
- this.dataset.read_function = this.options.read_function;
- this.dataset.create_function = function(data, options, sup) {
- var fct = self.options.create_function || sup;
- return fct.call(this, data, options).done(function(r) {
- self.trigger('create_completed saved', r);
- self.created_elements.push(r);
- });
- };
- this.dataset.write_function = function(id, data, options, sup) {
- var fct = self.options.write_function || sup;
- return fct.call(this, id, data, options).done(function(r) {
- self.trigger('write_completed saved', r);
- });
- };
- this.dataset.parent_view = this.options.parent_view;
- this.dataset.child_name = this.options.child_name;
- },
- display_popup: function() {
- var self = this;
- this.renderElement();
- var dialog = new instance.web.Dialog(this, {
- dialogClass: 'oe_act_window',
- title: this.options.title || "",
- }, this.$el).open();
- dialog.on('closing', this, function (e){
- self.check_exit(true);
- });
- this.$buttonpane = dialog.$buttons;
- this.start();
- },
- setup_form_view: function() {
- var self = this;
- if (this.row_id) {
- this.dataset.ids = [this.row_id];
- this.dataset.index = 0;
- } else {
- this.dataset.index = null;
- }
- var options = _.clone(self.options.form_view_options) || {};
- if (this.row_id !== null) {
- options.initial_mode = this.options.readonly ? "view" : "edit";
- }
- _.extend(options, {
- $buttons: this.$buttonpane,
- });
- this.view_form = new instance.web.FormView(this, this.dataset, this.options.view_id || false, options);
- if (this.options.alternative_form_view) {
- this.view_form.set_embedded_view(this.options.alternative_form_view);
- }
- this.view_form.appendTo(this.$(".oe_popup_form").show());
- this.view_form.on("form_view_loaded", self, function() {
- var multi_select = self.row_id === null && ! self.options.disable_multiple_selection;
- self.$buttonpane.html(QWeb.render("AbstractFormPopup.buttons", {
- multi_select: multi_select,
- readonly: self.row_id !== null && self.options.readonly,
- }));
- var $snbutton = self.$buttonpane.find(".oe_abstractformpopup-form-save-new");
- $snbutton.click(function() {
- $.when(self.view_form.save()).done(function() {
- self.view_form.reload_mutex.exec(function() {
- self.view_form.on_button_new();
- });
- });
- });
- var $sbutton = self.$buttonpane.find(".oe_abstractformpopup-form-save");
- $sbutton.click(function() {
- $.when(self.view_form.save()).done(function() {
- self.view_form.reload_mutex.exec(function() {
- self.check_exit();
- });
- });
- });
- var $cbutton = self.$buttonpane.find(".oe_abstractformpopup-form-close");
- $cbutton.click(function() {
- self.view_form.trigger('on_button_cancel');
- self.check_exit();
- });
- self.view_form.do_show();
- });
- },
- select_elements: function(element_ids) {
- this.trigger("elements_selected", element_ids);
- },
- check_exit: function(no_destroy) {
- if (this.created_elements.length > 0) {
- this.select_elements(this.created_elements);
- this.created_elements = [];
- }
- this.trigger('closed');
- this.destroy();
- },
- destroy: function () {
- this.trigger('closed');
- if (this.$el.is(":data(bs.modal)")) {
- this.$el.parents('.modal').modal('hide');
- }
- this._super();
- },
-});
-
-/**
- * Class to display a popup containing a form view.
- */
-instance.web.form.FormOpenPopup = instance.web.form.AbstractFormPopup.extend({
- show_element: function(model, row_id, context, options) {
- this.init_popup(model, row_id, [], context, options);
- _.defaults(this.options, {
- });
- this.display_popup();
- },
- start: function() {
- this._super();
- this.init_dataset();
- this.setup_form_view();
- },
-});
-
-/**
- * Class to display a popup to display a list to search a row. It also allows
- * to switch to a form view to create a new row.
- */
-instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend({
- /**
- * options:
- * - initial_ids
- * - initial_view: form or search (default search)
- * - disable_multiple_selection
- * - list_view_options
- */
- select_element: function(model, options, domain, context) {
- this.init_popup(model, null, domain, context, options);
- var self = this;
- _.defaults(this.options, {
- initial_view: "search",
- });
- this.initial_ids = this.options.initial_ids;
- this.display_popup();
- },
- start: function() {
- this.init_dataset();
- if (this.options.initial_view == "search") {
- var context = instance.web.pyeval.sync_eval_domains_and_contexts({
- domains: [],
- contexts: [this.context]
- }).context;
- var search_defaults = {};
- _.each(context, function (value_, key) {
- var match = /^search_default_(.*)$/.exec(key);
- if (match) {
- search_defaults[match[1]] = value_;
- }
- });
- this.setup_search_view(search_defaults);
- } else { // "form"
- this.new_object();
- }
- },
- setup_search_view: function(search_defaults) {
- var self = this;
- if (this.searchview) {
- this.searchview.destroy();
- }
- var $buttons = this.$('.o-search-options');
- this.searchview = new instance.web.SearchView(this,
- this.dataset, false, search_defaults, {$buttons: $buttons});
- this.searchview.on('search_data', self, function(domains, contexts, groupbys) {
- if (self.initial_ids) {
- self.do_search(domains.concat([[["id", "in", self.initial_ids]], self.domain]),
- contexts.concat(self.context), groupbys);
- self.initial_ids = undefined;
- } else {
- self.do_search(domains.concat([self.domain]), contexts.concat(self.context), groupbys);
- }
- });
- this.searchview.appendTo(this.$(".o-popup-search")).done(function() {
- self.searchview.toggle_visibility(true);
- self.view_list = new instance.web.form.SelectCreateListView(self,
- self.dataset, false,
- _.extend({'deletable': false,
- 'selectable': !self.options.disable_multiple_selection,
- 'import_enabled': false,
- '$buttons': self.$buttonpane,
- 'disable_editable_mode': true,
- '$pager': self.$('.oe_popup_list_pager'),
- }, self.options.list_view_options || {}));
- self.view_list.on('edit:before', self, function (e) {
- e.cancel = true;
- });
- self.view_list.popup = self;
- self.view_list.appendTo(self.$(".oe_popup_list").show()).then(function() {
- self.view_list.do_show();
- }).then(function() {
- if (self.options.initial_facet) {
- self.searchview.query.reset([self.options.initial_facet], {
- preventSearch: true,
- });
- }
- self.searchview.do_search();
- });
- self.view_list.on("list_view_loaded", self, function() {
- self.$buttonpane.html(QWeb.render("SelectCreatePopup.search.buttons", {widget:self}));
- var $cbutton = self.$buttonpane.find(".oe_selectcreatepopup-search-close");
- $cbutton.click(function() {
- self.destroy();
- });
- var $sbutton = self.$buttonpane.find(".oe_selectcreatepopup-search-select");
- $sbutton.click(function() {
- self.select_elements(self.selected_ids);
- self.destroy();
- });
- $cbutton = self.$buttonpane.find(".oe_selectcreatepopup-search-create");
- $cbutton.click(function() {
- self.new_object();
- });
- });
- });
- },
- do_search: function(domains, contexts, groupbys) {
- var results = instance.web.pyeval.sync_eval_domains_and_contexts({
- domains: domains || [],
- contexts: contexts || [],
- group_by_seq: groupbys || []
- });
- this.view_list.do_search(results.domain, results.context, results.group_by);
- },
- on_click_element: function(ids) {
- var self = this;
- this.selected_ids = ids || [];
- if(this.selected_ids.length > 0) {
- self.$buttonpane.find(".oe_selectcreatepopup-search-select").removeAttr('disabled');
- } else {
- self.$buttonpane.find(".oe_selectcreatepopup-search-select").attr('disabled', "disabled");
- }
- },
- new_object: function() {
- if (this.searchview) {
- this.searchview.do_hide();
- }
- if (this.view_list) {
- this.view_list.do_hide();
- this.view_list.$el.parent().hide();
- }
- this.setup_form_view();
- },
-});
-
-instance.web.form.DomainEditorPopup = instance.web.form.SelectCreatePopup.extend({
- select_element: function (model, options, domain, context) {
- if (options.readonly) {
- return this._super(model, options, domain, context);
- }
- options.initial_facet = {
- category: _t("Custom Filter"),
- icon: 'fa-star',
- field: {
- get_context: function () { return context; },
- get_groupby: function () { return []; },
- get_domain: function () { return domain; },
- },
- values: [{label: _t("Selected domain"), value: null}],
- };
- this._super(model, options, [], context);
- },
- get_domain: function (selected_ids) {
- var group_domain = [],
- domain;
- if (this.$('input.oe_list_record_selector').prop('checked')) {
- if (this.view_list.grouped) {
- var group_domain = _.chain(_.values(this.view_list.groups.children))
- .filter(function (child) { return child.records.length; })
- .map(function (c) { return c.datagroup.domain;})
- .value();
- group_domain = _.flatten(group_domain, true);
- group_domain = _.times(group_domain.length - 1, _.constant('|')).concat(group_domain);
- }
- var search_data = this.searchview.build_search_data();
- domain = instance.web.pyeval.sync_eval_domains_and_contexts({
- domains: search_data.domains,
- contexts: search_data.contexts,
- group_by_seq: search_data.groupbys || []
- }).domain;
- }
- else {
- domain = [["id", "in", selected_ids]];
- }
- return this.dataset.domain.concat(group_domain).concat(domain || []);
- },
-});
-
-instance.web.form.SelectCreateListView = instance.web.ListView.extend({
- do_add_record: function () {
- this.popup.new_object();
- },
- select_record: function(index) {
- this.popup.select_elements([this.dataset.ids[index]]);
- this.popup.destroy();
- },
- do_select: function(ids, records) {
- this._super(ids, records);
- this.popup.on_click_element(ids);
- }
-});
-
-instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
- template: 'FieldReference',
- init: function(field_manager, node) {
- this._super(field_manager, node);
- this.reference_ready = true;
- },
- destroy_content: function() {
- if (this.fm) {
- this.fm.destroy();
- this.fm = undefined;
- }
- },
- initialize_content: function() {
- var self = this;
- var fm = new instance.web.form.DefaultFieldManager(this);
- this.fm = fm;
- fm.extend_field_desc({
- "selection": {
- selection: this.field_manager.get_field_desc(this.name).selection,
- type: "selection",
- },
- "m2o": {
- relation: null,
- type: "many2one",
- },
- });
- this.selection = new instance.web.form.FieldSelection(fm, { attrs: {
- name: 'selection',
- modifiers: JSON.stringify({readonly: this.get('effective_readonly')}),
- }});
- this.selection.on("change:value", this, this.on_selection_changed);
- this.selection.appendTo(this.$(".oe_form_view_reference_selection"));
- this.selection
- .on('focused', null, function () {self.trigger('focused');})
- .on('blurred', null, function () {self.trigger('blurred');});
-
- this.m2o = new instance.web.form.FieldMany2One(fm, { attrs: {
- name: 'Referenced Document',
- modifiers: JSON.stringify({readonly: this.get('effective_readonly')}),
- }});
- this.m2o.on("change:value", this, this.data_changed);
- this.m2o.appendTo(this.$(".oe_form_view_reference_m2o"));
- this.m2o
- .on('focused', null, function () {self.trigger('focused');})
- .on('blurred', null, function () {self.trigger('blurred');});
- },
- on_selection_changed: function() {
- if (this.reference_ready) {
- this.internal_set_value([this.selection.get_value(), false]);
- this.render_value();
- }
- },
- data_changed: function() {
- if (this.reference_ready) {
- this.internal_set_value([this.selection.get_value(), this.m2o.get_value()]);
- }
- },
- set_value: function(val) {
- if (val) {
- val = val.split(',');
- val[0] = val[0] || false;
- val[1] = val[0] ? (val[1] ? parseInt(val[1], 10) : val[1]) : false;
- }
- this._super(val || [false, false]);
- },
- get_value: function() {
- return this.get('value')[0] && this.get('value')[1] ? (this.get('value')[0] + ',' + this.get('value')[1]) : false;
- },
- render_value: function() {
- this.reference_ready = false;
- if (!this.get("effective_readonly")) {
- this.selection.set_value(this.get('value')[0]);
- }
- this.m2o.field.relation = this.get('value')[0];
- this.m2o.set_value(this.get('value')[1]);
- this.m2o.$el.toggle(!!this.get('value')[0]);
- this.reference_ready = true;
- },
-});
-
-instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
- init: function(field_manager, node) {
- var self = this;
- this._super(field_manager, node);
- this.binary_value = false;
- this.useFileAPI = !!window.FileReader;
- this.max_upload_size = 25 * 1024 * 1024; // 25Mo
- if (!this.useFileAPI) {
- this.fileupload_id = _.uniqueId('oe_fileupload');
- $(window).on(this.fileupload_id, function() {
- var args = [].slice.call(arguments).slice(1);
- self.on_file_uploaded.apply(self, args);
- });
- }
- },
- stop: function() {
- if (!this.useFileAPI) {
- $(window).off(this.fileupload_id);
- }
- this._super.apply(this, arguments);
- },
- initialize_content: function() {
- var self= this;
- this.$el.find('input.oe_form_binary_file').change(this.on_file_change);
- this.$el.find('button.oe_form_binary_file_save').click(this.on_save_as);
- this.$el.find('.oe_form_binary_file_clear').click(this.on_clear);
- this.$el.find('.oe_form_binary_file_edit').click(function(event){
- self.$el.find('input.oe_form_binary_file').click();
- });
- },
- on_file_change: function(e) {
- var self = this;
- var file_node = e.target;
- if ((this.useFileAPI && file_node.files.length) || (!this.useFileAPI && $(file_node).val() !== '')) {
- if (this.useFileAPI) {
- var file = file_node.files[0];
- if (file.size > this.max_upload_size) {
- var msg = _t("The selected file exceed the maximum file size of %s.");
- instance.webclient.notification.warn(_t("File upload"), _.str.sprintf(msg, instance.web.human_size(this.max_upload_size)));
- return false;
- }
- var filereader = new FileReader();
- filereader.readAsDataURL(file);
- filereader.onloadend = function(upload) {
- var data = upload.target.result;
- data = data.split(',')[1];
- self.on_file_uploaded(file.size, file.name, file.type, data);
- };
- } else {
- this.$el.find('form.oe_form_binary_form input[name=session_id]').val(this.session.session_id);
- this.$el.find('form.oe_form_binary_form').submit();
- }
- this.$el.find('.oe_form_binary_progress').show();
- this.$el.find('.oe_form_binary').hide();
- }
- },
- on_file_uploaded: function(size, name, content_type, file_base64) {
- if (size === false) {
- this.do_warn(_t("File Upload"), _t("There was a problem while uploading your file"));
- // TODO: use openerp web crashmanager
- console.warn("Error while uploading file : ", name);
- } else {
- this.filename = name;
- this.on_file_uploaded_and_valid.apply(this, arguments);
- }
- this.$el.find('.oe_form_binary_progress').hide();
- this.$el.find('.oe_form_binary').show();
- },
- on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
- },
- on_save_as: function(ev) {
- var value = this.get('value');
- if (!value) {
- this.do_warn(_t("Save As..."), _t("The field is empty, there's nothing to save !"));
- ev.stopPropagation();
- } else {
- instance.web.blockUI();
- var c = instance.webclient.crashmanager;
- var filename_fieldname = this.node.attrs.filename;
- var filename_field = this.view.fields && this.view.fields[filename_fieldname];
- this.session.get_file({
- url: '/web/binary/saveas_ajax',
- data: {data: JSON.stringify({
- model: this.view.dataset.model,
- id: (this.view.datarecord.id || ''),
- field: this.name,
- filename_field: (filename_fieldname || ''),
- data: instance.web.form.is_bin_size(value) ? null : value,
- filename: filename_field ? filename_field.get('value') : null,
- context: this.view.dataset.get_context()
- })},
- complete: instance.web.unblockUI,
- error: c.rpc_error.bind(c)
- });
- ev.stopPropagation();
- return false;
- }
- },
- set_filename: function(value) {
- var filename = this.node.attrs.filename;
- if (filename) {
- var tmp = {};
- tmp[filename] = value;
- this.field_manager.set_values(tmp);
- }
- },
- on_clear: function() {
- if (this.get('value') !== false) {
- this.binary_value = false;
- this.internal_set_value(false);
- }
- return false;
- }
-});
-
-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.$el.find('a').click(function(ev) {
- if (self.get('value')) {
- self.on_save_as(ev);
- }
- return false;
- });
- }
- },
- render_value: function() {
- var show_value;
- if (!this.get("effective_readonly")) {
- if (this.node.attrs.filename) {
- show_value = this.view.datarecord[this.node.attrs.filename] || '';
- } else {
- show_value = (this.get('value') !== null && this.get('value') !== undefined && this.get('value') !== false) ? this.get('value') : '';
- }
- this.$el.find('input').eq(0).val(show_value);
- } else {
- this.$el.find('a').toggle(!!this.get('value'));
- if (this.get('value')) {
- show_value = _t("Download");
- if (this.view)
- show_value += " " + (this.view.datarecord[this.node.attrs.filename] || '');
- this.$el.find('a').text(show_value);
- }
- }
- },
- on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
- this.binary_value = true;
- this.internal_set_value(file_base64);
- var show_value = name + " (" + instance.web.human_size(size) + ")";
- this.$el.find('input').eq(0).val(show_value);
- this.set_filename(name);
- },
- on_clear: function() {
- this._super.apply(this, arguments);
- this.$el.find('input').eq(0).val('');
- this.set_filename('');
- }
-});
-
-instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({
- template: 'FieldBinaryImage',
- placeholder: "/web/static/src/img/placeholder.png",
- render_value: function() {
- var self = this;
- var url;
- if (this.get('value') && !instance.web.form.is_bin_size(this.get('value'))) {
- url = 'data:image/png;base64,' + this.get('value');
- } else if (this.get('value')) {
- var id = JSON.stringify(this.view.datarecord.id || null);
- var field = this.name;
- if (this.options.preview_image)
- field = this.options.preview_image;
- url = this.session.url('/web/binary/image', {
- model: this.view.dataset.model,
- id: id,
- field: field,
- t: (new Date().getTime()),
- });
- } else {
- url = this.placeholder;
- }
- var $img = $(QWeb.render("FieldBinaryImage-img", { widget: this, url: url }));
- $($img).click(function(e) {
- if(self.view.get("actual_mode") == "view") {
- var $button = $(".oe_form_button_edit");
- $button.openerpBounce();
- e.stopPropagation();
- }
- });
- this.$el.find('> img').remove();
- this.$el.prepend($img);
- $img.load(function() {
- if (! self.options.size)
- return;
- $img.css("max-width", "" + self.options.size[0] + "px");
- $img.css("max-height", "" + self.options.size[1] + "px");
- });
- $img.on('error', function() {
- $img.attr('src', self.placeholder);
- instance.webclient.notification.warn(_t("Image"), _t("Could not display the selected image."));
- });
- },
- on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
- this.internal_set_value(file_base64);
- this.binary_value = true;
- this.render_value();
- this.set_filename(name);
- },
- on_clear: function() {
- this._super.apply(this, arguments);
- this.render_value();
- this.set_filename('');
- },
- set_value: function(value_){
- var changed = value_ !== this.get_value();
- this._super.apply(this, arguments);
- // By default, on binary images read, the server returns the binary size
- // This is possible that two images have the exact same size
- // Therefore we trigger the change in case the image value hasn't changed
- // So the image is re-rendered correctly
- if (!changed){
- this.trigger("change:value", this, {
- oldValue: value_,
- newValue: value_
- });
- }
- }
-});
-
-/**
- * Widget for (many2many field) to upload one or more file in same time and display in list.
- * The user can delete his files.
- * Options on attribute ; "blockui" {Boolean} block the UI or not
- * during the file is uploading
- */
-instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
- template: "FieldBinaryFileUploader",
- init: function(field_manager, node) {
- this._super(field_manager, node);
- this.field_manager = field_manager;
- this.node = node;
- if(this.field.type != "many2many" || this.field.relation != 'ir.attachment') {
- throw _.str.sprintf(_t("The type of the field '%s' must be a many2many field with a relation to 'ir.attachment' model."), this.field.string);
- }
- this.data = {};
- this.set_value([]);
- this.ds_file = new instance.web.DataSetSearch(this, 'ir.attachment');
- this.fileupload_id = _.uniqueId('oe_fileupload_temp');
- $(window).on(this.fileupload_id, _.bind(this.on_file_loaded, this));
- },
- initialize_content: function() {
- this.$el.on('change', 'input.oe_form_binary_file', this.on_file_change );
- },
- // WARNING: duplicated in 4 other M2M widgets
- set_value: function(value_) {
- value_ = value_ || [];
- if (value_.length >= 1 && value_[0] instanceof Array) {
- // value_ is a list of m2m commands. We only process
- // LINK_TO and REPLACE_WITH in this context
- var val = [];
- _.each(value_, function (command) {
- if (command[0] === commands.LINK_TO) {
- val.push(command[1]); // (4, id[, _])
- } else if (command[0] === commands.REPLACE_WITH) {
- val = command[2]; // (6, _, ids)
- }
- });
- value_ = val;
- }
- this._super(value_);
- },
- get_value: function() {
- var tmp = [commands.replace_with(this.get("value"))];
- return tmp;
- },
- get_file_url: function (attachment) {
- return this.session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'datas_fname', id: attachment['id']});
- },
- read_name_values : function () {
- var self = this;
- // don't reset know values
- var ids = this.get('value');
- var _value = _.filter(ids, function (id) { return typeof self.data[id] == 'undefined'; } );
- // send request for get_name
- if (_value.length) {
- return this.ds_file.call('read', [_value, ['id', 'name', 'datas_fname']]).then(function (datas) {
- _.each(datas, function (data) {
- data.no_unlink = true;
- data.url = self.session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'datas_fname', id: data.id});
- self.data[data.id] = data;
- });
- return ids;
- });
- } else {
- return $.when(ids);
- }
- },
- render_value: function () {
- var self = this;
- this.read_name_values().then(function (ids) {
- var render = $(instance.web.qweb.render('FieldBinaryFileUploader.files', {'widget': self, 'values': ids}));
- render.on('click', '.oe_delete', _.bind(self.on_file_delete, self));
- self.$('.oe_placeholder_files, .oe_attachments').replaceWith( render );
-
- // reinit input type file
- var $input = self.$('input.oe_form_binary_file');
- $input.after($input.clone(true)).remove();
- self.$(".oe_fileupload").show();
-
- });
- },
- on_file_change: function (event) {
- event.stopPropagation();
- var self = this;
- var $target = $(event.target);
- if ($target.val() !== '') {
- var filename = $target.val().replace(/.*[\\\/]/,'');
- // don't uplode more of one file in same time
- if (self.data[0] && self.data[0].upload ) {
- return false;
- }
- for (var id in this.get('value')) {
- // if the files exits, delete the file before upload (if it's a new file)
- if (self.data[id] && (self.data[id].filename || self.data[id].name) == filename && !self.data[id].no_unlink ) {
- self.ds_file.unlink([id]);
- }
- }
-
- // block UI or not
- if(this.node.attrs.blockui>0) {
- instance.web.blockUI();
- }
-
- // TODO : unactivate send on wizard and form
-
- // submit file
- this.$('form.oe_form_binary_form').submit();
- this.$(".oe_fileupload").hide();
- // add file on data result
- this.data[0] = {
- 'id': 0,
- 'name': filename,
- 'filename': filename,
- 'url': '',
- 'upload': true
- };
- }
- },
- on_file_loaded: function (event, result) {
- var files = this.get('value');
-
- // unblock UI
- if(this.node.attrs.blockui>0) {
- instance.web.unblockUI();
- }
-
- if (result.error || !result.id ) {
- this.do_warn( _t('Uploading Error'), result.error);
- delete this.data[0];
- } else {
- if (this.data[0] && this.data[0].filename == result.filename && this.data[0].upload) {
- delete this.data[0];
- this.data[result.id] = {
- 'id': result.id,
- 'name': result.name,
- 'filename': result.filename,
- 'url': this.get_file_url(result)
- };
- } else {
- this.data[result.id] = {
- 'id': result.id,
- 'name': result.name,
- 'filename': result.filename,
- 'url': this.get_file_url(result)
- };
- }
- var values = _.clone(this.get('value'));
- values.push(result.id);
- this.set({'value': values});
- }
- this.render_value();
- },
- on_file_delete: function (event) {
- event.stopPropagation();
- var file_id=$(event.target).data("id");
- if (file_id) {
- var files = _.filter(this.get('value'), function (id) {return id != file_id;});
- if(!this.data[file_id].no_unlink) {
- this.ds_file.unlink([file_id]);
- }
- this.set({'value': files});
- }
- },
-});
-
-instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({
- template: "FieldStatus",
- init: function(field_manager, node) {
- this._super(field_manager, node);
- this.options.clickable = this.options.clickable || (this.node.attrs || {}).clickable || false;
- this.options.visible = this.options.visible || (this.node.attrs || {}).statusbar_visible || false;
- this.set({value: false});
- this.selection = {'unfolded': [], 'folded': []};
- this.set("selection", {'unfolded': [], 'folded': []});
- this.selection_dm = new instance.web.DropMisordered();
- this.dataset = new instance.web.DataSetStatic(this, this.field.relation, this.build_context());
- },
- start: function() {
- this.field_manager.on("view_content_has_changed", this, this.calc_domain);
- this.calc_domain();
- this.on("change:value", this, this.get_selection);
- this.on("change:evaluated_selection_domain", this, this.get_selection);
- this.on("change:selection", this, function() {
- this.selection = this.get("selection");
- this.render_value();
- });
- this.get_selection();
- if (this.options.clickable) {
- this.$el.on('click','li[data-id]',this.on_click_stage);
- }
- if (this.$el.parent().is('header')) {
- this.$el.after('<div class="oe_clear"/>');
- }
- this._super();
- },
- set_value: function(value_) {
- if (value_ instanceof Array) {
- value_ = value_[0];
- }
- this._super(value_);
- },
- render_value: function() {
- var self = this;
- var content = QWeb.render("FieldStatus.content", {
- 'widget': self,
- 'value_folded': _.find(self.selection.folded, function(i){return i[0] === self.get('value');})
- });
- self.$el.html(content);
- },
- calc_domain: function() {
- var d = instance.web.pyeval.eval('domain', this.build_domain());
- var domain = []; //if there is no domain defined, fetch all the records
-
- if (d.length) {
- domain = ['|',['id', '=', this.get('value')]].concat(d);
- }
-
- if (! _.isEqual(domain, this.get("evaluated_selection_domain"))) {
- this.set("evaluated_selection_domain", domain);
- }
- },
- /** Get the selection and render it
- * selection: [[identifier, value_to_display], ...]
- * For selection fields: this is directly given by this.field.selection
- * For many2one fields: perform a search on the relation of the many2one field
- */
- get_selection: function() {
- var self = this;
- var selection_unfolded = [];
- var selection_folded = [];
- var fold_field = this.options.fold_field;
-
- var calculation = _.bind(function() {
- if (this.field.type == "many2one") {
- return self.get_distant_fields().then(function (fields) {
- return new instance.web.DataSetSearch(self, self.field.relation, self.build_context(), self.get("evaluated_selection_domain"))
- .read_slice(_.union(_.keys(self.distant_fields), ['id']), {}).then(function (records) {
- var ids = _.pluck(records, 'id');
- return self.dataset.name_get(ids).then(function (records_name) {
- _.each(records, function (record) {
- var name = _.find(records_name, function (val) {return val[0] == record.id;})[1];
- if (fold_field && record[fold_field] && record.id != self.get('value')) {
- selection_folded.push([record.id, name]);
- } else {
- selection_unfolded.push([record.id, name]);
- }
- });
- });
- });
- });
- } else {
- // For field type selection filter values according to
- // statusbar_visible attribute of the field. For example:
- // statusbar_visible="draft,open".
- var select = this.field.selection;
- for(var i=0; i < select.length; i++) {
- var key = select[i][0];
- if(key == this.get('value') || !this.options.visible || this.options.visible.indexOf(key) != -1) {
- selection_unfolded.push(select[i]);
- }
- }
- return $.when();
- }
- }, this);
- this.selection_dm.add(calculation()).then(function () {
- var selection = {'unfolded': selection_unfolded, 'folded': selection_folded};
- if (! _.isEqual(selection, self.get("selection"))) {
- self.set("selection", selection);
- }
- });
- },
- /*
- * :deprecated: this feature will probably be removed with OpenERP v8
- */
- get_distant_fields: function() {
- var self = this;
- if (! this.options.fold_field) {
- this.distant_fields = {}
- }
- if (this.distant_fields) {
- return $.when(this.distant_fields);
- }
- return new instance.web.Model(self.field.relation).call("fields_get", [[this.options.fold_field]]).then(function(fields) {
- self.distant_fields = fields;
- return fields;
- });
- },
- on_click_stage: function (ev) {
- var self = this;
- var $li = $(ev.currentTarget);
- var val;
- if (this.field.type == "many2one") {
- val = parseInt($li.data("id"), 10);
- }
- else {
- val = $li.data("id");
- }
- if (val != self.get('value')) {
- this.view.recursive_save().done(function() {
- var change = {};
- change[self.name] = val;
- self.view.dataset.write(self.view.datarecord.id, change).done(function() {
- self.view.reload();
- });
- });
- }
- },
-});
-
-instance.web.form.FieldMonetary = instance.web.form.FieldFloat.extend({
- template: "FieldMonetary",
- widget_class: 'oe_form_field_float oe_form_field_monetary',
- init: function() {
- this._super.apply(this, arguments);
- this.set({"currency": false});
- if (this.options.currency_field) {
- this.field_manager.on("field_changed:" + this.options.currency_field, this, function() {
- this.set({"currency": this.field_manager.get_field_value(this.options.currency_field)});
- });
- }
- this.on("change:currency", this, this.get_currency_info);
- this.get_currency_info();
- this.ci_dm = new instance.web.DropMisordered();
- },
- start: function() {
- var tmp = this._super();
- this.on("change:currency_info", this, this.reinitialize);
- return tmp;
- },
- get_currency_info: function() {
- var self = this;
- if (this.get("currency") === false) {
- this.set({"currency_info": null});
- return;
- }
- return this.ci_dm.add(self.alive(new instance.web.Model("res.currency").query(["symbol", "position"])
- .filter([["id", "=", self.get("currency")]]).first())).then(function(res) {
- self.set({"currency_info": res});
- });
- },
- parse_value: function(val, def) {
- return instance.web.parse_value(val, {type: "float", digits: (this.node.attrs || {}).digits || this.field.digits}, def);
- },
- format_value: function(val, def) {
- return instance.web.format_value(val, {type: "float", digits: (this.node.attrs || {}).digits || this.field.digits}, def);
- },
-});
-
-/*
- This type of field display a list of checkboxes. It works only with m2ms. This field will display one checkbox for each
- record existing in the model targeted by the relation, according to the given domain if one is specified. Checked records
- will be added to the relation.
-*/
-instance.web.form.FieldMany2ManyCheckBoxes = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
- className: "oe_form_many2many_checkboxes",
- init: function() {
- this._super.apply(this, arguments);
- this.set("value", {});
- this.set("records", []);
- this.field_manager.on("view_content_has_changed", this, function() {
- var domain = new openerp.web.CompoundDomain(this.build_domain()).eval();
- if (! _.isEqual(domain, this.get("domain"))) {
- this.set("domain", domain);
- }
- });
- this.records_orderer = new instance.web.DropMisordered();
- },
- initialize_field: function() {
- instance.web.form.ReinitializeFieldMixin.initialize_field.call(this);
- this.on("change:domain", this, this.query_records);
- this.set("domain", new openerp.web.CompoundDomain(this.build_domain()).eval());
- this.on("change:records", this, this.render_value);
- },
- query_records: function() {
- var self = this;
- var model = new openerp.Model(openerp.session, this.field.relation);
- this.records_orderer.add(model.call("search", [this.get("domain")], {"context": this.build_context()}).then(function(record_ids) {
- return model.call("name_get", [record_ids] , {"context": self.build_context()});
- })).then(function(res) {
- self.set("records", res);
- });
- },
- render_value: function() {
- this.$().html(QWeb.render("FieldMany2ManyCheckBoxes", {widget: this, selected: this.get("value")}));
- var inputs = this.$("input");
- inputs.change(_.bind(this.from_dom, this));
- if (this.get("effective_readonly"))
- inputs.attr("disabled", "true");
- },
- from_dom: function() {
- var new_value = {};
- this.$("input").each(function() {
- var elem = $(this);
- new_value[elem.data("record-id")] = elem.is(':checked') ? true : undefined;
- });
- if (! _.isEqual(new_value, this.get("value")))
- this.internal_set_value(new_value);
- },
- // WARNING: (mostly) duplicated in 4 other M2M widgets
- set_value: function(value_) {
- value_ = value_ || [];
- if (value_.length >= 1 && value_[0] instanceof Array) {
- // value_ is a list of m2m commands. We only process
- // LINK_TO and REPLACE_WITH in this context
- var val = [];
- _.each(value_, function (command) {
- if (command[0] === commands.LINK_TO) {
- val.push(command[1]); // (4, id[, _])
- } else if (command[0] === commands.REPLACE_WITH) {
- val = command[2]; // (6, _, ids)
- }
- });
- value_ = val;
- }
- var formatted = {};
- _.each(value_, function(el) {
- formatted[JSON.stringify(el)] = true;
- });
- this._super(formatted);
- },
- get_value: function() {
- var value = _.filter(_.keys(this.get("value")), function(el) {
- return this.get("value")[el];
- }, this);
- value = _.map(value, function(el) {
- return JSON.parse(el);
- });
- return [commands.replace_with(value)];
- },
-});
-
-/**
- This field can be applied on many2many and one2many. It is a read-only field that will display a single link whose name is
- "<number of linked records> <label of the field>". When the link is clicked, it will redirect to another act_window
- action on the model of the relation and show only the linked records.
-
- Widget options:
-
- * views: The views to display in the act_window action. Must be a list of tuples whose first element is the id of the view
- to display (or False to take the default one) and the second element is the type of the view. Defaults to
- [[false, "tree"], [false, "form"]] .
-*/
-instance.web.form.X2ManyCounter = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
- className: "oe_form_x2many_counter",
- init: function() {
- this._super.apply(this, arguments);
- this.set("value", []);
- _.defaults(this.options, {
- "views": [[false, "tree"], [false, "form"]],
- });
- },
- render_value: function() {
- var text = _.str.sprintf("%d %s", this.val().length, this.string);
- this.$().html(QWeb.render("X2ManyCounter", {text: text}));
- this.$("a").click(_.bind(this.go_to, this));
- },
- go_to: function() {
- return this.view.recursive_save().then(_.bind(function() {
- var val = this.val();
- var context = {};
- if (this.field.type === "one2many") {
- context["default_" + this.field.relation_field] = this.view.datarecord.id;
- }
- var domain = [["id", "in", val]];
- return this.do_action({
- type: 'ir.actions.act_window',
- name: this.string,
- res_model: this.field.relation,
- views: this.options.views,
- target: 'current',
- context: context,
- domain: domain,
- });
- }, this));
- },
- val: function() {
- var value = this.get("value") || [];
- if (value.length >= 1 && value[0] instanceof Array) {
- value = value[0][2];
- }
- return value;
- }
-});
-
-/**
- This widget is intended to be used on stat button numeric fields. It will display
- the value many2many and one2many. It is a read-only field that will
- display a simple string "<value of field> <label of the field>"
-*/
-instance.web.form.StatInfo = instance.web.form.AbstractField.extend({
- is_field_number: true,
- init: function() {
- this._super.apply(this, arguments);
- this.internal_set_value(0);
- },
- set_value: function(value_) {
- if (value_ === false || value_ === undefined) {
- value_ = 0;
- }
- this._super.apply(this, [value_]);
- },
- render_value: function() {
- var options = {
- value: this.get("value") || 0,
- };
- if (! this.node.attrs.nolabel) {
- if(this.options.label_field && this.view.datarecord[this.options.label_field]) {
- options.text = this.view.datarecord[this.options.label_field];
- }
- else {
- options.text = this.string;
- }
- }
- this.$el.html(QWeb.render("StatInfo", options));
- },
-
-});
-
-/**
- This widget is intended to be used on boolean fields. It toggles a button
- switching between a green bullet / gray bullet.
-*/
-instance.web.form.FieldToggleBoolean = instance.web.form.AbstractField.extend({
- render_value: function () {
- this.icon = this.get_value() ? 'gtk-yes.png' : 'gtk-normal.png';
- this.$el.html(QWeb.render("FieldToggleBoolean", {'widget': this}));
- this.$('.oe_toggle_button').on('click', this.set_toggle_button.bind(this));
- },
- set_toggle_button: function () {
- var self = this;
- var toggle_value = this.get_value() === false ? true: false;
- if (this.view.get('actual_mode') == 'view') {
- var rec_values = {};
- rec_values[self.node.attrs.name] = toggle_value;
- return this.view.dataset._model.call(
- 'write', [
- [this.view.datarecord.id],
- rec_values,
- self.view.dataset.get_context()
- ]).done(function () { self.reload_record(); });
- }
- else {
- this.set_value(toggle_value);
- }
- },
- reload_record: function () {
- this.view.reload();
- },
-});
-
-/**
- * Registry of form fields, called by :js:`instance.web.FormView`.
- *
- * All referenced classes must implement FieldInterface. Those represent the classes whose instances
- * will substitute to the <field> tags as defined in OpenERP's views.
- */
-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',
- 'html' : 'instance.web.form.FieldTextHtml',
- 'char_domain': 'instance.web.form.FieldCharDomain',
- 'date' : 'instance.web.form.FieldDate',
- 'datetime' : 'instance.web.form.FieldDatetime',
- 'selection' : 'instance.web.form.FieldSelection',
- 'radio' : 'instance.web.form.FieldRadio',
- 'many2one' : 'instance.web.form.FieldMany2One',
- 'many2onebutton' : 'instance.web.form.Many2OneButton',
- 'many2many' : 'instance.web.form.FieldMany2Many',
- 'many2many_tags' : 'instance.web.form.FieldMany2ManyTags',
- 'many2many_kanban' : 'instance.web.form.FieldMany2ManyKanban',
- 'one2many' : 'instance.web.form.FieldOne2Many',
- 'one2many_list' : 'instance.web.form.FieldOne2Many',
- 'reference' : 'instance.web.form.FieldReference',
- 'boolean' : 'instance.web.form.FieldBoolean',
- 'toggle_button' : 'instance.web.form.FieldToggleBoolean',
- 'float' : 'instance.web.form.FieldFloat',
- 'percentpie': 'instance.web.form.FieldPercentPie',
- 'barchart': 'instance.web.form.FieldBarChart',
- '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',
- 'many2many_binary': 'instance.web.form.FieldMany2ManyBinaryMultiFiles',
- 'statusbar': 'instance.web.form.FieldStatus',
- 'monetary': 'instance.web.form.FieldMonetary',
- 'many2many_checkboxes': 'instance.web.form.FieldMany2ManyCheckBoxes',
- 'x2many_counter': 'instance.web.form.X2ManyCounter',
- 'priority':'instance.web.form.Priority',
- 'kanban_state_selection':'instance.web.form.KanbanSelection',
- 'statinfo': 'instance.web.form.StatInfo',
-});
-
-/**
- * Registry of widgets usable in the form view that can substitute to any possible
- * tags defined in OpenERP's form views.
- *
- * Every referenced class should extend FormWidget.
- */
-instance.web.form.tags = new instance.web.Registry({
- 'button' : 'instance.web.form.WidgetButton',
-});
-
-instance.web.form.custom_widgets = new instance.web.Registry({
-});
-
-})();