*: barcodes, mail, mass_mailing, note, test_website, web, web_tour, website, website_blog, website_event, website_form, website_forum, website_mail_channel, website_mass_mailing, website_profile, website_sale, website_twitter, doc New solutions are being investigated in master. For the moment, it seems better to remove Jabberwock to unlock difficult forward-port between 14.0 and 14.1/master, until we decide what to do once and for all in a later master. We may un-revert this later but that would be way easier than reverting Jabberwock in a few months. This was done as safely as possible by removing any commit related to Jabberwock, then resolving conflicts, then reforcing commits from 14.0 that were hugely adapted for 14.1/master, then forcing the whole diff over a rebased 14.1/master. The only possible miss (other than me making a mistake) would be commits that were not forward-ported to 14.1 thanks to Jabberwock handling the issue on its own (I know we had such commits in website, which I manually included in this revert, see below). For reference: Commits which were reforced to their 14.0 version: - https://github.com/odoo/odoo/commit/1a916fc2362c0b006460d94a219366ade1cce058 - https://github.com/odoo/odoo/commit/fd9e58a675bb7ec1bf97cb77b21b3c65369d51fe - https://github.com/odoo/odoo/commit/bdfddace29b16404c06a7ebfc0cc242463a0e768 - https://github.com/odoo/odoo/commit/42b3ad10e0b32b7fc72f801e2c67d6baf938c566 - https://github.com/odoo/odoo/commit/710784da1f02d45cbe898da426ba7e2ac63dc711 - https://github.com/odoo/odoo/commit/597585c9b8b1350bcfd84e8433ea6c82060bcc3d - https://github.com/odoo/odoo/commit/333a9124608ff655bd9ad5e63044a5cb7ef9c636 Commits that were not forward-ported to 14.1/master and now are: - https://github.com/odoo/odoo/commit/55ff2d971b672dec5f103215be39101c88856b6c - https://github.com/odoo/odoo/commit/ebbb3de1e5f363689a5ba1647bb4c26416e42d5b + Adapt this forward-port: https://github.com/odoo/odoo/pull/60976 (as it was simplified for non-stable master version relying to the Jabberwock implementation and now needed to be adapted to the summernote implementation). See `!$el.data('oe-expression') && $el.data('oe-xpath')` + Revert https://github.com/odoo/odoo/pull/60477 and reforce original 14.0 fix https://github.com/odoo/odoo/commit/746bf53b4aecfc601f0581a948d7cb7153812c82 Note: this also means that any good refactoring that was done by the Jabberwock-related commits is lost for now. Once the revert reaches master, I'll try and restore what we want from those commits. Here are their references (but obviously they have the opposite conflicts than those resolved during this revert): e766842a92b6 [REM] web_editor,website: empty summernote files 08c94c986e09 [REF] web_editor,web: adapt to new jabberwock editor 1546c1b74713 [REF] mass_mailing: adapt to new jabberwock editor fd0b963c6028 [REF] website: adapt to new jabberwock editor 0113d05c6c94 [ADD] web_editor: add new Jabberwock editor lib d0c88a396493 [FIX] web_editor: don't change background color out of the website editor d52d3d67d4e1 [FIX] web_editor : better icon in text style dropdown 61cb2f0d21da [FIX] web_editor: should not ask if want to leave the page twice c42012b863b5 [FIX] web_editor: trigger a resize when use the mobile preview d934d05d6e39 [FIX] web_editor: need to build the snippet before commit it into vDom 495bea924745 [FIX] web_editor: remove box shadow on the #wrap container bffb5612e689 [ADD] field_html: add resizer in most field_html f3c94e40cccf [IMP] web_editor: update Jabberwock library to commit 41e4063 cc87dea3ef32 [FIX] web_editor: update header change position to work with JW 5d9b25f66704 [FIX] web_editor: do not insert chars around step icon on click 70d3f0e4630c [IMP] web_editor: update Jabberwock library to commit 0bd94881 a57f13891f82 [FIX] web_editor: open media modal in appropriate tab on dblclick media 5b7537397e08 [IMP] web_editor: update Jabberwock library to commit a7ba7c34 a8d7ec235eeb [FIX] web_editor: adapt iframe Qunit test to new editor 5f794eac7209 [FIX] mass_mailing: hide all panels on show themes 551d45641d19 [IMP] website: remove unused reference 10580e1c102f [IMP] wesbite: add comment in tour c17049f6b764 [FIX] web_editor: Fix description toggle in pricelist snippet 18a428e854b1 [IMP] web_editor: update Jabberwock library to commit 43a10003 58a161c645a5 [REF] web_editor, website: use editor helper setClass 62dd0bd3bc66 [FIX] web_editor: properly deactivate snippets and reactivate the last 99eebcaa34cb [FIX] web_editor: disable snippet in preview mode on mouse leave options a84216932958 [FIX] web_editor: fix the image gallery snippet f8dd4ea3e7d5 [FIX] web_editor: fix shadow selector for Safari e50a4f3a16de [IMP] web_editor: update Jabberwock library to commit de13ed7e 4a2718f7a5ba [FIX] web_editor: ensure dom is properly cleaned at end of save test 03684c004a4e [FIX] website: ensure reset of bg-image on add bg-video d63397159708 [FIX] web_editor: fix image remove from images wall 82fa5142f1a5 [IMP] note: restyle note without sheet or resizer 8b9f1ce603d5 [FIX] web_editor: properly mock createWysiwygIntance (sic) in tests fc2183b66305 [FIX] web_editor: image overlay did not update with changes cd0d2f5791cd [FIX] web_editor: add color preview to color picker 28a168172454 [FIX] web_editor: restore removal of spinner 67c90dd3b946 [FIX] website_forum: properly initialize editor 920dfe2a430a [FIX] website_forum: better css in the JW toolbar 43f33fd651af [IMP] web_editor: update Jabberwock library to commit 6853b60 71246c92f8de [FIX] Web_editor: table options button should be inside the toolbar 12b9e5916692 [FIX] web_editor: properly update the image options on replace image e889cf8583da [FIX] website, website_sale: properly save filter id of dynamic snippets e0fd11e36e45 [IMP] web_editor: update Jabberwock library to commit 4b2c903b 8f2b7ba35614 [FIX] web_editor: properly stop snippet option changed event propagation a30c2c4105c7 [FIX] website: fix megamenu snippet editor behavior 312091cf822b [FIX] web_editor: fix overlay that is not reappearing 9f1d03dda613 [FIX] web_editor: table picker not fully displayed 814ddada6124 [IMP] web_editor: remove message before leaving page if editor is destro 4f38e26af7da [FIX] web: allow saving copyright footer 01e947b6123b [FIX] web_editor: only save translations that changed 103676072c50 [FIX] web_editor: prevent traceback on open crop dialog 5844ff66d4a0 [FIX] web_editor: apply image crop in jw on save dialog 1b435652e4a0 [FIX] website_forum: ensure media modal opens on click button 4f4a6ca0b8ac [FIX] web_editor: ensure valid default html value 51e22026ac7b [IMP] web_editor: update Jabberwock library to commit ab1184f8 2413fa19be0e [FIX] web_editor: Qunit test properly wait for editor stop aa89e1ea3e97 [FIX] web_editor: properly save view blocks with an id e2e90b53992e [FIX] web_editor: ensure language selector is non editable 0efd72089d5c [IMP] web_editor: clean useless lines f54dfc7631f9 [IMP] web_editor: remove useless comment 3995ab1b84f2 [IMP] web_editor: update Jabberwock library to commit b8d73691 0834b1e5740a [IMP] web_editor: withDomMutations f1459fb7b45c [FIX] web_editor: fix non-deterministic error in QUnit wysiwyg tests 6e8acc6c8cc7 [IMP] web_editor: update Jabberwock library to commit 3bbb175c 18a0c95d51cb [FIX] website, web_editor: #wrapwrap in body 4686a92e742c [FIX] website_form: allow edition of success message 78cc4da075cc [FIX] web_editor: allow edition of branded nodes only bcffa7353448 [FIX] web: dialog should not use field value footer items 67ff56e14746 [FIX] web_editor,website_mass_mailing: display the popup preview 3cb9bea50c20 [IMP] web_editor: update Jabberwock library to commit a20492ea 518f03e6f1c9 [FIX] web_editor: use withIntangibles to find ZoneNode d0ad6a568f49 [FIX] website: move sidebar out of theme d690f5da13ea [FIX] website: properly save popup id 43b3433df3e6 [FIX] web_editor: fix non-deterministic error in QUnit wysiwyg tests 4ee8f4dedc30 [FIX] web_editor: prevent deadlock when removing child snippet of popup 850be198ef6d [FIX] web_editor: prevent traceback on reposition background image 4c3eaba41f5d [FIX] mass_mailing: adapt tour to the new editor design. 496d3ea272ed [FIX] web_editor: properly position sidebar scrollbar 5be4de703074 [FIX] web_editor: Show the toolbar when select the text in forum edition closes odoo/odoo#63768 Related: odoo/enterprise#15458 Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
301 lines
12 KiB
JavaScript
301 lines
12 KiB
JavaScript
odoo.define('barcodes.BarcodeEvents', function(require) {
|
|
"use strict";
|
|
|
|
var config = require('web.config');
|
|
var core = require('web.core');
|
|
var mixins = require('web.mixins');
|
|
var session = require('web.session');
|
|
|
|
|
|
// For IE >= 9, use this, new CustomEvent(), instead of new Event()
|
|
function CustomEvent ( event, params ) {
|
|
params = params || { bubbles: false, cancelable: false, detail: undefined };
|
|
var evt = document.createEvent( 'CustomEvent' );
|
|
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
|
|
return evt;
|
|
}
|
|
CustomEvent.prototype = window.Event.prototype;
|
|
|
|
var BarcodeEvents = core.Class.extend(mixins.PropertiesMixin, {
|
|
timeout: null,
|
|
key_pressed: {},
|
|
buffered_key_events: [],
|
|
// Regexp to match a barcode input and extract its payload
|
|
// Note: to build in init() if prefix/suffix can be configured
|
|
regexp: /(.{3,})[\n\r\t]*/,
|
|
// By knowing the terminal character we can interpret buffered keys
|
|
// as a barcode as soon as it's encountered (instead of waiting x ms)
|
|
suffix: /[\n\r\t]+/,
|
|
// 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);
|
|
// Keep a reference of the handler functions to use when adding and removing event listeners
|
|
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
|
|
$(_.bind(this.start, this, false));
|
|
|
|
// Mobile device detection
|
|
this.isChromeMobile = config.device.isMobileDevice && navigator.userAgent.match(/Chrome/i);
|
|
|
|
// Creates an input who will receive the barcode scanner value.
|
|
this.$barcodeInput = $('<input/>', {
|
|
name: 'barcode',
|
|
type: 'text',
|
|
css: {
|
|
'position': 'fixed',
|
|
'top': '50%',
|
|
'transform': 'translateY(-50%)',
|
|
'z-index': '-1',
|
|
'opacity': '0',
|
|
},
|
|
});
|
|
// Avoid to show autocomplete for a non appearing input
|
|
this.$barcodeInput.attr('autocomplete', 'off');
|
|
|
|
this.__blurBarcodeInput = _.debounce(this._blurBarcodeInput, this.inputTimeOut);
|
|
},
|
|
|
|
handle_buffered_keys: function() {
|
|
var str = this.buffered_key_events.reduce(function(memo, e) { return memo + String.fromCharCode(e.which) }, '');
|
|
var match = str.match(this.regexp);
|
|
|
|
if (match) {
|
|
var barcode = match[1];
|
|
|
|
// Send the target in case there are several barcode widgets on the same page (e.g.
|
|
// registering the lot numbers in a stock picking)
|
|
core.bus.trigger('barcode_scanned', barcode, this.buffered_key_events[0].target);
|
|
|
|
// Dispatch a barcode_scanned DOM event to elements that have barcode_events="true" set.
|
|
if (this.buffered_key_events[0].target.getAttribute("barcode_events") === "true")
|
|
$(this.buffered_key_events[0].target).trigger('barcode_scanned', barcode);
|
|
} else {
|
|
this.resend_buffered_keys();
|
|
}
|
|
|
|
this.buffered_key_events = [];
|
|
},
|
|
|
|
resend_buffered_keys: function() {
|
|
var old_event, new_event;
|
|
for(var i = 0; i < this.buffered_key_events.length; i++) {
|
|
old_event = this.buffered_key_events[i];
|
|
|
|
if(old_event.which !== 13) { // ignore returns
|
|
// We do not create a 'real' keypress event through
|
|
// eg. KeyboardEvent because there are several issues
|
|
// with them that make them very different from
|
|
// genuine keypresses. Chrome per example has had a
|
|
// bug for the longest time that causes keyCode and
|
|
// charCode to not be set for events created this way:
|
|
// https://bugs.webkit.org/show_bug.cgi?id=16735
|
|
var params = {
|
|
'bubbles': old_event.bubbles,
|
|
'cancelable': old_event.cancelable,
|
|
};
|
|
new_event = $.Event('keypress', params);
|
|
new_event.viewArg = old_event.viewArg;
|
|
new_event.ctrl = old_event.ctrl;
|
|
new_event.alt = old_event.alt;
|
|
new_event.shift = old_event.shift;
|
|
new_event.meta = old_event.meta;
|
|
new_event.char = old_event.char;
|
|
new_event.key = old_event.key;
|
|
new_event.charCode = old_event.charCode;
|
|
new_event.keyCode = old_event.keyCode || old_event.which; // Firefox doesn't set keyCode for keypresses, only keyup/down
|
|
new_event.which = old_event.which;
|
|
new_event.dispatched_by_barcode_reader = true;
|
|
|
|
$(old_event.target).trigger(new_event);
|
|
}
|
|
}
|
|
},
|
|
|
|
element_is_editable: function(element) {
|
|
return $(element).is('input,textarea,[contenteditable="true"]');
|
|
},
|
|
|
|
// This checks that a keypress event is either ESC, TAB, an arrow
|
|
// key or a function key. This is Firefox specific, in Chrom{e,ium}
|
|
// keypress events are not fired for these types of keys, only
|
|
// keyup/keydown.
|
|
is_special_key: function(e) {
|
|
if (e.key === "ArrowLeft" || e.key === "ArrowRight" ||
|
|
e.key === "ArrowUp" || e.key === "ArrowDown" ||
|
|
e.key === "Escape" || e.key === "Tab" ||
|
|
e.key === "Backspace" || e.key === "Delete" ||
|
|
e.key === "Home" || e.key === "End" ||
|
|
e.key === "PageUp" || e.key === "PageDown" ||
|
|
e.key === "Unidentified" || /F\d\d?/.test(e.key)) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
// The keydown and keyup handlers are here to disallow key
|
|
// repeat. When preventDefault() is called on a keydown event
|
|
// the keypress that normally follows is cancelled.
|
|
keydown_handler: function(e){
|
|
if (this.key_pressed[e.which]) {
|
|
e.preventDefault();
|
|
} else {
|
|
this.key_pressed[e.which] = true;
|
|
}
|
|
},
|
|
|
|
keyup_handler: function(e){
|
|
this.key_pressed[e.which] = false;
|
|
},
|
|
|
|
handler: function(e){
|
|
// Don't catch events we resent
|
|
if (e.dispatched_by_barcode_reader)
|
|
return;
|
|
// Don't catch non-printable keys for which Firefox triggers a keypress
|
|
if (this.is_special_key(e))
|
|
return;
|
|
// Don't catch keypresses which could have a UX purpose (like shortcuts)
|
|
if (e.ctrlKey || e.metaKey || e.altKey)
|
|
return;
|
|
// Don't catch Return when nothing is buffered. This way users
|
|
// can still use Return to 'click' on focused buttons or links.
|
|
if (e.which === 13 && this.buffered_key_events.length === 0)
|
|
return;
|
|
// Don't catch events targeting elements that are editable because we
|
|
// 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 <input> element.
|
|
if ((this.element_is_editable(e.target) && !$(e.target).data('enableBarcode')) && e.target.getAttribute("barcode_events") !== "true")
|
|
return;
|
|
|
|
// Catch and buffer the event
|
|
this.buffered_key_events.push(e);
|
|
e.preventDefault();
|
|
e.stopImmediatePropagation();
|
|
|
|
// Handle buffered keys immediately if the keypress marks the end
|
|
// of a barcode or after x milliseconds without a new keypress
|
|
clearTimeout(this.timeout);
|
|
if (String.fromCharCode(e.which).match(this.suffix)) {
|
|
this.handle_buffered_keys();
|
|
} else {
|
|
this.timeout = setTimeout(_.bind(this.handle_buffered_keys, this), this.max_time_between_keys_in_ms);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 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 ($(document.activeElement).not('input:text, textarea, [contenteditable], ' +
|
|
'[type="email"], [type="number"], [type="password"], [type="tel"], [type="search"]').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.__blurBarcodeInput();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 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._blurBarcodeInput();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Removes the value and focus from the barcode input.
|
|
* If nothing happens, the focus will be lost and
|
|
* the virtual keyboard on mobile devices will be closed.
|
|
*
|
|
* @private
|
|
*/
|
|
_blurBarcodeInput: function () {
|
|
// Close the virtual keyboard on mobile browsers
|
|
// FIXME: actually we can't prevent keyboard from opening
|
|
this.$barcodeInput.val('').blur();
|
|
},
|
|
|
|
start: function(prevent_key_repeat){
|
|
// 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);
|
|
}
|
|
},
|
|
|
|
stop: function(){
|
|
$('body').off("keypress", this.__handler);
|
|
$('body').off("keydown", this.__keydown_handler);
|
|
$('body').off('keyup', this.__keyup_handler);
|
|
},
|
|
});
|
|
|
|
return {
|
|
/** Singleton that emits barcode_scanned events on core.bus */
|
|
BarcodeEvents: new BarcodeEvents(),
|
|
/**
|
|
* List of barcode prefixes that are reserved for internal purposes
|
|
* @type Array
|
|
*/
|
|
ReservedBarcodePrefixes: ['O-CMD'],
|
|
};
|
|
|
|
});
|