[REF] barcodes,web: barcode_handler and field_float_scannable to wowl
closes odoo/odoo#102150 X-original-commit: c57a0ada02bfc1f02b3516f92f87b510942c5a87 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
@@ -15,13 +15,9 @@
|
||||
'web.assets_backend': [
|
||||
'barcodes/static/src/**/*',
|
||||
],
|
||||
'web.qunit_suite_tests': [
|
||||
'barcodes/static/tests/barcode_tests.js',
|
||||
'barcodes/static/tests/barcode_parser_tests.js',
|
||||
],
|
||||
'web.qunit_mobile_suite_tests': [
|
||||
'barcodes/static/tests/barcode_mobile_tests.js',
|
||||
],
|
||||
'web.tests_assets': ['barcodes/static/tests/helpers.js'],
|
||||
'web.qunit_suite_tests': ['barcodes/static/tests/basic/*.js'],
|
||||
'web.qunit_mobile_suite_tests': ['barcodes/static/tests/mobile/*.js'],
|
||||
},
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { standardFieldProps } from "@web/views/fields/standard_field_props";
|
||||
import { useBus, useService } from "@web/core/utils/hooks";
|
||||
|
||||
const { Component, xml } = owl;
|
||||
|
||||
export class BarcodeHandlerField extends Component {
|
||||
setup() {
|
||||
const barcode = useService("barcode");
|
||||
useBus(barcode.bus, "barcode_scanned", this.onBarcodeScanned);
|
||||
}
|
||||
onBarcodeScanned(event) {
|
||||
const { barcode } = event.detail;
|
||||
this.props.update(barcode);
|
||||
}
|
||||
}
|
||||
|
||||
BarcodeHandlerField.template = xml``;
|
||||
BarcodeHandlerField.props = { ...standardFieldProps };
|
||||
|
||||
registry.category("fields").add("barcode_handler", BarcodeHandlerField);
|
||||
@@ -43,7 +43,8 @@ export const barcodeService = {
|
||||
function handleBarcode(barcode, target) {
|
||||
bus.trigger('barcode_scanned', {barcode,target});
|
||||
if (target.getAttribute('barcode_events') === "true") {
|
||||
$(target).trigger('barcode_scanned', barcode);
|
||||
const barcodeScannedEvent = new CustomEvent("barcode_scanned", { detail: { barcode, target } });
|
||||
target.dispatchEvent(barcodeScannedEvent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { FloatField } from "@web/views/fields/float/float_field";
|
||||
|
||||
export class FloatScannableField extends FloatField {
|
||||
onBarcodeScanned() {
|
||||
this.inputRef.el.dispatchEvent(new InputEvent("input"));
|
||||
}
|
||||
}
|
||||
FloatScannableField.template = "barcodes.FloatScannableField";
|
||||
|
||||
registry.category("fields").add("field_float_scannable", FloatScannableField);
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="barcodes.FloatScannableField" t-inherit="web.FloatField" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//input" position="attributes">
|
||||
<attribute name="t-on-barcode_scanned">onBarcodeScanned</attribute>
|
||||
<attribute name="t-att-data-enable-barcode">true</attribute>
|
||||
<attribute name="barcode_events">true</attribute>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
+1
-18
@@ -7,34 +7,17 @@ const {barcodeRemapperService} = require("@barcodes/js/barcode_events");
|
||||
const { makeTestEnv } = require("@web/../tests/helpers/mock_env");
|
||||
const { registry } = require("@web/core/registry");
|
||||
const { mockTimeout } = require("@web/../tests/helpers/utils");
|
||||
const { simulateBarCode } = require("@barcodes/../tests/helpers");
|
||||
|
||||
var FormController = require('web.FormController');
|
||||
var FormView = require('web.FormView');
|
||||
var testUtils = require('web.test_utils');
|
||||
|
||||
var createView = testUtils.createView;
|
||||
var triggerEvent = testUtils.dom.triggerEvent;
|
||||
var core = require('web.core');
|
||||
|
||||
const maxTimeBetweenKeysInMs = barcodeService.maxTimeBetweenKeysInMs;
|
||||
|
||||
function simulateBarCode(chars, target = document.body) {
|
||||
for (let char of chars) {
|
||||
let keycode;
|
||||
if (char === 'Enter') {
|
||||
keycode = $.ui.keyCode.ENTER;
|
||||
} else if (char === "Tab") {
|
||||
keycode = $.ui.keyCode.TAB;
|
||||
} else {
|
||||
keycode = char.charCodeAt(0);
|
||||
}
|
||||
triggerEvent(target, 'keydown', {
|
||||
key: char,
|
||||
keyCode: keycode,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QUnit.module('Barcodes', {
|
||||
beforeEach: function () {
|
||||
barcodeService.maxTimeBetweenKeysInMs = 0;
|
||||
@@ -0,0 +1,142 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { nextTick, patchWithCleanup, getFixture } from "@web/../tests/helpers/utils";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { barcodeGenericHandlers } from "@barcodes/barcode_handlers";
|
||||
import { barcodeService } from "@barcodes/barcode_service";
|
||||
import { simulateBarCode } from "../helpers";
|
||||
import { FormController } from "@web/views/form/form_controller";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const maxTimeBetweenKeysInMs = barcodeService.maxTimeBetweenKeysInMs;
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
|
||||
QUnit.module("Fields", (hooks) => {
|
||||
hooks.beforeEach(() => {
|
||||
target = getFixture();
|
||||
barcodeService.maxTimeBetweenKeysInMs = 0;
|
||||
registry.category("services").add("barcode", barcodeService, { force: true });
|
||||
registry
|
||||
.category("services")
|
||||
.add("barcode_autoclick", barcodeGenericHandlers, { force: true });
|
||||
|
||||
serverData = {
|
||||
models: {
|
||||
product: {
|
||||
fields: {
|
||||
name: { string: "Product name", type: "char" },
|
||||
float_field: { string: "Float", type: "float" },
|
||||
float_field_2: { string: "Float", type: "float" },
|
||||
barcode: { string: "Barcode", type: "char" },
|
||||
},
|
||||
records: [
|
||||
{ id: 1, name: "Large Cabinet", barcode: "1234567890" },
|
||||
{ id: 2, name: "Cabinet with Doors", barcode: "0987654321" },
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
setupViewRegistries();
|
||||
});
|
||||
hooks.afterEach(() => {
|
||||
barcodeService.maxTimeBetweenKeysInMs = maxTimeBetweenKeysInMs;
|
||||
});
|
||||
|
||||
QUnit.module("FloatScannableField");
|
||||
|
||||
QUnit.test("widget field_float_scannable", async function (assert) {
|
||||
serverData.models.product.records[0].float_field = 4;
|
||||
|
||||
const onBarcodeScanned = (event) => {
|
||||
assert.step(`barcode scanned ${event.detail.barcode}`);
|
||||
};
|
||||
|
||||
const view = await makeView({
|
||||
type: "form",
|
||||
resModel: "product",
|
||||
serverData,
|
||||
resId: 1,
|
||||
arch: /*xml*/ `
|
||||
<form>
|
||||
<field name="display_name"/>
|
||||
<field name="float_field" widget="field_float_scannable"/>
|
||||
<field name="float_field_2"/>
|
||||
</form>
|
||||
`,
|
||||
});
|
||||
view.env.services.barcode.bus.addEventListener("barcode_scanned", onBarcodeScanned);
|
||||
|
||||
const inputDiv1 = target.querySelector(".o_field_widget[name=float_field]");
|
||||
assert.strictEqual(
|
||||
inputDiv1.querySelector("input").value,
|
||||
"4.00",
|
||||
"should display the correct value"
|
||||
);
|
||||
|
||||
// we check here that a scan on the field_float_scannable widget triggers a
|
||||
// barcode event
|
||||
inputDiv1.querySelector("input").focus();
|
||||
simulateBarCode(["6", "0", "1", "6", "4", "7", "8", "5"], inputDiv1, "input");
|
||||
await nextTick();
|
||||
assert.verifySteps(["barcode scanned 60164785"]);
|
||||
assert.strictEqual(
|
||||
inputDiv1.querySelector("input"),
|
||||
document.activeElement,
|
||||
"float field should stay focused"
|
||||
);
|
||||
|
||||
// we check here that a scan on the field without widget does not trigger a
|
||||
// barcode event
|
||||
const inputDiv2 = target.querySelector(".o_field_widget[name=float_field_2]");
|
||||
inputDiv2.querySelector("input").focus();
|
||||
simulateBarCode(["6", "0", "1", "6", "4", "7", "8", "5"], inputDiv2, "input");
|
||||
await nextTick();
|
||||
assert.verifySteps([]);
|
||||
|
||||
view.env.services.barcode.bus.removeEventListener("barcode_scanned", onBarcodeScanned);
|
||||
});
|
||||
|
||||
QUnit.test("do no update form twice after a command barcode scanned", async function (assert) {
|
||||
assert.expect(5);
|
||||
|
||||
patchWithCleanup(FormController.prototype, {
|
||||
onPagerUpdate() {
|
||||
assert.step("update");
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "product",
|
||||
serverData,
|
||||
arch: /*xml*/ `
|
||||
<form>
|
||||
<field name="display_name"/>
|
||||
<field name="float_field" widget="field_float_scannable"/>
|
||||
</form>
|
||||
`,
|
||||
mockRPC(_route, args) {
|
||||
if (args.method === "read") {
|
||||
assert.step("read");
|
||||
}
|
||||
},
|
||||
resId: 1,
|
||||
resIds: [1, 2],
|
||||
});
|
||||
|
||||
assert.verifySteps(["read"], "update should not have been called yet");
|
||||
|
||||
// switch to next record
|
||||
simulateBarCode(
|
||||
["O", "-", "C", "M", "D", ".", "N", "E", "X", "T", "Enter"],
|
||||
document.activeElement
|
||||
);
|
||||
await nextTick();
|
||||
// a first update is done to reload the data (thus followed by a read), but
|
||||
// update shouldn't be called afterwards
|
||||
assert.verifySteps(["update", "read"]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { triggerEvent } from "@web/../tests/helpers/utils";
|
||||
|
||||
export function simulateBarCode(chars, target = document.body, selector = undefined) {
|
||||
for (let char of chars) {
|
||||
let keycode;
|
||||
if (char === "Enter") {
|
||||
keycode = $.ui.keyCode.ENTER;
|
||||
} else if (char === "Tab") {
|
||||
keycode = $.ui.keyCode.TAB;
|
||||
} else {
|
||||
keycode = char.charCodeAt(0);
|
||||
}
|
||||
triggerEvent(target, selector, "keydown", {
|
||||
key: char,
|
||||
keyCode: keycode,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ const { Component } = owl;
|
||||
|
||||
export class FloatField extends Component {
|
||||
setup() {
|
||||
useInputField({
|
||||
this.inputRef = useInputField({
|
||||
getValue: () => this.formattedValue,
|
||||
refName: "numpadDecimal",
|
||||
parse: (v) => this.parse(v),
|
||||
|
||||
@@ -152,4 +152,6 @@ export function useInputField(params) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inputRef;
|
||||
}
|
||||
|
||||
@@ -201,19 +201,7 @@ export class FormController extends Component {
|
||||
offset: resIds.indexOf(this.model.root.resId),
|
||||
limit: 1,
|
||||
total: resIds.length,
|
||||
onUpdate: async ({ offset }) => {
|
||||
await this.model.root.askChanges(); // ensures that isDirty is correct
|
||||
let canProceed = true;
|
||||
if (this.model.root.isDirty) {
|
||||
canProceed = await this.model.root.save({
|
||||
stayInEdition: true,
|
||||
useSaveErrorDialog: true,
|
||||
});
|
||||
}
|
||||
if (canProceed) {
|
||||
return this.model.load({ resId: resIds[offset] });
|
||||
}
|
||||
},
|
||||
onUpdate: ({ offset }) => this.onPagerUpdate({ offset, resIds }),
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -263,6 +251,20 @@ export class FormController extends Component {
|
||||
return this.model.root.data.display_name || this.env._t("New");
|
||||
}
|
||||
|
||||
async onPagerUpdate({ offset, resIds }) {
|
||||
await this.model.root.askChanges(); // ensures that isDirty is correct
|
||||
let canProceed = true;
|
||||
if (this.model.root.isDirty) {
|
||||
canProceed = await this.model.root.save({
|
||||
stayInEdition: true,
|
||||
useSaveErrorDialog: true,
|
||||
});
|
||||
}
|
||||
if (canProceed) {
|
||||
return this.model.load({ resId: resIds[offset] });
|
||||
}
|
||||
}
|
||||
|
||||
beforeUnload() {
|
||||
return this.model.root.urgentSave();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user