summaryrefslogtreecommitdiff
path: root/addons/barcodes/static/src/js
diff options
context:
space:
mode:
authorAdrien Dieudonne <adr@odoo.com>2018-03-14 09:33:35 +0100
committerAdrien Dieudonne <adr@odoo.com>2018-03-20 16:21:20 +0100
commit3a74afd21eb12ca79a2f609b58f271278cae57cf (patch)
treea5214608970d5e0b7be4e8ddf598841bd827fe60 /addons/barcodes/static/src/js
parent5bc29744c9e605729632c6d2561156b886772058 (diff)
[FIX] barcodes: barcode scanning on Chrome mobile
Before this commit, the barcode value wasn't triggered on some Android devices with Google Chrome and using the Odoo app. In fact, the keypress event may not trigger with some devices. This is probably due to the fact that this event is marked as 'Legacy'. See: https://www.w3.org/TR/uievents/#legacy-keyboardevent-event-types To fix this, we can use 'keydown' event but we have to handle an other issue: there is no way to know which key is typed. The auto-suggest feature on Android invalidate all the following properties: Keycode, charCode, key, which, keyIdentifier, code. This is a well-known issue: https://bugs.chromium.org/p/chromium/issues/detail?id=118639 For more infos, please read this blog: https://www.outsystems.com/blog/javascript-events-unmasked-how-to-create-input-mask-for-mobile.html As a work around, we create a temporary input field that stores the barcode value. The focus is set on this input when a keydown is detected. Note that when an input has the focus, the android virtual keyboard will be opened. We can't avoid this behavior. The only thing we can do is to automatically close it after 800 ms. See: https://bugs.chromium.org/p/chromium/issues/detail?id=662386 As this fix is specific for Chrome only, it's not possible to test it easily. Some tests will be added in master.
Diffstat (limited to 'addons/barcodes/static/src/js')
-rw-r--r--addons/barcodes/static/src/js/barcode_events.js108
1 files changed, 107 insertions, 1 deletions
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 = $('<input/>', {
+ 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);