1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-22 16:18:08 +02:00

AcroForm: Add support for ResetForm action

- it aims to fix #12721.
  - Thanks to PR #14023, we've now the fieldObjects in the annotation layer so we can easily map fields names on their id if needed.
  - Reset values in the storage, in the JS sandbox and in the visible html elements.
This commit is contained in:
Calixte Denizet 2021-09-26 21:04:11 +02:00
parent db7c91e7b1
commit aecbd7cd89
7 changed files with 302 additions and 15 deletions

View file

@ -1328,6 +1328,20 @@ class Catalog {
const actionName = actionType.name;
switch (actionName) {
case "ResetForm":
const flags = action.get("Flags");
const include = ((isNum(flags) ? flags : 0) & 1) === 0;
const fields = [];
const refs = [];
for (const obj of action.get("Fields") || []) {
if (isRef(obj)) {
refs.push(obj.toString());
} else if (isString(obj)) {
fields.push(stringToPDFString(obj));
}
}
resultObj.resetForm = { fields, refs, include };
break;
case "URI":
url = action.get("URI");
if (url instanceof Name) {
@ -1405,11 +1419,7 @@ class Catalog {
}
/* falls through */
default:
if (
actionName === "JavaScript" ||
actionName === "ResetForm" ||
actionName === "SubmitForm"
) {
if (actionName === "JavaScript" || actionName === "SubmitForm") {
// Don't bother the user with a warning for actions that require
// scripting support, since those will be handled separately.
break;

View file

@ -429,6 +429,7 @@ class LinkAnnotationElement extends AnnotationElement {
parameters.data.dest ||
parameters.data.action ||
parameters.data.isTooltipOnly ||
parameters.data.resetForm ||
(parameters.data.actions &&
(parameters.data.actions.Action ||
parameters.data.actions["Mouse Up"] ||
@ -454,17 +455,25 @@ class LinkAnnotationElement extends AnnotationElement {
this._bindNamedAction(link, data.action);
} else if (data.dest) {
this._bindLink(link, data.dest);
} else if (
data.actions &&
(data.actions.Action ||
data.actions["Mouse Up"] ||
data.actions["Mouse Down"]) &&
this.enableScripting &&
this.hasJSActions
) {
this._bindJSAction(link, data);
} else {
this._bindLink(link, "");
let hasClickAction = false;
if (
data.actions &&
(data.actions.Action ||
data.actions["Mouse Up"] ||
data.actions["Mouse Down"]) &&
this.enableScripting &&
this.hasJSActions
) {
hasClickAction = true;
this._bindJSAction(link, data);
}
if (data.resetForm) {
this._bindResetFormAction(link, data.resetForm);
} else if (!hasClickAction) {
this._bindLink(link, "");
}
}
if (this.quadrilaterals) {
@ -557,6 +566,106 @@ class LinkAnnotationElement extends AnnotationElement {
}
link.className = "internalLink";
}
_bindResetFormAction(link, resetForm) {
const otherClickAction = link.onclick;
if (!otherClickAction) {
link.href = this.linkService.getAnchorUrl("");
}
link.className = "internalLink";
if (!this._fieldObjects) {
warn(
`_bindResetFormAction - "resetForm" action not supported, ` +
"ensure that the `fieldObjects` parameter is provided."
);
if (!otherClickAction) {
link.onclick = () => false;
}
return;
}
link.onclick = () => {
if (otherClickAction) {
otherClickAction();
}
const {
fields: resetFormFields,
refs: resetFormRefs,
include,
} = resetForm;
const allFields = [];
if (resetFormFields.length !== 0 || resetFormRefs.length !== 0) {
const fieldIds = new Set(resetFormRefs);
for (const fieldName of resetFormFields) {
const fields = this._fieldObjects[fieldName] || [];
for (const { id } of fields) {
fieldIds.add(id);
}
}
for (const fields of Object.values(this._fieldObjects)) {
for (const field of fields) {
if (fieldIds.has(field.id) === include) {
allFields.push(field);
}
}
}
} else {
for (const fields of Object.values(this._fieldObjects)) {
allFields.push(...fields);
}
}
const storage = this.annotationStorage;
const allIds = [];
for (const field of allFields) {
const { id } = field;
allIds.push(id);
switch (field.type) {
case "text": {
const value = field.defaultValue || "";
storage.setValue(id, { value, valueAsString: value });
break;
}
case "checkbox":
case "radiobutton": {
const value = field.defaultValue === field.exportValues;
storage.setValue(id, { value });
break;
}
case "combobox":
case "listbox": {
const value = field.defaultValue || "";
storage.setValue(id, { value });
break;
}
default:
continue;
}
const domElement = document.getElementById(id);
if (!domElement || !GetElementsByNameSet.has(domElement)) {
continue;
}
domElement.dispatchEvent(new Event("resetform"));
}
if (this.enableScripting) {
// Update the values in the sandbox.
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
source: this,
detail: {
id: "app",
ids: allIds,
name: "ResetForm",
},
});
}
return false;
};
}
}
class TextAnnotationElement extends AnnotationElement {
@ -804,6 +913,12 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
);
});
element.addEventListener("resetform", event => {
const defaultValue = this.data.defaultFieldValue || "";
element.value = elementData.userValue = defaultValue;
delete elementData.formattedValue;
});
let blurListener = event => {
if (elementData.formattedValue) {
event.target.value = elementData.formattedValue;
@ -1057,6 +1172,11 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
storage.setValue(id, { value: checked });
});
element.addEventListener("resetform", event => {
const defaultValue = data.defaultFieldValue || "Off";
event.target.checked = defaultValue === data.exportValue;
});
if (this.enableScripting && this.hasJSActions) {
element.addEventListener("updatefromsandbox", jsEvent => {
const actions = {
@ -1129,6 +1249,14 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
storage.setValue(id, { value: checked });
});
element.addEventListener("resetform", event => {
const defaultValue = data.defaultFieldValue;
event.target.checked =
defaultValue !== null &&
defaultValue !== undefined &&
defaultValue === data.buttonValue;
});
if (this.enableScripting && this.hasJSActions) {
const pdfButtonValue = data.buttonValue;
element.addEventListener("updatefromsandbox", jsEvent => {
@ -1231,6 +1359,13 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
}
}
selectElement.addEventListener("resetform", event => {
const defaultValue = this.data.defaultFieldValue;
for (const option of selectElement.options) {
option.selected = option.value === defaultValue;
}
});
// Insert the options into the choice field.
for (const option of this.data.options) {
const optionElement = document.createElement("option");

View file

@ -79,6 +79,13 @@ class EventDispatcher {
baseEvent.actions,
baseEvent.pageNumber
);
} else if (id === "app" && baseEvent.name === "ResetForm") {
for (const fieldId of baseEvent.ids) {
const obj = this._objects[fieldId];
if (obj) {
obj.obj._reset();
}
}
}
return;
}

View file

@ -476,6 +476,10 @@ class Field extends PDFObject {
return false;
}
_reset() {
this.value = this.valueAsString = this.defaultValue;
}
_runActions(event) {
const eventName = event.name;
if (!this._actions.has(eventName)) {