odoo.define('web.form_widgets', function (require) { "use strict"; var ajax = require('web.ajax'); var core = require('web.core'); var crash_manager = require('web.crash_manager'); var data = require('web.data'); var datepicker = require('web.datepicker'); var dom_utils = require('web.dom_utils'); var Priority = require('web.Priority'); var ProgressBar = require('web.ProgressBar'); var Dialog = require('web.Dialog'); var common = require('web.form_common'); var formats = require('web.formats'); var framework = require('web.framework'); var Model = require('web.DataModel'); var pyeval = require('web.pyeval'); var session = require('web.session'); var utils = require('web.utils'); var _t = core._t; var QWeb = core.qweb; var WidgetButton = common.FormWidget.extend({ template: 'WidgetButton', init: function(field_manager, node) { node.attrs.type = node.attrs['data-button-type']; 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) { this.fa_icon = this.node.attrs.icon.indexOf('fa-') === 0; } }, 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 || session.debug) { this.do_attach_tooltip(); } this.setupFocus(this.$el); }, on_click: function() { var self = this; if (this.view.is_disabled) { return; } this.force_disabled = true; this.check_disable(); this.view.disable_button(); this.execute_action().always(function() { self.view.enable_button(); self.force_disabled = false; self.check_disable(); if (self.$el.hasClass('o_wow')) { self.show_wow(); } }); }, execute_action: function() { var self = this; var exec_action = function() { if (self.node.attrs.confirm) { var def = $.Deferred(); Dialog.confirm(self, self.node.attrs.confirm, { confirm_callback: self.on_confirmed }) .on("closed", 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(); } }).fail(function () { 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' : ''); }, show_wow: function() { var class_to_add = 'o_wow_thumbs'; if (Math.random() > 0.9) { var other_classes = ['o_wow_peace', 'o_wow_heart']; class_to_add = other_classes[Math.floor(Math.random()*other_classes.length)]; } var $body = $('body'); $body.addClass(class_to_add); setTimeout(function() { $body.removeClass(class_to_add); }, 1000); } }); var FieldChar = common.AbstractField.extend(common.ReinitializeFieldMixin, { template: 'FieldChar', events: { 'change': '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() { if(!this.get('effective_readonly') && !this.$input) { this.$input = this.$el; } this.setupFocus(this.$el); }, destroy_content: function() { this.$input = undefined; }, store_dom_value: function () { if (this.$input && 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.$input) { this.$input.val(show_value); } else { if (this.password) { show_value = new Array(show_value.length + 1).join('*'); } this.$el.text(show_value); } }, is_syntax_valid: function() { if (this.$input) { try { this.parse_value(this.$input.val(), ''); } catch(e) { return false; } } return true; }, parse_value: function(val, def) { return formats.parse_value(val, this, def); }, format_value: function(val, def) { return formats.format_value(val, this, def); }, is_false: function() { return this.get('value') === '' || this._super(); }, focus: function() { if (this.$input) { return this.$input.focus(); } return false; }, }); var KanbanSelection = common.AbstractField.extend({ template: "FormSelection", events: { 'click a': function(e) { e.preventDefault(); }, 'mouseup a': function(e) { e.stopPropagation(); }, 'click li': 'set_kanban_selection' }, start: function () { // hook on form view content changed: recompute the states, because it may be related to the current stage this.view.on('view_content_has_changed', this, function () { this.render_value(); }); return this._super(); }, prepare_dropdown_selection: function() { var self = this; var _data = []; var current_stage_id = self.view.datarecord.stage_id && self.view.datarecord.stage_id[0]; var stage_data = { id: current_stage_id, legend_normal: self.view.datarecord.legend_normal || undefined, legend_blocked : self.view.datarecord.legend_blocked || undefined, legend_done: self.view.datarecord.legend_done || undefined, }; _.map(self.field.selection || [], function(selection_item) { var value = { 'name': selection_item[0], 'tooltip': selection_item[1], }; if (selection_item[0] === 'normal') { value.state_name = stage_data.legend_normal ? stage_data.legend_normal : selection_item[1]; } else if (selection_item[0] === 'done') { value.state_class = 'oe_kanban_status_green'; value.state_name = stage_data.legend_done ? stage_data.legend_done : selection_item[1]; } else { value.state_class = 'oe_kanban_status_red'; value.state_name = stage_data.legend_blocked ? stage_data.legend_blocked : selection_item[1]; } _data.push(value); }); return _data; }, render_value: function() { this._super(); this.states = this.prepare_dropdown_selection(); var self = this; // Adapt "FormSelection" var current_state = _.find(this.states, function(state) { return state.name === self.get('value'); }); this.$('.oe_kanban_status') .removeClass('oe_kanban_status_red oe_kanban_status_green') .addClass(current_state.state_class); // Render "FormSelection.Items" and move it into "FormSelection" var $items = $(QWeb.render('FormSelection.items', { states: _.without(this.states, current_state) })); var $dropdown = this.$el.find('.dropdown-menu'); $dropdown.children().remove(); // remove old items $items.appendTo($dropdown); }, /* 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; ev.preventDefault(); 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(); }, }); var FieldPriority = common.AbstractField.extend({ events: { 'mouseup': function(e) { e.stopPropagation(); }, }, start: function() { this.priority = new Priority(this, { readonly: this.get('readonly'), value: this.get('value'), values: this.field.selection || [], }); this.priority.on('update', this, function(update) { /* 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. */ var view = this.view; if(view.get('actual_mode') === 'view') { var write_values = {}; write_values[this.name] = update.value; view.dataset._model.call('write', [ [view.datarecord.id], write_values, view.dataset.get_context() ]).done(function() { view.reload(); }); } else { this.set_value(update.value); } }); this.on('change:readonly', this, function() { this.priority.readonly = this.get('readonly'); var $div = $('
').insertAfter(this.$el); this.priority.replace($div); this.setElement(this.priority.$el); }); var self = this; return $.when(this._super(), this.priority.appendTo('
').then(function() { self.priority.$el.addClass(self.$el.attr('class')); self.replaceElement(self.priority.$el); })); }, render_value: function() { this.priority.set_value(this.get('value')); }, }); var FieldID = FieldChar.extend({ process_modifiers: function () { this._super(); this.set({ readonly: true }); }, }); var FieldEmail = FieldChar.extend({ template: 'FieldEmail', prefix: 'mailto', init: function() { this._super.apply(this, arguments); this.clickable = true; }, render_value: function() { this._super(); if (this.get("effective_readonly") && this.clickable) { this.$el.attr('href', this.prefix + ':' + this.get('value')); } } }); var FieldUrl = FieldEmail.extend({ render_value: function() { this._super(); if(this.get("effective_readonly")) { 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.attr('href', tmp).text(text); } } }); var FieldFloat = FieldChar.extend({ 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; } }, initialize_content: function() { this._super(); this.$el.addClass('o_form_field_number'); }, set_value: function(value_) { if (value_ === false || value_ === undefined) { value_ = 0; // As in GTK client, floats default to 0 } if (this.digits !== undefined && this.digits.length === 2) { value_ = utils.round_decimals(value_, this.digits[1]); } this._super(value_); } }); var FieldCharDomain = common.AbstractField.extend(common.ReinitializeFieldMixin, { template: "FieldCharDomain", events: { 'click button': 'on_click', 'change .o_debug_input': function(e) { this.set('value', $(e.target).val()); } }, init: function() { this._super.apply(this, arguments); this.debug = session.debug; }, start: function() { var self = this; var tmp = this._super(); if (this.options.model_field){ this.field_manager.fields[this.options.model_field].on("change:value", this, function(){ if (self.view && self.view.record_loaded.state == "resolved" && self.view.onchanges_mutex){ self.view.onchanges_mutex.def.then(function(){ self.render_value(); }); } }); } return tmp; }, render_value: function() { var self = this; if (this.get('value')) { var model = this.options.model || this.field_manager.get_field_value(this.options.model_field); try{ var domain = pyeval.eval('domain', this.get('value')); } catch(e){ this.do_warn(_t('Error: Bad domain'), _t('The domain is wrong.')); return; } var ds = new data.DataSetStatic(self, model, self.build_context()); ds.call('search_count', [domain, ds.get_context()]).then(function (results) { self.$('.o_count').text(results + ' ' + _t(' selected records')); if (self.get('effective_readonly')) { self.$('button').text(_t('See selection ')); } else { self.$('button').text(_t('Change selection ')); } self.$('button').append($("").addClass('fa fa-arrow-right')); }); if(this.debug) { this.$('.o_debug_input').val(this.get('value')); } } else { this.$('.o_form_input').val(''); this.$('.o_count').text(_t('No selected record')); var $arrow = this.$('button span').detach(); this.$('button').text(_t('Select records ')).append($("").addClass('fa fa-arrow-right')); } }, on_click: function(event) { event.preventDefault(); var self = this; var dialog = new common.DomainEditorDialog(this, { res_model: this.options.model || this.field_manager.get_field_value(this.options.model_field), default_domain: this.get('value'), title: this.get('effective_readonly') ? _t('Selected records') : _t('Select records...'), readonly: this.get('effective_readonly'), disable_multiple_selection: this.get('effective_readonly'), no_create: this.get('effective_readonly'), on_selected: function(selected_ids) { if (!self.get('effective_readonly')) { self.set_value(dialog.get_domain(selected_ids)); } } }).open(); }, }); var FieldDate = common.AbstractField.extend(common.ReinitializeFieldMixin, { tagName: "span", className: "o_form_field_date", build_widget: function() { return new datepicker.DateWidget(this); }, initialize_content: function() { if (this.datewidget) { this.datewidget.destroy(); this.datewidget = undefined; } if (!this.get("effective_readonly")) { this.datewidget = this.build_widget(); this.datewidget.on('datetime_changed', this, function() { this.internal_set_value(this.datewidget.get_value()); }); var self = this; this.datewidget.appendTo('
').done(function() { self.datewidget.$el.addClass(self.$el.attr('class')); self.replaceElement(self.datewidget.$el); self.datewidget.$input.addClass('o_form_input'); self.setupFocus(self.datewidget.$input); }); } }, render_value: function() { if (this.get("effective_readonly")) { this.$el.text(formats.format_value(this.get('value'), this, '')); } else { this.datewidget.set_value(this.get('value')); } }, is_syntax_valid: function() { return this.get("effective_readonly") || !this.datewidget || this.datewidget.is_valid(); }, is_false: function() { return this.get('value') === '' || this._super(); }, focus: function() { if (!this.get("effective_readonly")) { return this.datewidget.$input.focus(); } return false; }, }); var FieldDatetime = FieldDate.extend({ build_widget: function() { return new datepicker.DateTimeWidget(this); }, }); var FieldText = common.AbstractField.extend(common.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': 'store_dom_value', }, initialize_content: function() { if (!this.get("effective_readonly")) { this.auto_sized = false; this.setupFocus(this.$el); } }, commit_value: function () { if (!this.get("effective_readonly")) { this.store_dom_value(); } return this._super(); }, store_dom_value: function () { this.internal_set_value(formats.parse_value(this.$el.val(), this)); }, render_value: function() { if (this.get("effective_readonly")) { var txt = this.get("value") || ''; this.$el.text(txt); } else { var show_value = formats.format_value(this.get('value'), this, ''); this.$el.val(show_value); dom_utils.autoresize(this.$el, {parent: this}); } }, is_syntax_valid: function() { if (!this.get("effective_readonly")) { try { formats.parse_value(this.$el.val(), this, ''); } catch(e) { return false; } } return true; }, is_false: function() { return this.get('value') === '' || this._super(); }, focus: function($el) { if(!this.get("effective_readonly")) { return this.$el.focus(); } return false; }, set_dimensions: function(height, width) { this.$el.css({ width: width, minHeight: height, }); }, }); var FieldBoolean = common.AbstractField.extend({ template: 'FieldBoolean', events: { 'click': function() { this.internal_set_value(this.$checkbox.prop('checked')); } }, start: function() { this.$checkbox = this.$('input'); this.$checkbox.prop('disabled', this.get("effective_readonly")); this.on("change:effective_readonly", this, function() { this.$checkbox.prop('disabled', this.get("effective_readonly")); }); this.setupFocus(this.$checkbox); return this._super(); }, render_value: function() { this.$checkbox.prop('checked', this.get('value')); }, focus: function() { return this.$checkbox.focus(); }, set_dimensions: function(height, width) {}, // Checkboxes have a fixed height and width (even in list editable) is_false: function() { return false; }, }); /** This widget is intended to be used on stat button boolean fields. It is a read-only field that will display a simple string "