From 920df597d3d7174723736a395e94dc8ff413c70b Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Wed, 29 Mar 2017 17:25:18 +0200 Subject: [PATCH] [REF] barcode, *: update code to new JS framework With the new views, the barcode mechanisms were completely broken, because the view and widget JS code was totally changed. This commit update and refactor all that code to make it work with the new design philosophy. Also, it update the coding style. --- .../js/reconciliation/reconciliation_model.js | 2 +- .../barcodes/static/src/js/barcode_events.js | 2 +- .../barcodes/static/src/js/barcode_field.js | 107 ++++++ .../static/src/js/barcode_form_view.js | 340 ++++++++++++++++++ .../static/src/js/barcode_handler_mixin.js | 52 --- .../static/src/js/field_float_scannable.js | 59 --- .../src/js/form_view_barcode_handler.js | 205 ----------- .../src/js/inherited_form_widget_button.js | 34 -- addons/barcodes/views/barcodes_templates.xml | 6 +- .../static/src/js/greeting_message.js | 18 +- .../hr_attendance/static/src/js/kiosk_mode.js | 14 +- addons/web/static/src/js/core/mixins.js | 8 +- .../static/src/js/services/data_manager.js | 2 +- .../static/src/js/views/basic/basic_model.js | 8 +- addons/web/static/src/xml/base.xml | 4 +- 15 files changed, 479 insertions(+), 382 deletions(-) create mode 100644 addons/barcodes/static/src/js/barcode_field.js create mode 100644 addons/barcodes/static/src/js/barcode_form_view.js delete mode 100644 addons/barcodes/static/src/js/barcode_handler_mixin.js delete mode 100644 addons/barcodes/static/src/js/field_float_scannable.js delete mode 100644 addons/barcodes/static/src/js/form_view_barcode_handler.js delete mode 100644 addons/barcodes/static/src/js/inherited_form_widget_button.js diff --git a/addons/account/static/src/js/reconciliation/reconciliation_model.js b/addons/account/static/src/js/reconciliation/reconciliation_model.js index 5085b757f20..791d94e515d 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_model.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_model.js @@ -1011,7 +1011,7 @@ var ManualModel = StatementModel.extend({ } })); }); - return $.when(defs).then(function() { + return $.when.apply($, defs).then(function() { if (account_ids.length) { self._rpc({ model: 'account.account', diff --git a/addons/barcodes/static/src/js/barcode_events.js b/addons/barcodes/static/src/js/barcode_events.js index da0074fb5c7..04c03a41308 100644 --- a/addons/barcodes/static/src/js/barcode_events.js +++ b/addons/barcodes/static/src/js/barcode_events.js @@ -154,7 +154,7 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { // have no way of redispatching 'genuine' key events. Resent events // don't trigger native event handlers of elements. So this means that // our fake events will not appear in eg. an element. - if (this.element_is_editable(e.target) && e.target.getAttribute("barcode_events") !== "true") + if ((this.element_is_editable(e.target) && !$(e.target).data('enableBarcode')) && e.target.getAttribute("barcode_events") !== "true") return; // Catch and buffer the event diff --git a/addons/barcodes/static/src/js/barcode_field.js b/addons/barcodes/static/src/js/barcode_field.js new file mode 100644 index 00000000000..c9f30d53094 --- /dev/null +++ b/addons/barcodes/static/src/js/barcode_field.js @@ -0,0 +1,107 @@ +odoo.define('barcodes.field', function(require) { +"use strict"; + +var AbstractField = require('web.AbstractField'); +var basicFields = require('web.basic_fields'); +var fieldRegistry = require('web.field_registry'); + +// Field in which the user can both type normally and scan barcodes + +var FieldFloatScannable = basicFields.FieldFloat.extend({ + events: { + // The barcode_events component intercepts keypresses and releases them when it + // appears they are not part of a barcode. But since released keypresses don't + // trigger native behaviour (like characters input), we must simulate it. + 'keypress': '_onKeypress', + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + * @private + */ + _renderEdit: function() { + var self = this; + $.when(this._super()).then(function () { + self.$input.data('enableBarcode', true); + }); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {KeyboardEvent} e + */ + _onKeypress: function (e) { + /* only simulate a keypress if it has been previously prevented */ + if (e.originalEvent.dispatched_by_barcode_reader !== true) { + e.preventDefault(); + this.$input.blur(); + return; + } + var character = String.fromCharCode(e.which); + var current_str = e.target.value; + var str_before_carret = current_str.substring(0, e.target.selectionStart); + var str_after_carret = current_str.substring(e.target.selectionEnd); + e.target.value = str_before_carret + character + str_after_carret; + var new_carret_index = str_before_carret.length + character.length; + e.target.setSelectionRange(new_carret_index, new_carret_index); + // Note: FieldChar (that FieldFloat extends) calls store_dom_value upon change + // event, which is triggered when the input loses focus and its internal dirty + // flag is set. But here, we directly modify the value property of the input, + // which doesn't set the dirty flag. So we could call store_dom_value upon blur. + // But we also want the DOM value to be stored when a barcode_event occurs and + // triggers an onchange. We could listen to barcode_event in order to call + // store_dom_value, but that would have to happen before the onchange is triggered. + // So the safest method is still to store the value each time it changes. + // This long explanation is here to avoid having to do the thinking all over agan + // in case this strategy doesn't work / breaks. + // TL;DR Safest way not to lose the value when a barcode scan triggers an onchange. + this._setValue(e.target.value); + }, +}); + +// Field to use scan barcodes + +var FormViewBarcodeHandler = AbstractField.extend({ + /** + * Trigger_up 'activeBarcode' to activate features and send options + * - @params {string} name: the current field name + * - @params {string} [fieldName] optional for x2many sub field + * - @params {string} [quantity] optional field to increase quantity + * - @params {Object} [commands] optional added methods + * can use comand with specific barcode (with ReservedBarcodePrefixes) + * or change 'barcode' for all other received barcodes + * (e.g.: 'O-CMD.MAIN-MENU': function ..., barcode: function () {...}) + * + * @override + */ + init: function() { + this._super.apply(this, arguments); + + this.trigger_up('activeBarcode', { + name: this.name, + fieldName: 'pack_operation_product_ids', + quantity: 'qty_done', + commands: { + barcode: '_barcodeAddX2MQuantity', + } + }); + }, +}); + +fieldRegistry.add('field_float_scannable', FieldFloatScannable); +fieldRegistry.add('barcode_handler', FormViewBarcodeHandler); + +return { + FieldFloatScannable: FieldFloatScannable, + FormViewBarcodeHandler: FormViewBarcodeHandler, +}; + +}); diff --git a/addons/barcodes/static/src/js/barcode_form_view.js b/addons/barcodes/static/src/js/barcode_form_view.js new file mode 100644 index 00000000000..2e22587fbe6 --- /dev/null +++ b/addons/barcodes/static/src/js/barcode_form_view.js @@ -0,0 +1,340 @@ +odoo.define('barcodes.FormView', function (require) { +"use strict"; + +var BarcodeEvents = require('barcodes.BarcodeEvents'); // handle to trigger barcode on bus +var core = require('web.core'); +var Dialog = require('web.Dialog'); +var FormController = require('web.FormController'); +var FormRenderer = require('web.FormRenderer'); + +var _t = core._t; + + +FormController.include({ + custom_events: _.extend({}, FormController.prototype.custom_events, { + activeBarcode: '_barcodeActivated', + }), + + init: function () { + this._super.apply(this, arguments); + this.activeBarcode = { + form_view: { + commands: { + 'O-CMD.NEW': 'createRecord', + 'O-CMD.EDIT': 'toEditMode', + 'O-CMD.CANCEL': 'discardChange', + 'O-CMD.SAVE': 'saveRecord', + // 'O-CMD.PAGER-PREV': + // 'O-CMD.PAGER-NEXT': + } + } + }; + this._barcodeStartListening(); + }, + destroy: function () { + this._barcodeStopListening(); + this._super(); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + * @param {any} barcode + * @param {any} activeBarcode + * @returns {any} + */ + _barcodeAddX2MQuantity: function (barcode, activeBarcode) { + if (this.mode === 'readonly') { + this.do_warn(_t('Error : Document not editable'), + _t('To modify this document, please first start edition.')); + return new $.Deferred().reject(); + } + + var record = this.model.get(this.handle); + var candidate = this._getBarCodeRecord(record, barcode, activeBarcode); + if (candidate) { + return this._barcodeSelectedCandidate(candidate, record, barcode, activeBarcode); + } else { + return this._barcodeWithoutCandidate(record, barcode, activeBarcode); + } + }, + /** + * @private + * @param {any} record + * @param {any} barcode + * @param {any} activeBarcode + * @returns {boolean} + */ + _barcodeRecordFilter: function (record, barcode, activeBarcode) { + return record.data.product_barcode === barcode; + }, + /** + * @private + * @param {any} candidate + * @param {any} record + * @param {any} barcode + * @param {any} activeBarcode + * @returns {Deferred} + */ + _barcodeSelectedCandidate: function (candidate, record, barcode, activeBarcode) { + var changes = {}; + changes[activeBarcode.quantity] = candidate.data[activeBarcode.quantity] + 1; + return this.model.notifyChanges(candidate.id, changes); + }, + /** + * @private + */ + _barcodeStartListening: function () { + core.bus.on('barcode_scanned', this, this._barcodeScanned); + core.bus.on('keypress', this, this._quantityListener); + }, + + /** + * @private + */ + _barcodeStopListening: function () { + core.bus.off('barcode_scanned', this, this._barcodeScanned); + core.bus.off('keypress', this, this._quantityListener); + }, + /** + * @private + * @param {any} record + * @param {any} barcode + * @param {any} activeBarcode + * @returns {Deferred} + */ + _barcodeWithoutCandidate: function (record, barcode, activeBarcode) { + var changes = {}; + changes[activeBarcode.name] = barcode; + return this.model.notifyChanges(record.id, changes).then(function () { + return new $.Deferred().reject(); + }); + }, + /** + * @private + * @param {any} record + * @param {any} barcode + * @param {any} activeBarcode + * @returns {any} + */ + _getBarCodeRecord: function (record, barcode, activeBarcode) { + var self = this; + if (!activeBarcode.fieldName) { + return; + } + return _.find(record.data[activeBarcode.fieldName].data, function (record) { + return self._barcodeRecordFilter(record, barcode, activeBarcode); + }); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * The barcode is activate when at least one widget trigger_up 'activeBarcode' event + * with the widget option + * + * @param {OdooEvent} event + */ + _barcodeActivated: function (event) { + event.stopPropagation(); + var name = event.data.name; + this.activeBarcode[name] = { + name: name, + handle: this.handle, + target: event.target, + widget: event.target.attrs && event.target.attrs.widget, + fieldName: event.data.fieldName, + quantity: event.data.quantity, + commands: event.data.commands || {}, + candidate: this.activeBarcode[name] && this.activeBarcode[name].handle === this.handle ? + this.activeBarcode[name].candidate : null, + }; + }, + /** + * @private + * @param {any} method + * @param {any} barcode + * @param {any} activeBarcode + * @returns {Deferred} + */ + _barcodeActiveScanned: function (method, barcode, activeBarcode) { + var self = this; + var methodDef; + var def = new $.Deferred(); + if (typeof method === 'string') { + methodDef = this[method](barcode, activeBarcode); + } else { + methodDef = method.call(this, barcode, activeBarcode); + } + methodDef.always(function () { + var record = self.model.get(self.handle); + var candidate = self._getBarCodeRecord(record, barcode, activeBarcode); + activeBarcode.candidate = candidate; + def.resolve(); + }); + return def; + }, + /** + * Method called when a user scan a barcode, call each method in function of the + * widget options then update the renderer + * + * @private + * @param {string} barcode + * @returns {Deferred} + */ + _barcodeScanned: function (barcode, target) { + var prefixed = _.any(BarcodeEvents.ReservedBarcodePrefixes, + function (reserved) {return barcode.indexOf(reserved) === 0;}); + var hasCommand = false; + var self = this; + var defs = []; + for (var k in this.activeBarcode) { + var activeBarcode = this.activeBarcode[k]; + // Handle the case where there are several barcode widgets on the same page. Since the + // event is global on the page, all barcode widgets will be triggered. However, we only + // want to keep the event on the target widget. + if (self.target && !$.contains(target, self.target.el)) { + continue; + } + + var methods = this.activeBarcode[k].commands; + var method = prefixed ? methods[barcode] : methods.barcode; + if (method) { + if (prefixed) { + hasCommand = true; + } + defs.push(this._barcodeActiveScanned(method, barcode, activeBarcode)); + } + } + if (prefixed && !hasCommand) { + return this.do_warn(_t('Error : Barcode command is undefined'), barcode); + } + return $.when.apply($, defs).then(function () { + self.update({}, {reload: false}); + }); + }, + /** + * @private + * @param {any} event + */ + _quantityListener: function (event) { + var character = String.fromCharCode(event.which); + + // only catch the event if we're not focused in + // another field and it's a number + if (!$(event.target).is('body') || !/[0-9]/.test(character)) { + return; + } + + if (!_.compact(_.pluck(this.activeBarcode, 'candidate')).length) { + return this.do_warn(_t('Error : No last scanned barcode'), + _t('To set the quantity please scan a barcode first.')); + } + + for (var k in this.activeBarcode) { + if (this.activeBarcode[k].candidate) { + this._quantityOpenDialog(character, this.activeBarcode[k]); + } + } + }, + /** + * @private + * @param {any} character + * @param {any} activeBarcode + */ + _quantityOpenDialog: function (character, activeBarcode) { + var self = this; + var $content = $('
').append($('', {type: 'text', class: 'o_set_qty_input'})); + this.dialog = new Dialog(this, { + title: _t('Set quantity'), + buttons: [{text: _t('Select'), classes: 'btn-primary', close: true, click: function () { + var new_qty = this.$content.find('.o_set_qty_input').val(); + var values = {}; + values[activeBarcode.quantity] = parseFloat(new_qty); + return self.model.notifyChanges(activeBarcode.candidate.id, values).then(function () { + self.update({}, {reload: false}); + }); + }}, {text: _t('Discard'), close: true}], + $content: $content, + }).open(); + // This line set the value of the key which triggered the _set_quantity in the input + var $input = this.dialog.$content.find('.o_set_qty_input').focus().val(character); + + var $selectBtn = this.dialog.$footer.find('.btn-primary'); + $input.on('keypress', function (event){ + if (event.which === 13) { + event.preventDefault(); + $input.off(); + $selectBtn.click(); + } + }); + }, +}); + + +FormRenderer.include({ + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + /** + * trigger_up 'activeBarcode' to Add barcode event handler + * + * @private + * @param {jQueryElement} $button + * @param {Object} node + */ + _barcodeButtonHandler: function ($button, node) { + var commands = {}; + commands.barcode = function () {return $.when();}; + commands['O-BTN.' + node.attrs.barcode_trigger] = function () { + if (!$button.hasClass('o_form_invisible')) { + $button.click(); + } + return $.when(); + }; + this.trigger_up('activeBarcode', { + name: node.attrs.name, + commands: commands + }); + }, + /** + * Add barcode event handler + * + * @override + * @private + * @param {Object} node + * @returns {jQueryElement} + */ + _renderHeaderButton: function (node) { + var $button = this._super.apply(this, arguments); + if (node.attrs.barcode_trigger) { + this._barcodeButtonHandler($button, node); + } + return $button; + }, + /** + * Add barcode event handler + * + * @override + * @private + * @param {Object} node + * @returns {jQueryElement} + */ + _renderStatButton: function (node) { + var $button = this._super.apply(this, arguments); + if (node.attrs.barcode_trigger) { + this._barcodeButtonHandler($button, node); + } + return $button; + }, +}); + +BarcodeEvents.ReservedBarcodePrefixes.push('O-BTN'); + +}); diff --git a/addons/barcodes/static/src/js/barcode_handler_mixin.js b/addons/barcodes/static/src/js/barcode_handler_mixin.js deleted file mode 100644 index 40f7ac734ca..00000000000 --- a/addons/barcodes/static/src/js/barcode_handler_mixin.js +++ /dev/null @@ -1,52 +0,0 @@ -odoo.define('barcodes.BarcodeHandlerMixin', function(require) { -"use strict"; - -var AbstractView = require('web.AbstractView'); -var core = require('web.core'); - -// Mixin implementing the common basis for barcode handlers. -// The object on which this mixin is applied must also include ParentedMixin. Example : -// Widget.extend(BarcodeHandlerMixin, { ... }); -// Class.extend(PropertiesMixin, BarcodeHandlerMixin, { ... }); - -return { - init: function() { - var self = this; - this._super.apply(this, arguments); - this.__on_barcode_scanned = function (barcode, target) { - // Handle the case where there are several barcode widgets on the same page. Since the - // event is global on the page, all barcode widgets will be triggered. However, we only - // want to keep the event on the target widget. - if ($.contains(target, self.el)) { - self.on_barcode_scanned.call(self, barcode); - } - }; - this.start_listening(); - // Handlers inside a View managed by a ViewManager only listen to barcode events while their view is displayed - var view = this.findAncestor(function(ancestor) { return ancestor instanceof AbstractView }); - if (view) { - view.on('attached', this, this.start_listening); - view.on('detached', this, this.stop_listening); - } - }, - - start_listening: function() { - if (! this.is_listening) { - core.bus.on('barcode_scanned', this, this.__on_barcode_scanned); - this.is_listening = true; - } - }, - - stop_listening: function() { - if (this.is_listening) { - core.bus.off('barcode_scanned', this, this.__on_barcode_scanned); - this.is_listening = false; - } - }, - - on_barcode_scanned: function(barcode) { - console.error('A class implementing BarcodeHandlerMixin must redefine method on_barcode_scanned.'); - }, -}; - -}); diff --git a/addons/barcodes/static/src/js/field_float_scannable.js b/addons/barcodes/static/src/js/field_float_scannable.js deleted file mode 100644 index f30e42d461a..00000000000 --- a/addons/barcodes/static/src/js/field_float_scannable.js +++ /dev/null @@ -1,59 +0,0 @@ -odoo.define('barcodes.FieldFloatScannable', function(require) { -"use strict"; - -return; - -var AbstractField = require('web.AbstractField'); -var field_registry = require('web.field_registry'); - -// Field in which the user can both type normally and scan barcodes - -var FieldFloatScannable = form_widgets.FieldFloat.extend({ - events: { - // The barcode_events component intercepts keypresses and releases them when it - // appears they are not part of a barcode. But since released keypresses don't - // trigger native behaviour (like characters input), we must simulate it. - 'keypress': 'simulateKeypress', - }, - - // Widget values are parsed according to the widget type. Since this widget is of type - // "FieldFloatScannable" and there is no parsing planned for this type, it defaults - // to outputting the value as a string. Hence the need to redefine parse_value - parse_value: function(val, def) { - return formats.parse_value(val, {type: "float"}, def); - }, - - simulateKeypress: function (e) { - /* only simulate a keypress if it has been previously prevented */ - if (e.originalEvent.dispatched_by_barcode_reader !== true) { - return; - } - var character = String.fromCharCode(e.which); - var current_str = e.target.value; - var str_before_carret = current_str.substring(0, e.target.selectionStart); - var str_after_carret = current_str.substring(e.target.selectionEnd); - e.target.value = str_before_carret + character + str_after_carret; - var new_carret_index = str_before_carret.length + character.length; - e.target.setSelectionRange(new_carret_index, new_carret_index); - // Note: FieldChar (that FieldFloat extends) calls store_dom_value upon change - // event, which is triggered when the input loses focus and its internal dirty - // flag is set. But here, we directly modify the value property of the input, - // which doesn't set the dirty flag. So we could call store_dom_value upon blur. - // But we also want the DOM value to be stored when a barcode_event occurs and - // triggers an onchange. We could listen to barcode_event in order to call - // store_dom_value, but that would have to happen before the onchange is triggered. - // So the safest method is still to store the value each time it changes. - // This long explanation is here to avoid having to do the thinking all over agan - // in case this strategy doesn't work / breaks. - // TL;DR Safest way not to lose the value when a barcode scan triggers an onchange. - this.store_dom_value(); - }, -}); - -core.form_widget_registry.add('field_float_scannable', FieldFloatScannable); - -return { - FieldFloatScannable: AbstractField, -}; - -}); diff --git a/addons/barcodes/static/src/js/form_view_barcode_handler.js b/addons/barcodes/static/src/js/form_view_barcode_handler.js deleted file mode 100644 index 2b3fe8cfaad..00000000000 --- a/addons/barcodes/static/src/js/form_view_barcode_handler.js +++ /dev/null @@ -1,205 +0,0 @@ -odoo.define('barcodes.FormViewBarcodeHandler', function(require) { -"use strict"; - -var core = require('web.core'); -var concurrency = require('web.concurrency'); -var BarcodeEvents = require('barcodes.BarcodeEvents'); -var BarcodeHandlerMixin = require('barcodes.BarcodeHandlerMixin'); -var KanbanRecord = require('web.KanbanRecord'); -var Dialog = require('web.Dialog'); -var AbstractField = require('web.AbstractField'); - -var _t = core._t; - -// web.KanbanRecord and web.list_common.Record do not implement the -// same interface and are thus inherently incompatible with each -// other. Luckily barcodes keeps things pretty simple when it comes to -// the records it wants to use. So if we give the KanbanRecord a get() -// function that behaves like the one of web.list.Record, everything -// is fine. -KanbanRecord.include({ - get: function (key) { - return this.values[key]; - }, -}); - -var FormViewBarcodeHandler = AbstractField.extend(BarcodeHandlerMixin, { - init: function(parent, context) { - this.__quantity_listener = _.bind(this._set_quantity_listener, this); - BarcodeHandlerMixin.init.apply(this, arguments); - - this.process_barcode_mutex = new concurrency.Mutex(); - - return this._super.apply(this, arguments); - }, - - start: function() { - this._super(); - this.form_view = this.field_manager; - // Hardcoded barcode actions - this.map_barcode_method = { - 'O-CMD.NEW': _.bind(this.form_view.on_button_new, this.form_view), - 'O-CMD.EDIT': _.bind(this.form_view.on_button_edit, this.form_view), - 'O-CMD.CANCEL': _.bind(this.form_view.on_button_cancel, this.form_view), - // FIXME: on_button_save shouldn't mix view and model concerns (it expects to be used as onclick handler) - 'O-CMD.SAVE': _.bind(this.form_view.on_button_save, this.form_view, {target: $('.o_cp_buttons .o_form_button_save')}), - }; - // Old design pager actions - if (this.form_view.execute_pager_action) { - this.map_barcode_method['O-CMD.PAGER-PREV'] = _.bind(this.form_view.execute_pager_action, this.form_view, 'previous'); - this.map_barcode_method['O-CMD.PAGER-NEXT'] = _.bind(this.form_view.execute_pager_action, this.form_view, 'next'); - // New design pager actions - } else if (this.form_view.pager) { - this.map_barcode_method['O-CMD.PAGER-PREV'] = _.bind(this.form_view.pager.previous, this.form_view.pager); - this.map_barcode_method['O-CMD.PAGER-NEXT'] = _.bind(this.form_view.pager.next, this.form_view.pager); - } - }, - - destroy: function () { - this.stop_listening(); - this._super.apply(this, arguments); - }, - - _display_no_edit_mode_warning: function() { - this.do_warn(_t('Error : Document not editable'), _t('To modify this document, please first start edition.')); - }, - - _display_no_last_scanned_warning: function() { - this.do_warn(_t('Error : No last scanned barcode'), _t('To set the quantity please scan a barcode first.')); - }, - - _set_quantity_listener: function(event) { - var self = this; - var character = String.fromCharCode(event.which); - - // only catch the event if we're not focused in - // another field and it's a number - if ($(event.target).is('body') && /[0-9]/.test(character)) { - if (this.form_view.get('actual_mode') === 'view') { - this._display_no_edit_mode_warning(); - } else { - var field = this.form_view.fields[this.m2x_field]; - var view = field.viewmanager.active_view; - var $content = $('
').append($('', {type: 'text', class: 'o_set_qty_input'})); - - if (this.last_scanned_barcode) { - this.dialog = new Dialog(this, { - title: _t('Set quantity'), - buttons: [{text: _t('Select'), classes: 'btn-primary', close: true, click: function () { - var new_qty = this.$content.find('.o_set_qty_input').val(); - var record = _.find(self._get_records(field), function (record) { - return record.get('product_barcode') === self.last_scanned_barcode; - }); - if (record) { - var values = {}; - values[self.quantity_field] = parseFloat(new_qty); - field.data_update(record.get('id'), values).then(function () { - view.controller.reload_record(record); - }); - } else { - self._display_no_last_scanned_warning(); - } - }}, {text: _t('Discard'), close: true}], - $content: $content, - }).open(); - // This line set the value of the key which triggered the _set_quantity in the input - this.dialog.$content.find('.o_set_qty_input').focus().val(character); - - var $selectBtn = this.dialog.$footer.find('.btn-primary'); - core.bus.on('keypress', this.dialog, function(event){ - if (event.which === 13) { - event.preventDefault(); - $selectBtn.click(); - } - }); - } else { - this._display_no_last_scanned_warning(); - } - } - } - }, - - start_listening: function() { - if (this.quantity_field && ! this.is_listening) { - core.bus.on('keypress', this, this.__quantity_listener); - } - - BarcodeHandlerMixin.start_listening.call(this); - }, - - stop_listening: function() { - if (this.quantity_field && this.is_listening) { - core.bus.off('keypress', this, this.__quantity_listener); - delete this.last_scanned_barcode; - } - - BarcodeHandlerMixin.stop_listening.call(this); - }, - - // Let subclasses add custom behaviour before onchange. Must return a deferred. - // Resolve the deferred with true proceed with the onchange, false to prevent it. - pre_onchange_hook: function(barcode) { - return $.Deferred().resolve(true); - }, - - on_barcode_scanned: function(barcode) { - var self = this; - self.last_scanned_barcode = barcode; - // Execute a harcoded action - var action = this.map_barcode_method[barcode]; - if (typeof action === "function") - return $.when(action()); - if (_.any(BarcodeEvents.ReservedBarcodePrefixes, function(prefix) { return barcode.indexOf(prefix) === 0 })) - return; - // Warn the user if form view is not editable - else if (this.form_view.get('actual_mode') === 'view') - this._display_no_edit_mode_warning(); - else { - var process_barcode = function () { - // this function can be passed to `Mutex.exec` in order to make sure - // that every ongoing onchanges in the form view are done - var form_onchanges_mutex = function () { - return self.form_view.onchanges_mutex.def; - } - - // before setting the barcode field with the received barcode, we commit - // every fields of the form view and we wait for their hypothetical ongoing - // onchanges to finish - var commit_mutex = new concurrency.Mutex(); - _.each(self.form_view.fields, function (field) { - commit_mutex.exec(function () { - return field.commit_value(); - }); - commit_mutex.exec(form_onchanges_mutex); - }); - - return commit_mutex.def.then(function () { - return self.pre_onchange_hook(barcode).then(function (proceed) { - if (proceed) { - self.set_value(barcode); // set the barcode field with the received one - return form_onchanges_mutex(); // wait for its onchange to finish - } - }); - }); - }; - - this.process_barcode_mutex.exec(process_barcode); - } - }, - - _get_records: function(field) { - var active_view = field.viewmanager.active_view; - if (active_view.type === "kanban") { - return active_view.controller.widgets; - } else { - // tree view case - return active_view.controller.records.records; - } - }, -}); - -core.form_widget_registry.add('barcode_handler', FormViewBarcodeHandler); - -return FormViewBarcodeHandler; - -}); diff --git a/addons/barcodes/static/src/js/inherited_form_widget_button.js b/addons/barcodes/static/src/js/inherited_form_widget_button.js deleted file mode 100644 index d1998cd40cf..00000000000 --- a/addons/barcodes/static/src/js/inherited_form_widget_button.js +++ /dev/null @@ -1,34 +0,0 @@ -odoo.define('barcode.InheritedFormWidgetButton', function (require) { -"use strict"; - -// FIXME -// var widgets = require('web.form_widgets'); -// var BarcodeEvents = require('barcodes.BarcodeEvents'); -// var BarcodeHandlerMixin = require('barcodes.BarcodeHandlerMixin'); - -// // If the button has a barcode_trigger attribute, dynamically inherit -// // BarcodeHandlerMixin and redefine on_barcode_scanned - -// var ButtonBarcodeHandlerMixin = _.extend({}, BarcodeHandlerMixin, { -// init: function(field_manager, node) { -// if (node.attrs.barcode_trigger) { -// BarcodeHandlerMixin.init.call(this, field_manager, node); -// var self = this; -// this.on_barcode_scanned = function(barcode) { -// var match = barcode.match(/O-BTN\.(.+)/); -// if (match && match[1] === self.node.attrs.barcode_trigger && -// (self.$el.is(':visible') || self.$el.parent('.dropdown-menu').length)) { -// self.on_click(); -// } -// }; -// } else { -// this._super(field_manager, node); -// } -// }, -// }); - -// BarcodeEvents.ReservedBarcodePrefixes.push('O-BTN'); - -// widgets.WidgetButton.include(ButtonBarcodeHandlerMixin); - -}); diff --git a/addons/barcodes/views/barcodes_templates.xml b/addons/barcodes/views/barcodes_templates.xml index 4370533d4ca..420a069a491 100644 --- a/addons/barcodes/views/barcodes_templates.xml +++ b/addons/barcodes/views/barcodes_templates.xml @@ -5,10 +5,8 @@ - - - - + + diff --git a/addons/hr_attendance/static/src/js/greeting_message.js b/addons/hr_attendance/static/src/js/greeting_message.js index f60fea5a060..08c6879e077 100644 --- a/addons/hr_attendance/static/src/js/greeting_message.js +++ b/addons/hr_attendance/static/src/js/greeting_message.js @@ -1,15 +1,13 @@ odoo.define('hr_attendance.greeting_message', function (require) { "use strict"; -var BarcodeHandlerMixin = require('barcodes.BarcodeHandlerMixin'); - var core = require('web.core'); var Widget = require('web.Widget'); var _t = core._t; -var GreetingMessage = Widget.extend(BarcodeHandlerMixin, { +var GreetingMessage = Widget.extend({ template: 'HrAttendanceGreetingMessage', events: { @@ -19,12 +17,12 @@ var GreetingMessage = Widget.extend(BarcodeHandlerMixin, { init: function(parent, action) { var self = this; this._super.apply(this, arguments); - BarcodeHandlerMixin.init.apply(this, arguments); + this.activeBarcode = true; // if no correct action given (due to an erroneous back or refresh from the browser), we set the dismiss button to return // to the (likely) appropriate menu, according to the user access rights if(!action.attendance) { - this.stop_listening(); + this.activeBarcode = false; this.session.user_has_group('hr_attendance.group_hr_attendance_user').then(function(has_group) { if(has_group) { self.next_action = 'hr_attendance.hr_attendance_action_kiosk_mode'; @@ -38,7 +36,7 @@ var GreetingMessage = Widget.extend(BarcodeHandlerMixin, { this.next_action = action.next_action || 'hr_attendance.hr_attendance_action_my_attendances'; // no listening to barcode scans if we aren't coming from the kiosk mode (and thus not going back to it with next_action) if (this.next_action != 'hr_attendance.hr_attendance_action_kiosk_mode' && this.next_action.tag != 'hr_attendance_kiosk_mode') { - this.stop_listening(); + this.activeBarcode = false; } this.attendance = action.attendance; // check in/out times displayed in the greeting message template. @@ -52,6 +50,9 @@ var GreetingMessage = Widget.extend(BarcodeHandlerMixin, { if (this.attendance) { this.attendance.check_out ? this.farewell_message() : this.welcome_message(); } + if (this.activeBarcode) { + core.bus.on('barcode_scanned', this, this._onBarcodeScanned); + } }, welcome_message: function() { @@ -100,7 +101,7 @@ var GreetingMessage = Widget.extend(BarcodeHandlerMixin, { if(now.valueOf() - last_check_in_date.valueOf() > 1000*60*60*12){ this.$('.o_hr_attendance_warning_message').append(_t("Warning! Last check in was over 12 hours ago.
If this isn't right, please contact Human Resources.")); clearTimeout(this.return_to_main_menu); - this.stop_listening(); + this.activeBarcode = false; } else if(now.valueOf() - last_check_in_date.valueOf() > 1000*60*60*8){ this.$('.o_hr_attendance_random_message').html(_t("Another good day's work! See you soon!")); } @@ -126,7 +127,7 @@ var GreetingMessage = Widget.extend(BarcodeHandlerMixin, { } }, - on_barcode_scanned: function(barcode) { + _onBarcodeScanned: function(barcode) { var self = this; if (this.return_to_main_menu) { // in case of multiple scans in the greeting message view, delete the timer, a new one will be created. clearTimeout(this.return_to_main_menu); @@ -146,6 +147,7 @@ var GreetingMessage = Widget.extend(BarcodeHandlerMixin, { }, destroy: function () { + core.bus.off('barcode_scanned', this, this._onBarcodeScanned); clearTimeout(this.return_to_main_menu); this._super.apply(this, arguments); }, diff --git a/addons/hr_attendance/static/src/js/kiosk_mode.js b/addons/hr_attendance/static/src/js/kiosk_mode.js index e633c4d53e1..6eb065739f6 100644 --- a/addons/hr_attendance/static/src/js/kiosk_mode.js +++ b/addons/hr_attendance/static/src/js/kiosk_mode.js @@ -4,24 +4,17 @@ odoo.define('hr_attendance.kiosk_mode', function (require) { var core = require('web.core'); var Widget = require('web.Widget'); var Session = require('web.session'); -var BarcodeHandlerMixin = require('barcodes.BarcodeHandlerMixin'); var QWeb = core.qweb; -var KioskMode = Widget.extend(BarcodeHandlerMixin, { +var KioskMode = Widget.extend({ events: { "click .o_hr_attendance_button_employees": function(){ this.do_action('hr_attendance.hr_employee_attendance_action_kanban'); }, }, - init: function (parent, action) { - // Note: BarcodeHandlerMixin.init calls this._super.init, so there's no need to do it here. - // Yet, "_super" must be present in a function for the class mechanism to replace it with the actual parent method. - this._super; - BarcodeHandlerMixin.init.apply(this, arguments); - }, - start: function () { var self = this; + core.bus.on('barcode_scanned', this, this._onBarcodeScanned); self.session = Session; this._rpc({ model: 'res.company', @@ -37,7 +30,7 @@ var KioskMode = Widget.extend(BarcodeHandlerMixin, { return self._super.apply(this, arguments); }, - on_barcode_scanned: function(barcode) { + _onBarcodeScanned: function(barcode) { var self = this; this._rpc({ model: 'hr.employee', @@ -60,6 +53,7 @@ var KioskMode = Widget.extend(BarcodeHandlerMixin, { }, destroy: function () { + core.bus.off('barcode_scanned', this, this._onBarcodeScanned); clearInterval(this.clock_start); this._super.apply(this, arguments); }, diff --git a/addons/web/static/src/js/core/mixins.js b/addons/web/static/src/js/core/mixins.js index 8a5038557c2..ae513b0b506 100644 --- a/addons/web/static/src/js/core/mixins.js +++ b/addons/web/static/src/js/core/mixins.js @@ -508,11 +508,11 @@ var ServicesMixin = { this.trigger_up('do_action', {'action': action, options: options}); return def; }, - do_notify: function (title, message, sticky) { - this.trigger_up('notification', {title: title, message: message, sticky: sticky}); + do_notify: function (title, message, sticky, className) { + this.trigger_up('notification', {title: title, message: message, sticky: sticky, className: className}); }, - do_warn: function (title, message, sticky) { - this.trigger_up('warning', {title: title, message: message, sticky: sticky}); + do_warn: function (title, message, sticky, className) { + this.trigger_up('warning', {title: title, message: message, sticky: sticky, className: className}); }, }; diff --git a/addons/web/static/src/js/services/data_manager.js b/addons/web/static/src/js/services/data_manager.js index 1a4cebdc955..5d2bf18f1de 100644 --- a/addons/web/static/src/js/services/data_manager.js +++ b/addons/web/static/src/js/services/data_manager.js @@ -271,7 +271,7 @@ return core.Class.extend({ field.onChange = "1"; } - if (field.views) { + if (!_.isEmpty(field.views)) { // process the inner fields_view as well to find the fields they use. // register those fields' description directly on the view. // for those inner views, the list of all fields isn't necessary, so diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index f59cf96a881..ced26fb7cb2 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -1052,7 +1052,13 @@ var BasicModel = AbstractModel.extend({ defs.push(def); break; case 'CREATE': - defs.push(this.addDefaultRecord(list.id)); + if (command.data) { + defs.push(this.addDefaultRecord(list.id).then(function (id) { + return this.notifyChanges(id, command.data); + })); + } else { + defs.push(this.addDefaultRecord(list.id)); + } break; case 'UPDATE': defs.push(this._applyChange(command.id, command.data)); diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index e6e8c9a4d17..e22eebc89ce 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -4,13 +4,13 @@