odoo.define('web.form_widgets', function (require) { "use strict"; var core = require('web.core'); var crash_manager = require('web.crash_manager'); var data = require('web.data'); var datepicker = require('web.datepicker'); 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 Priority = require('web.Priority'); 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) { // if the icon isn't a font-awesome one, find it in the icons folder this.fa_icon = this.node.attrs.icon.indexOf('fa-') === 0; if (!this.fa_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 || 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(); 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(); } }); }, 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', 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 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() { 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 }); } }); var KanbanSelection = FieldChar.extend({ template: "FormSelection", 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 current_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); this.$el.find('a').on('click', this.set_kanban_selection.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_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', 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'); } } }); var FieldUrl = 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); } } }); var FieldFloat = 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_ = utils.round_decimals(value_, this.digits[1]); } this._super.apply(this, [value_]); }, focus: function () { var $input = this.$('input:first'); return $input.length ? $input.select() : false; } }); 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; }, 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); var domain = pyeval.eval('domain', this.get('value')); var ds = new data.DataSetStatic(self, model, self.build_context()); ds.call('search_count', [domain]).then(function (results) { self.$('.o_count').text(results + ' selected records'); if (self.get('effective_readonly')) { self.$('button').text('See selection '); } else { self.$('button').text('Change selection '); } self.$('button').append($("").addClass('fa fa-arrow-right')); }); if(this.debug) { this.$('.o_debug_input').val(this.get('value')); } } else { this.$('.o_count').text('No selected record'); var $arrow = this.$('button span').detach(); this.$('button').text('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') ? 'Selected records' : '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 FieldDatetime = common.AbstractField.extend(common.ReinitializeFieldMixin, { template: "FieldDatetime", build_widget: function() { return new datepicker.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(formats.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); } } }); var FieldDate = FieldDatetime.extend({ template: "FieldDate", build_widget: function() { return new datepicker.DateWidget(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 textarea': 'store_dom_value', }, initialize_content: function() { 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(formats.parse_value(this.$textarea.val(), this)); }, render_value: function() { if (! this.get("effective_readonly")) { var show_value = formats.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; autosize(this.$textarea); } 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 { formats.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 }); } }, }); var FieldBoolean = common.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($('
')); }); } }); /** This widget is intended to be used on stat button boolean fields. It is a read-only field that will display a simple string "