From 2df4060c776c7a6a946f2b7a164ff7bbbe5e854a Mon Sep 17 00:00:00 2001 From: Arthur Maniet Date: Wed, 8 Jul 2015 08:08:25 +0200 Subject: [PATCH 1/5] [ADD] barcodes: onchange-like mechanism to react to scans in form view For a model to use it, it must : - Inherit barcodes.barcode_events_mixin and override on_barcode_scanned(self, barcode) - Add to each form view that should listen to barcode events and trigger on_barcode_scanned. The method on_barcode_scanned works just like an onchange. --- addons/barcodes/__init__.py | 4 + addons/barcodes/models/__init__.py | 3 + .../barcodes/models/barcode_events_mixin.py | 24 ++++++ .../src/js/form_view_barcode_handler.js | 79 +++++++++++++++++++ addons/barcodes/views/templates.xml | 1 + 5 files changed, 111 insertions(+) create mode 100644 addons/barcodes/models/__init__.py create mode 100644 addons/barcodes/models/barcode_events_mixin.py create mode 100644 addons/barcodes/static/src/js/form_view_barcode_handler.js diff --git a/addons/barcodes/__init__.py b/addons/barcodes/__init__.py index 5bea30ad78f..42a4cc552ac 100644 --- a/addons/barcodes/__init__.py +++ b/addons/barcodes/__init__.py @@ -1 +1,5 @@ +# -*- coding: utf-8 -*- + import barcodes + +import models diff --git a/addons/barcodes/models/__init__.py b/addons/barcodes/models/__init__.py new file mode 100644 index 00000000000..5c31ffd4a98 --- /dev/null +++ b/addons/barcodes/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +import barcode_events_mixin diff --git a/addons/barcodes/models/barcode_events_mixin.py b/addons/barcodes/models/barcode_events_mixin.py new file mode 100644 index 00000000000..383e622c0a1 --- /dev/null +++ b/addons/barcodes/models/barcode_events_mixin.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +from openerp import models, fields, api + +class BarcodeEventsMixin(models.AbstractModel): + """ Mixin class for objects reacting when a barcode is scanned in their form views + which contains ``. + Models using this mixin must implement the method on_barcode_scanned. It works + like an onchange and receives the scanned barcode in parameter. + """ + + _name = 'barcodes.barcode_events_mixin' + + _barcode_scanned = fields.Char("Barcode Scanned", help="Value of the last barcode scanned.", store=False) + + @api.onchange('_barcode_scanned') + def _on_barcode_scanned(self): + barcode = self._barcode_scanned + if barcode: + self._barcode_scanned = "" + return self.on_barcode_scanned(barcode) + + def on_barcode_scanned(self, barcode): + raise NotImplementedError("In order to use barcodes.barcode_events_mixin, method on_barcode_scanned must be implemented") diff --git a/addons/barcodes/static/src/js/form_view_barcode_handler.js b/addons/barcodes/static/src/js/form_view_barcode_handler.js new file mode 100644 index 00000000000..a68d531819f --- /dev/null +++ b/addons/barcodes/static/src/js/form_view_barcode_handler.js @@ -0,0 +1,79 @@ +odoo.define('barcodes.FormViewBarcodeHandler', function(require) { +"use strict"; + +var core = require('web.core'); +var utils = require('web.utils'); +var common = require('web.form_common'); + +var _t = core._t; + +var FormViewBarcodeHandler = common.AbstractField.extend({ + + start: function() { + this._super(); + this.form_view = this.field_manager; + core.bus.on('barcode_scanned', this, this.on_barcode_scanned); + // Hardcoded barcode actions + this.map_barcode_method = { + 'O-CMD.NEW': _.bind(this.form_view.on_button_new, 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); + } + }, + + // Let subclasses add custom behaviour before onchange + // Return false in order to proceed with the onchange, anything else to prevent it. + pre_onchange_hook: function(barcode) { + return false; + }, + + on_barcode_scanned: function(barcode) { + var self = this; + // Execute a harcoded action + var action = this.map_barcode_method[barcode]; + if (typeof action === "function") + return $.when(action()); + // Warn the user if form view is not editable + else if (this.form_view.get('actual_mode') === 'view') + this.do_warn(_t('Error : Document not editable'), _t('To modify this document, please first start edition.')); + else { + // Call hook method possibly implemented by subclass + $.when(this.pre_onchange_hook(barcode)).then(function(result) { + if (result !== false) { + return result; + } else { + // Wait for hypothetical ongoing onchange to finish + self.form_view.onchanges_mutex.exec(function() { + // A real onchange is triggered when a value actually changes (which can correspond + // to a widget's blur event per example). Commit the value of fields before + // programmatically triggering an onchange to be consistent with this. + var mutex_commit_value = new utils.Mutex(); + _.each(self.form_view.fields, function(field) { + mutex_commit_value.exec(_.bind(field.commit_value, field)); + }); + return mutex_commit_value.def.then(function(){ + // Trigger the barcode onchange + self.set_value(barcode); + }); + }); + } + }); + } + }, +}); + +core.form_widget_registry.add('barcode_handler', FormViewBarcodeHandler); + +return FormViewBarcodeHandler; + +}); diff --git a/addons/barcodes/views/templates.xml b/addons/barcodes/views/templates.xml index f0f92579f49..2db01ead8de 100644 --- a/addons/barcodes/views/templates.xml +++ b/addons/barcodes/views/templates.xml @@ -6,6 +6,7 @@ + From 670d759dbf1c7153c3df17f533972b47d13e84f3 Mon Sep 17 00:00:00 2001 From: Arthur Maniet Date: Fri, 10 Jul 2015 13:38:01 +0200 Subject: [PATCH 2/5] [ADD] barcodes: BarcodeHandlerMixin that starts/stops the event handling Only the barcode handlers in the front view should listen to barcode events. --- .../static/src/js/barcode_handler_mixin.js | 47 +++++++++++++++++++ .../src/js/form_view_barcode_handler.js | 5 +- addons/barcodes/views/templates.xml | 1 + 3 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 addons/barcodes/static/src/js/barcode_handler_mixin.js diff --git a/addons/barcodes/static/src/js/barcode_handler_mixin.js b/addons/barcodes/static/src/js/barcode_handler_mixin.js new file mode 100644 index 00000000000..5b01516788f --- /dev/null +++ b/addons/barcodes/static/src/js/barcode_handler_mixin.js @@ -0,0 +1,47 @@ +odoo.define('barcodes.BarcodeHandlerMixin', function(require) { +"use strict"; + +var core = require('web.core'); +var View = require('web.View'); + +// 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 () { + self.on_barcode_scanned.apply(self, arguments); + }; + 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 View }); + 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/form_view_barcode_handler.js b/addons/barcodes/static/src/js/form_view_barcode_handler.js index a68d531819f..ab2f6514bf0 100644 --- a/addons/barcodes/static/src/js/form_view_barcode_handler.js +++ b/addons/barcodes/static/src/js/form_view_barcode_handler.js @@ -4,15 +4,14 @@ odoo.define('barcodes.FormViewBarcodeHandler', function(require) { var core = require('web.core'); var utils = require('web.utils'); var common = require('web.form_common'); +var BarcodeHandlerMixin = require('barcodes.BarcodeHandlerMixin'); var _t = core._t; -var FormViewBarcodeHandler = common.AbstractField.extend({ - +var FormViewBarcodeHandler = common.AbstractField.extend(BarcodeHandlerMixin, { start: function() { this._super(); this.form_view = this.field_manager; - core.bus.on('barcode_scanned', this, this.on_barcode_scanned); // Hardcoded barcode actions this.map_barcode_method = { 'O-CMD.NEW': _.bind(this.form_view.on_button_new, this.form_view), diff --git a/addons/barcodes/views/templates.xml b/addons/barcodes/views/templates.xml index 2db01ead8de..1b36f049a9c 100644 --- a/addons/barcodes/views/templates.xml +++ b/addons/barcodes/views/templates.xml @@ -6,6 +6,7 @@ + From 9cf6339b3e2fc489da067dbb878ccf2d2ed5446d Mon Sep 17 00:00:00 2001 From: Arthur Maniet Date: Wed, 8 Jul 2015 08:17:16 +0200 Subject: [PATCH 3/5] [ADD] web: fields char and many2one can take an option barcode_events This allows to detect barcode scans while the focus is in such a field. It is not the default behaviour because buffering keystrokes to see if they are a barcode then releasing them if they aren't isn't trivial (it is impossible to trigger a native keyboard event). --- addons/web/static/src/xml/base.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index c79d9fc1f86..428a12db511 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -666,6 +666,7 @@
Date: Thu, 27 Aug 2015 16:29:34 +0200 Subject: [PATCH 4/5] [ADD] barcodes/web: element button can use attribute barcode_trigger A button can be triggered when a barcode corresponding to its attribute 'barcode_trigger' is scanned --- .../src/js/inherited_form_widget_button.js | 29 +++++++++++++++++++ addons/barcodes/views/templates.xml | 1 + .../web/static/src/js/views/form_widgets.js | 3 +- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 addons/barcodes/static/src/js/inherited_form_widget_button.js diff --git a/addons/barcodes/static/src/js/inherited_form_widget_button.js b/addons/barcodes/static/src/js/inherited_form_widget_button.js new file mode 100644 index 00000000000..6c85b9a92a8 --- /dev/null +++ b/addons/barcodes/static/src/js/inherited_form_widget_button.js @@ -0,0 +1,29 @@ +odoo.define('barcode.InheritedFormWidgetButton', function (require) { +"use strict"; + +var widgets = require('web.form_widgets'); +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.on_click(); + } + }; + } else { + this._super(field_manager, node); + } + }, +}); + +widgets.WidgetButton.include(ButtonBarcodeHandlerMixin); + +}); diff --git a/addons/barcodes/views/templates.xml b/addons/barcodes/views/templates.xml index 1b36f049a9c..2fcab2ea6db 100644 --- a/addons/barcodes/views/templates.xml +++ b/addons/barcodes/views/templates.xml @@ -8,6 +8,7 @@ + diff --git a/addons/web/static/src/js/views/form_widgets.js b/addons/web/static/src/js/views/form_widgets.js index 8a51e8aa480..54c57c6fd8c 100644 --- a/addons/web/static/src/js/views/form_widgets.js +++ b/addons/web/static/src/js/views/form_widgets.js @@ -1663,7 +1663,8 @@ core.form_tag_registry.add('button', WidgetButton); return { FieldChar: FieldChar, - FieldMonetary: FieldMonetary + FieldMonetary: FieldMonetary, + WidgetButton: WidgetButton }; }); From 17e2526667cd45199fc541c719f5a700c9d289e0 Mon Sep 17 00:00:00 2001 From: Arthur Maniet Date: Tue, 1 Sep 2015 12:16:08 +0200 Subject: [PATCH 5/5] [FIX] barcodes: use _.bind instead of function.bind because IE --- addons/barcodes/static/src/js/barcode_events.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/barcodes/static/src/js/barcode_events.js b/addons/barcodes/static/src/js/barcode_events.js index 11df512ccea..7d0789f76b6 100644 --- a/addons/barcodes/static/src/js/barcode_events.js +++ b/addons/barcodes/static/src/js/barcode_events.js @@ -21,12 +21,12 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { init: function() { mixins.PropertiesMixin.init.call(this); // Keep a reference of the handler functions to use when adding and removing event listeners - this.__keydown_handler = this.keydown_handler.bind(this); - this.__keyup_handler = this.keyup_handler.bind(this); - this.__handler = this.handler.bind(this); + this.__keydown_handler = _.bind(this.keydown_handler, this); + this.__keyup_handler = _.bind(this.keyup_handler, this); + this.__handler = _.bind(this.handler, this); // Bind event handler once the DOM is loaded // TODO: find a way to be active only when there are listeners on the bus - $(this.start.bind(this, false)); + $(_.bind(this.start, this, false)); }, handle_buffered_keys: function() { @@ -145,7 +145,7 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { if (String.fromCharCode(e.which).match(this.suffix)) { this.handle_buffered_keys(); } else { - this.timeout = setTimeout(this.handle_buffered_keys.bind(this), this.max_time_between_keys_in_ms); + this.timeout = setTimeout(_.bind(this.handle_buffered_keys, this), this.max_time_between_keys_in_ms); } },