diff --git a/addons/barcodes/static/src/js/barcode_events.js b/addons/barcodes/static/src/js/barcode_events.js index 3c5c1f51ddf..71e07030ffd 100644 --- a/addons/barcodes/static/src/js/barcode_events.js +++ b/addons/barcodes/static/src/js/barcode_events.js @@ -28,6 +28,12 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { // Keys from a barcode scanner are usually processed as quick as possible, // but some scanners can use an intercharacter delay (we support <= 50 ms) max_time_between_keys_in_ms: session.max_time_between_keys_in_ms || 55, + // To be able to receive the barcode value, an input must be focused. + // On mobile devices, this causes the virtual keyboard to open. + // Unfortunately it is not possible to avoid this behavior... + // To avoid keyboard flickering at each detection of a barcode value, + // we want to keep it open for a while (800 ms). + inputTimeOut: 800, init: function() { mixins.PropertiesMixin.init.call(this); @@ -38,6 +44,30 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { // Bind event handler once the DOM is loaded // TODO: find a way to be active only when there are listeners on the bus $(_.bind(this.start, this, false)); + + // Mobile device detection + var isMobile = navigator.userAgent.match(/Android/i) || + navigator.userAgent.match(/webOS/i) || + navigator.userAgent.match(/iPhone/i) || + navigator.userAgent.match(/iPad/i) || + navigator.userAgent.match(/iPod/i) || + navigator.userAgent.match(/BlackBerry/i) || + navigator.userAgent.match(/Windows Phone/i); + this.isChromeMobile = isMobile && window.chrome; + + // Creates an input who will receive the barcode scanner value. + if (this.isChromeMobile) { + this.$barcodeInput = $('', { + name: 'barcode', + type: 'text', + css: { + 'position': 'absolute', + 'opacity': 0, + }, + }); + } + + this.__removeBarcodeField = _.debounce(this._removeBarcodeField, this.inputTimeOut); }, handle_buffered_keys: function() { @@ -167,8 +197,84 @@ var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, { } }, + /** + * Try to detect the barcode value by listening all keydown events: + * Checks if a dom element who may contains text value has the focus. + * If not, it's probably because these events are triggered by a barcode scanner. + * To be able to handle this value, a focused input will be created. + * + * This function also has the responsibility to detect the end of the barcode value. + * (1) In most of cases, an optional key (tab or enter) is sent to mark the end of the value. + * So, we direclty handle the value. + * (2) If no end key is configured, we have to calculate the delay between each keydowns. + * 'max_time_between_keys_in_ms' depends of the device and may be configured. + * Exceeded this timeout, we consider that the barcode value is entirely sent. + * + * @private + * @param {jQuery.Event} e keydown event + */ + _listenBarcodeScanner: function (e) { + if (!$('input:text:focus, textarea:focus, [contenteditable]:focus').length) { + $('body').append(this.$barcodeInput); + this.$barcodeInput.focus(); + } + if (this.$barcodeInput.is(":focus")) { + // Handle buffered keys immediately if the keypress marks the end + // of a barcode or after x milliseconds without a new keypress. + clearTimeout(this.timeout); + // On chrome mobile, e.which only works for some special characters like ENTER or TAB. + if (String.fromCharCode(e.which).match(this.suffix)) { + this._handleBarcodeValue(e); + } else { + this.timeout = setTimeout(this._handleBarcodeValue.bind(this, e), + this.max_time_between_keys_in_ms); + } + // if the barcode input doesn't receive keydown for a while, remove it. + this.__removeBarcodeField(); + } + }, + + /** + * Retrieves the barcode value from the temporary input element. + * This checks this value and trigger it on the bus. + * + * @private + * @param {jQuery.Event} keydown event + */ + _handleBarcodeValue: function (e) { + var barcodeValue = this.$barcodeInput.val(); + if (barcodeValue.match(this.regexp)) { + core.bus.trigger('barcode_scanned', barcodeValue, $(e.target).parent()[0]); + this.$barcodeInput.val(''); + } + }, + + /** + * Remove the temporary input created to store the barcode value. + * If nothing happens, this input will be removed, so the focus will be lost + * and the virtual keyboard on mobile devices will be closed. + * + * @private + */ + _removeBarcodeField: function () { + if (this.$barcodeInput) { + // Reset the value and remove from the DOM. + this.$barcodeInput.val('').remove(); + } + }, + start: function(prevent_key_repeat){ - $('body').bind("keypress", this.__handler); + // Chrome Mobile isn't triggering keypress event. + // This is marked as Legacy in the DOM-Level-3 Standard. + // See: https://www.w3.org/TR/uievents/#legacy-keyboardevent-event-types + // This fix is only applied for Google Chrome Mobile but it should work for + // all other cases. + // In master, we could remove the behavior with keypress and only use keydown. + if (this.isChromeMobile) { + $('body').on("keydown", this._listenBarcodeScanner.bind(this)); + } else { + $('body').bind("keypress", this.__handler); + } if (prevent_key_repeat === true) { $('body').bind("keydown", this.__keydown_handler); $('body').bind('keyup', this.__keyup_handler);