[JS] Handle correctly choice widgets where the display and the export values are different (issue #15815)
This commit is contained in:
parent
64786b4c93
commit
0c1ec946aa
@ -1978,7 +1978,15 @@ class WidgetAnnotation extends Annotation {
|
|||||||
|
|
||||||
assert(typeof value === "string", "Expected `value` to be a string.");
|
assert(typeof value === "string", "Expected `value` to be a string.");
|
||||||
|
|
||||||
value = value.trim();
|
if (!this.data.combo) {
|
||||||
|
value = value.trim();
|
||||||
|
} else {
|
||||||
|
// The value is supposed to be one of the exportValue.
|
||||||
|
const option =
|
||||||
|
this.data.options.find(({ exportValue }) => value === exportValue) ||
|
||||||
|
this.data.options[0];
|
||||||
|
value = (option && option.displayValue) || "";
|
||||||
|
}
|
||||||
|
|
||||||
if (value === "") {
|
if (value === "") {
|
||||||
// the field is empty: nothing to render
|
// the field is empty: nothing to render
|
||||||
|
@ -1612,10 +1612,10 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
selectElement.addEventListener("input", removeEmptyEntry);
|
selectElement.addEventListener("input", removeEmptyEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
const getValue = (event, isExport) => {
|
const getValue = isExport => {
|
||||||
const name = isExport ? "value" : "textContent";
|
const name = isExport ? "value" : "textContent";
|
||||||
const options = event.target.options;
|
const { options, multiple } = selectElement;
|
||||||
if (!event.target.multiple) {
|
if (!multiple) {
|
||||||
return options.selectedIndex === -1
|
return options.selectedIndex === -1
|
||||||
? null
|
? null
|
||||||
: options[options.selectedIndex][name];
|
: options[options.selectedIndex][name];
|
||||||
@ -1625,6 +1625,8 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
.map(option => option[name]);
|
.map(option => option[name]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let selectedValues = getValue(/* isExport */ false);
|
||||||
|
|
||||||
const getItems = event => {
|
const getItems = event => {
|
||||||
const options = event.target.options;
|
const options = event.target.options;
|
||||||
return Array.prototype.map.call(options, option => {
|
return Array.prototype.map.call(options, option => {
|
||||||
@ -1643,8 +1645,9 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
option.selected = values.has(option.value);
|
option.selected = values.has(option.value);
|
||||||
}
|
}
|
||||||
storage.setValue(id, {
|
storage.setValue(id, {
|
||||||
value: getValue(event, /* isExport */ true),
|
value: getValue(/* isExport */ true),
|
||||||
});
|
});
|
||||||
|
selectedValues = getValue(/* isExport */ false);
|
||||||
},
|
},
|
||||||
multipleSelection(event) {
|
multipleSelection(event) {
|
||||||
selectElement.multiple = true;
|
selectElement.multiple = true;
|
||||||
@ -1664,15 +1667,17 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
storage.setValue(id, {
|
storage.setValue(id, {
|
||||||
value: getValue(event, /* isExport */ true),
|
value: getValue(/* isExport */ true),
|
||||||
items: getItems(event),
|
items: getItems(event),
|
||||||
});
|
});
|
||||||
|
selectedValues = getValue(/* isExport */ false);
|
||||||
},
|
},
|
||||||
clear(event) {
|
clear(event) {
|
||||||
while (selectElement.length !== 0) {
|
while (selectElement.length !== 0) {
|
||||||
selectElement.remove(0);
|
selectElement.remove(0);
|
||||||
}
|
}
|
||||||
storage.setValue(id, { value: null, items: [] });
|
storage.setValue(id, { value: null, items: [] });
|
||||||
|
selectedValues = getValue(/* isExport */ false);
|
||||||
},
|
},
|
||||||
insert(event) {
|
insert(event) {
|
||||||
const { index, displayValue, exportValue } = event.detail.insert;
|
const { index, displayValue, exportValue } = event.detail.insert;
|
||||||
@ -1687,9 +1692,10 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
selectElement.append(optionElement);
|
selectElement.append(optionElement);
|
||||||
}
|
}
|
||||||
storage.setValue(id, {
|
storage.setValue(id, {
|
||||||
value: getValue(event, /* isExport */ true),
|
value: getValue(/* isExport */ true),
|
||||||
items: getItems(event),
|
items: getItems(event),
|
||||||
});
|
});
|
||||||
|
selectedValues = getValue(/* isExport */ false);
|
||||||
},
|
},
|
||||||
items(event) {
|
items(event) {
|
||||||
const { items } = event.detail;
|
const { items } = event.detail;
|
||||||
@ -1707,9 +1713,10 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
selectElement.options[0].selected = true;
|
selectElement.options[0].selected = true;
|
||||||
}
|
}
|
||||||
storage.setValue(id, {
|
storage.setValue(id, {
|
||||||
value: getValue(event, /* isExport */ true),
|
value: getValue(/* isExport */ true),
|
||||||
items: getItems(event),
|
items: getItems(event),
|
||||||
});
|
});
|
||||||
|
selectedValues = getValue(/* isExport */ false);
|
||||||
},
|
},
|
||||||
indices(event) {
|
indices(event) {
|
||||||
const indices = new Set(event.detail.indices);
|
const indices = new Set(event.detail.indices);
|
||||||
@ -1717,8 +1724,9 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
option.selected = indices.has(option.index);
|
option.selected = indices.has(option.index);
|
||||||
}
|
}
|
||||||
storage.setValue(id, {
|
storage.setValue(id, {
|
||||||
value: getValue(event, /* isExport */ true),
|
value: getValue(/* isExport */ true),
|
||||||
});
|
});
|
||||||
|
selectedValues = getValue(/* isExport */ false);
|
||||||
},
|
},
|
||||||
editable(event) {
|
editable(event) {
|
||||||
event.target.disabled = !event.detail.editable;
|
event.target.disabled = !event.detail.editable;
|
||||||
@ -1728,18 +1736,19 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
});
|
});
|
||||||
|
|
||||||
selectElement.addEventListener("input", event => {
|
selectElement.addEventListener("input", event => {
|
||||||
const exportValue = getValue(event, /* isExport */ true);
|
const exportValue = getValue(/* isExport */ true);
|
||||||
const value = getValue(event, /* isExport */ false);
|
|
||||||
storage.setValue(id, { value: exportValue });
|
storage.setValue(id, { value: exportValue });
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
|
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
|
||||||
source: this,
|
source: this,
|
||||||
detail: {
|
detail: {
|
||||||
id,
|
id,
|
||||||
name: "Keystroke",
|
name: "Keystroke",
|
||||||
value,
|
value: selectedValues,
|
||||||
changeEx: exportValue,
|
changeEx: exportValue,
|
||||||
willCommit: true,
|
willCommit: false,
|
||||||
commitKey: 1,
|
commitKey: 1,
|
||||||
keyDown: false,
|
keyDown: false,
|
||||||
},
|
},
|
||||||
@ -1761,7 +1770,7 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
selectElement.addEventListener("input", function (event) {
|
selectElement.addEventListener("input", function (event) {
|
||||||
storage.setValue(id, { value: getValue(event, /* isExport */ true) });
|
storage.setValue(id, { value: getValue(/* isExport */ true) });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,6 +137,7 @@ class EventDispatcher {
|
|||||||
case "Keystroke":
|
case "Keystroke":
|
||||||
savedChange = {
|
savedChange = {
|
||||||
value: event.value,
|
value: event.value,
|
||||||
|
changeEx: event.changeEx,
|
||||||
change: event.change,
|
change: event.change,
|
||||||
selStart: event.selStart,
|
selStart: event.selStart,
|
||||||
selEnd: event.selEnd,
|
selEnd: event.selEnd,
|
||||||
@ -170,6 +171,16 @@ class EventDispatcher {
|
|||||||
if (event.willCommit) {
|
if (event.willCommit) {
|
||||||
this.runValidation(source, event);
|
this.runValidation(source, event);
|
||||||
} else {
|
} else {
|
||||||
|
if (source.obj._isChoice) {
|
||||||
|
source.obj.value = savedChange.changeEx;
|
||||||
|
source.obj._send({
|
||||||
|
id: source.obj._id,
|
||||||
|
siblings: source.obj._siblings,
|
||||||
|
value: source.obj.value,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const value = (source.obj.value = this.mergeChange(event));
|
const value = (source.obj.value = this.mergeChange(event));
|
||||||
let selStart, selEnd;
|
let selStart, selEnd;
|
||||||
if (
|
if (
|
||||||
|
@ -242,6 +242,11 @@ class Field extends PDFObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set value(value) {
|
set value(value) {
|
||||||
|
if (this._isChoice) {
|
||||||
|
this._setChoiceValue(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (value === "") {
|
if (value === "") {
|
||||||
this._value = "";
|
this._value = "";
|
||||||
} else if (typeof value === "string") {
|
} else if (typeof value === "string") {
|
||||||
@ -260,23 +265,37 @@ class Field extends PDFObject {
|
|||||||
} else {
|
} else {
|
||||||
this._value = value;
|
this._value = value;
|
||||||
}
|
}
|
||||||
if (this._isChoice) {
|
}
|
||||||
if (this.multipleSelection) {
|
|
||||||
const values = new Set(value);
|
_setChoiceValue(value) {
|
||||||
if (Array.isArray(this._currentValueIndices)) {
|
if (this.multipleSelection) {
|
||||||
this._currentValueIndices.length = 0;
|
if (!Array.isArray(value)) {
|
||||||
} else {
|
value = [value];
|
||||||
this._currentValueIndices = [];
|
}
|
||||||
}
|
const values = new Set(value);
|
||||||
this._items.forEach(({ displayValue }, i) => {
|
if (Array.isArray(this._currentValueIndices)) {
|
||||||
if (values.has(displayValue)) {
|
this._currentValueIndices.length = 0;
|
||||||
this._currentValueIndices.push(i);
|
this._value.length = 0;
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
this._currentValueIndices = this._items.findIndex(
|
this._currentValueIndices = [];
|
||||||
({ displayValue }) => value === displayValue
|
this._value = [];
|
||||||
);
|
}
|
||||||
|
this._items.forEach((item, i) => {
|
||||||
|
if (values.has(item.exportValue)) {
|
||||||
|
this._currentValueIndices.push(i);
|
||||||
|
this._value.push(item.exportValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value = value[0];
|
||||||
|
}
|
||||||
|
const index = this._items.findIndex(
|
||||||
|
({ exportValue }) => value === exportValue
|
||||||
|
);
|
||||||
|
if (index !== -1) {
|
||||||
|
this._currentValueIndices = index;
|
||||||
|
this._value = this._items[index].exportValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1651,4 +1651,54 @@ describe("Interaction", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("in issue15815.pdf", () => {
|
||||||
|
let pages;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
pages = await loadAndWait("issue15815.pdf", getSelector("24R"));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await closePages(pages);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("must check field value is correctly updated when committed with ENTER key", async () => {
|
||||||
|
await Promise.all(
|
||||||
|
pages.map(async ([browserName, page]) => {
|
||||||
|
await page.waitForFunction(
|
||||||
|
"window.PDFViewerApplication.scriptingReady === true"
|
||||||
|
);
|
||||||
|
|
||||||
|
let value = "A";
|
||||||
|
for (const [displayValue, exportValue] of [
|
||||||
|
["B", "x2"],
|
||||||
|
["C", "x3"],
|
||||||
|
["A", "x1"],
|
||||||
|
]) {
|
||||||
|
await clearInput(page, getSelector("27R"));
|
||||||
|
await page.select(getSelector("24R"), exportValue);
|
||||||
|
await page.waitForFunction(
|
||||||
|
`${getQuerySelector("27R")}.value !== ""`
|
||||||
|
);
|
||||||
|
const text = await page.$eval(getSelector("27R"), el => el.value);
|
||||||
|
expect(text)
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toEqual(`value=${value}, changeEx=${exportValue}`);
|
||||||
|
value = displayValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const exportValue of ["x3", "x2", "x1"]) {
|
||||||
|
await clearInput(page, getSelector("27R"));
|
||||||
|
await page.type(getSelector("27R"), exportValue);
|
||||||
|
await page.click("[data-annotation-id='28R']");
|
||||||
|
await page.waitForTimeout(10);
|
||||||
|
|
||||||
|
value = await page.$eval(getSelector("24R"), el => el.value);
|
||||||
|
expect(value).withContext(`In ${browserName}`).toEqual(exportValue);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -561,3 +561,4 @@
|
|||||||
!issue15753.pdf
|
!issue15753.pdf
|
||||||
!issue15789.pdf
|
!issue15789.pdf
|
||||||
!fields_order.pdf
|
!fields_order.pdf
|
||||||
|
!issue15815.pdf
|
||||||
|
BIN
test/pdfs/issue15815.pdf
Executable file
BIN
test/pdfs/issue15815.pdf
Executable file
Binary file not shown.
@ -7240,5 +7240,32 @@
|
|||||||
"value": "مرحبا بالعالم"
|
"value": "مرحبا بالعالم"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "issue15815-print",
|
||||||
|
"file": "pdfs/issue15815.pdf",
|
||||||
|
"md5": "48b8b057954d5b773421ac03b7fcd738",
|
||||||
|
"rounds": 1,
|
||||||
|
"type": "eq",
|
||||||
|
"print": true,
|
||||||
|
"annotationStorage": {
|
||||||
|
"24R": {
|
||||||
|
"value": "x3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "issue15815-save-print",
|
||||||
|
"file": "pdfs/issue15815.pdf",
|
||||||
|
"md5": "48b8b057954d5b773421ac03b7fcd738",
|
||||||
|
"rounds": 1,
|
||||||
|
"type": "eq",
|
||||||
|
"save": true,
|
||||||
|
"print": true,
|
||||||
|
"annotationStorage": {
|
||||||
|
"24R": {
|
||||||
|
"value": "x2"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user