mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-22 16:18:08 +02:00
Merge pull request #12635 from calixteman/js_display_evts
JS -- Send events to the sandbox from annotation layer
This commit is contained in:
commit
3c603fb28b
11 changed files with 647 additions and 113 deletions
|
@ -365,7 +365,11 @@ class LinkAnnotationElement extends AnnotationElement {
|
|||
parameters.data.url ||
|
||||
parameters.data.dest ||
|
||||
parameters.data.action ||
|
||||
parameters.data.isTooltipOnly
|
||||
parameters.data.isTooltipOnly ||
|
||||
(parameters.data.actions &&
|
||||
(parameters.data.actions.Action ||
|
||||
parameters.data.actions.MouseUp ||
|
||||
parameters.data.actions.MouseDown))
|
||||
);
|
||||
super(parameters, { isRenderable, createQuadrilaterals: true });
|
||||
}
|
||||
|
@ -387,6 +391,13 @@ 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.MouseUp || data.actions.MouseDown) &&
|
||||
this.enableScripting &&
|
||||
this.hasJSActions
|
||||
) {
|
||||
this._bindJSAction(link);
|
||||
} else {
|
||||
this._bindLink(link, "");
|
||||
}
|
||||
|
@ -443,6 +454,42 @@ class LinkAnnotationElement extends AnnotationElement {
|
|||
};
|
||||
link.className = "internalLink";
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind JS actions to the link element.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} link
|
||||
* @param {Object} data
|
||||
* @memberof LinkAnnotationElement
|
||||
*/
|
||||
_bindJSAction(link) {
|
||||
link.href = this.linkService.getAnchorUrl("#");
|
||||
const { data } = this;
|
||||
const map = new Map([
|
||||
["Action", "onclick"],
|
||||
["MouseUp", "onmouseup"],
|
||||
["MouseDown", "onmousedown"],
|
||||
]);
|
||||
for (const name of Object.keys(data.actions)) {
|
||||
const jsName = map.get(name);
|
||||
if (!jsName) {
|
||||
continue;
|
||||
}
|
||||
link[jsName] = () => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("dispatchEventInSandbox", {
|
||||
detail: {
|
||||
id: data.id,
|
||||
name,
|
||||
},
|
||||
})
|
||||
);
|
||||
return false;
|
||||
};
|
||||
}
|
||||
link.className = "internalLink";
|
||||
}
|
||||
}
|
||||
|
||||
class TextAnnotationElement extends AnnotationElement {
|
||||
|
@ -488,6 +535,53 @@ class WidgetAnnotationElement extends AnnotationElement {
|
|||
|
||||
return this.container;
|
||||
}
|
||||
|
||||
_getKeyModifier(event) {
|
||||
return (
|
||||
(navigator.platform.includes("Win") && event.ctrlKey) ||
|
||||
(navigator.platform.includes("Mac") && event.metaKey)
|
||||
);
|
||||
}
|
||||
|
||||
_setEventListener(element, baseName, eventName, valueGetter) {
|
||||
if (this.data.actions && eventName.replace(" ", "") in this.data.actions) {
|
||||
if (baseName.includes("mouse")) {
|
||||
// Mouse events
|
||||
element.addEventListener(baseName, event => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("dispatchEventInSandbox", {
|
||||
detail: {
|
||||
id: this.data.id,
|
||||
name: eventName,
|
||||
value: valueGetter(event),
|
||||
shift: event.shiftKey,
|
||||
modifier: this._getKeyModifier(event),
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
} else {
|
||||
// Non mouse event
|
||||
element.addEventListener(baseName, event => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("dispatchEventInSandbox", {
|
||||
detail: {
|
||||
id: this.data.id,
|
||||
name: eventName,
|
||||
value: event.target.checked,
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_setEventListeners(element, names, getter) {
|
||||
for (const [baseName, eventName] of names) {
|
||||
this._setEventListener(element, baseName, eventName, getter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TextWidgetAnnotationElement extends WidgetAnnotationElement {
|
||||
|
@ -496,6 +590,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||
parameters.renderInteractiveForms ||
|
||||
(!parameters.data.hasAppearance && !!parameters.data.fieldValue);
|
||||
super(parameters, { isRenderable });
|
||||
this.mouseState = parameters.mouseState;
|
||||
}
|
||||
|
||||
render() {
|
||||
|
@ -513,6 +608,12 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||
const textContent = storage.getOrCreateValue(id, {
|
||||
value: this.data.fieldValue,
|
||||
}).value;
|
||||
const elementData = {
|
||||
userValue: null,
|
||||
formattedValue: null,
|
||||
beforeInputSelectionRange: null,
|
||||
beforeInputValue: null,
|
||||
};
|
||||
|
||||
if (this.data.multiLine) {
|
||||
element = document.createElement("textarea");
|
||||
|
@ -523,104 +624,196 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||
element.setAttribute("value", textContent);
|
||||
}
|
||||
|
||||
element.userValue = textContent;
|
||||
elementData.userValue = textContent;
|
||||
element.setAttribute("id", id);
|
||||
|
||||
element.addEventListener("input", function (event) {
|
||||
storage.setValue(id, { value: event.target.value });
|
||||
});
|
||||
|
||||
element.addEventListener("blur", function (event) {
|
||||
let blurListener = event => {
|
||||
if (elementData.formattedValue) {
|
||||
event.target.value = elementData.formattedValue;
|
||||
}
|
||||
event.target.setSelectionRange(0, 0);
|
||||
});
|
||||
elementData.beforeInputSelectionRange = null;
|
||||
};
|
||||
|
||||
if (this.enableScripting && this.hasJSActions) {
|
||||
element.addEventListener("focus", event => {
|
||||
if (event.target.userValue) {
|
||||
event.target.value = event.target.userValue;
|
||||
if (elementData.userValue) {
|
||||
event.target.value = elementData.userValue;
|
||||
}
|
||||
});
|
||||
|
||||
if (this.data.actions) {
|
||||
element.addEventListener("updateFromSandbox", function (event) {
|
||||
const detail = event.detail;
|
||||
const actions = {
|
||||
value() {
|
||||
const value = detail.value;
|
||||
if (value === undefined || value === null) {
|
||||
// remove data
|
||||
event.target.userValue = "";
|
||||
} else {
|
||||
event.target.userValue = value;
|
||||
}
|
||||
},
|
||||
valueAsString() {
|
||||
const value = detail.valueAsString;
|
||||
if (value === undefined || value === null) {
|
||||
// remove data
|
||||
event.target.value = "";
|
||||
} else {
|
||||
event.target.value = value;
|
||||
}
|
||||
storage.setValue(id, event.target.value);
|
||||
},
|
||||
focus() {
|
||||
event.target.focus({ preventScroll: false });
|
||||
},
|
||||
userName() {
|
||||
const tooltip = detail.userName;
|
||||
event.target.title = tooltip;
|
||||
},
|
||||
hidden() {
|
||||
event.target.style.display = detail.hidden ? "none" : "block";
|
||||
},
|
||||
editable() {
|
||||
event.target.disabled = !detail.editable;
|
||||
},
|
||||
selRange() {
|
||||
const [selStart, selEnd] = detail.selRange;
|
||||
if (selStart >= 0 && selEnd < event.target.value.length) {
|
||||
event.target.setSelectionRange(selStart, selEnd);
|
||||
}
|
||||
},
|
||||
strokeColor() {
|
||||
const color = detail.strokeColor;
|
||||
event.target.style.color = ColorConverters[`${color[0]}_HTML`](
|
||||
color.slice(1)
|
||||
);
|
||||
},
|
||||
};
|
||||
for (const name of Object.keys(detail)) {
|
||||
if (name in actions) {
|
||||
actions[name]();
|
||||
element.addEventListener("updateFromSandbox", function (event) {
|
||||
const { detail } = event;
|
||||
const actions = {
|
||||
value() {
|
||||
elementData.userValue = detail.value || "";
|
||||
storage.setValue(id, { value: elementData.userValue.toString() });
|
||||
},
|
||||
valueAsString() {
|
||||
elementData.formattedValue = detail.valueAsString || "";
|
||||
if (event.target !== document.activeElement) {
|
||||
// Input hasn't the focus so display formatted string
|
||||
event.target.value = elementData.formattedValue;
|
||||
}
|
||||
storage.setValue(id, {
|
||||
formattedValue: elementData.formattedValue,
|
||||
});
|
||||
},
|
||||
focus() {
|
||||
setTimeout(() => event.target.focus({ preventScroll: false }), 0);
|
||||
},
|
||||
userName() {
|
||||
// tooltip
|
||||
event.target.title = detail.userName;
|
||||
},
|
||||
hidden() {
|
||||
event.target.style.visibility = detail.hidden
|
||||
? "hidden"
|
||||
: "visible";
|
||||
storage.setValue(id, { hidden: detail.hidden });
|
||||
},
|
||||
editable() {
|
||||
event.target.disabled = !detail.editable;
|
||||
},
|
||||
selRange() {
|
||||
const [selStart, selEnd] = detail.selRange;
|
||||
if (selStart >= 0 && selEnd < event.target.value.length) {
|
||||
event.target.setSelectionRange(selStart, selEnd);
|
||||
}
|
||||
},
|
||||
strokeColor() {
|
||||
const color = detail.strokeColor;
|
||||
event.target.style.color = ColorConverters[`${color[0]}_HTML`](
|
||||
color.slice(1)
|
||||
);
|
||||
},
|
||||
};
|
||||
Object.keys(detail)
|
||||
.filter(name => name in actions)
|
||||
.forEach(name => actions[name]());
|
||||
});
|
||||
|
||||
if (this.data.actions) {
|
||||
// Even if the field hasn't any actions
|
||||
// leaving it can still trigger some actions with Calculate
|
||||
element.addEventListener("keydown", event => {
|
||||
elementData.beforeInputValue = event.target.value;
|
||||
// if the key is one of Escape, Enter or Tab
|
||||
// then the data are committed
|
||||
let commitKey = -1;
|
||||
if (event.key === "Escape") {
|
||||
commitKey = 0;
|
||||
} else if (event.key === "Enter") {
|
||||
commitKey = 2;
|
||||
} else if (event.key === "Tab") {
|
||||
commitKey = 3;
|
||||
}
|
||||
if (commitKey === -1) {
|
||||
return;
|
||||
}
|
||||
// Save the entered value
|
||||
elementData.userValue = event.target.value;
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("dispatchEventInSandbox", {
|
||||
detail: {
|
||||
id,
|
||||
name: "Keystroke",
|
||||
value: event.target.value,
|
||||
willCommit: true,
|
||||
commitKey,
|
||||
selStart: event.target.selectionStart,
|
||||
selEnd: event.target.selectionEnd,
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
const _blurListener = blurListener;
|
||||
blurListener = null;
|
||||
element.addEventListener("blur", event => {
|
||||
if (this.mouseState.isDown) {
|
||||
// Focus out using the mouse: data are committed
|
||||
elementData.userValue = event.target.value;
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("dispatchEventInSandbox", {
|
||||
detail: {
|
||||
id,
|
||||
name: "Keystroke",
|
||||
value: event.target.value,
|
||||
willCommit: true,
|
||||
commitKey: 1,
|
||||
selStart: event.target.selectionStart,
|
||||
selEnd: event.target.selectionEnd,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
_blurListener(event);
|
||||
});
|
||||
element.addEventListener("mousedown", event => {
|
||||
elementData.beforeInputValue = event.target.value;
|
||||
elementData.beforeInputSelectionRange = null;
|
||||
});
|
||||
element.addEventListener("keyup", event => {
|
||||
// keyup is triggered after input
|
||||
if (event.target.selectionStart === event.target.selectionEnd) {
|
||||
elementData.beforeInputSelectionRange = null;
|
||||
}
|
||||
});
|
||||
element.addEventListener("select", event => {
|
||||
elementData.beforeInputSelectionRange = [
|
||||
event.target.selectionStart,
|
||||
event.target.selectionEnd,
|
||||
];
|
||||
});
|
||||
|
||||
for (const eventType of Object.keys(this.data.actions)) {
|
||||
switch (eventType) {
|
||||
case "Format":
|
||||
element.addEventListener("change", function (event) {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("dispatchEventInSandbox", {
|
||||
detail: {
|
||||
id,
|
||||
name: "Keystroke",
|
||||
value: event.target.value,
|
||||
willCommit: true,
|
||||
commitKey: 1,
|
||||
selStart: event.target.selectionStart,
|
||||
selEnd: event.target.selectionEnd,
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
break;
|
||||
}
|
||||
if ("Keystroke" in this.data.actions) {
|
||||
// We should use beforeinput but this
|
||||
// event isn't available in Firefox
|
||||
element.addEventListener("input", event => {
|
||||
let selStart = -1;
|
||||
let selEnd = -1;
|
||||
if (elementData.beforeInputSelectionRange) {
|
||||
[selStart, selEnd] = elementData.beforeInputSelectionRange;
|
||||
}
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("dispatchEventInSandbox", {
|
||||
detail: {
|
||||
id,
|
||||
name: "Keystroke",
|
||||
value: elementData.beforeInputValue,
|
||||
change: event.data,
|
||||
willCommit: false,
|
||||
selStart,
|
||||
selEnd,
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
this._setEventListeners(
|
||||
element,
|
||||
[
|
||||
["focus", "Focus"],
|
||||
["blur", "Blur"],
|
||||
["mousedown", "Mouse Down"],
|
||||
["mouseenter", "Mouse Enter"],
|
||||
["mouseleave", "Mouse Exit"],
|
||||
["mouseup", "MouseUp"],
|
||||
],
|
||||
event => event.target.value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (blurListener) {
|
||||
element.addEventListener("blur", blurListener);
|
||||
}
|
||||
|
||||
element.disabled = this.data.readOnly;
|
||||
element.name = this.data.fieldName;
|
||||
|
||||
|
@ -715,6 +908,7 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||
if (value) {
|
||||
element.setAttribute("checked", true);
|
||||
}
|
||||
element.setAttribute("id", id);
|
||||
|
||||
element.addEventListener("change", function (event) {
|
||||
const name = event.target.name;
|
||||
|
@ -730,6 +924,48 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||
storage.setValue(id, { value: event.target.checked });
|
||||
});
|
||||
|
||||
if (this.enableScripting && this.hasJSActions) {
|
||||
element.addEventListener("updateFromSandbox", event => {
|
||||
const { detail } = event;
|
||||
const actions = {
|
||||
value() {
|
||||
event.target.checked = detail.value !== "Off";
|
||||
storage.setValue(id, { value: event.target.checked });
|
||||
},
|
||||
focus() {
|
||||
setTimeout(() => event.target.focus({ preventScroll: false }), 0);
|
||||
},
|
||||
hidden() {
|
||||
event.target.style.visibility = detail.hidden
|
||||
? "hidden"
|
||||
: "visible";
|
||||
storage.setValue(id, { hidden: detail.hidden });
|
||||
},
|
||||
editable() {
|
||||
event.target.disabled = !detail.editable;
|
||||
},
|
||||
};
|
||||
Object.keys(detail)
|
||||
.filter(name => name in actions)
|
||||
.forEach(name => actions[name]());
|
||||
});
|
||||
|
||||
this._setEventListeners(
|
||||
element,
|
||||
[
|
||||
["change", "Validate"],
|
||||
["change", "Action"],
|
||||
["focus", "Focus"],
|
||||
["blur", "Blur"],
|
||||
["mousedown", "Mouse Down"],
|
||||
["mouseenter", "Mouse Enter"],
|
||||
["mouseleave", "Mouse Exit"],
|
||||
["mouseup", "MouseUp"],
|
||||
],
|
||||
event => event.target.checked
|
||||
);
|
||||
}
|
||||
|
||||
this.container.appendChild(element);
|
||||
return this.container;
|
||||
}
|
||||
|
@ -756,20 +992,69 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||
if (value) {
|
||||
element.setAttribute("checked", true);
|
||||
}
|
||||
element.setAttribute("pdfButtonValue", data.buttonValue);
|
||||
element.setAttribute("id", id);
|
||||
|
||||
element.addEventListener("change", function (event) {
|
||||
const name = event.target.name;
|
||||
for (const radio of document.getElementsByName(name)) {
|
||||
if (radio !== event.target) {
|
||||
storage.setValue(
|
||||
radio.parentNode.getAttribute("data-annotation-id"),
|
||||
{ value: false }
|
||||
);
|
||||
const target = event.target;
|
||||
for (const radio of document.getElementsByName(event.target.name)) {
|
||||
if (radio !== target) {
|
||||
storage.setValue(radio.getAttribute("id"), { value: false });
|
||||
}
|
||||
}
|
||||
storage.setValue(id, { value: event.target.checked });
|
||||
storage.setValue(id, { value: target.checked });
|
||||
});
|
||||
|
||||
if (this.enableScripting && this.hasJSActions) {
|
||||
element.addEventListener("updateFromSandbox", event => {
|
||||
const { detail } = event;
|
||||
const actions = {
|
||||
value() {
|
||||
const fieldValue = detail.value;
|
||||
for (const radio of document.getElementsByName(event.target.name)) {
|
||||
const radioId = radio.getAttribute("id");
|
||||
if (fieldValue === radio.getAttribute("pdfButtonValue")) {
|
||||
radio.setAttribute("checked", true);
|
||||
storage.setValue(radioId, { value: true });
|
||||
} else {
|
||||
storage.setValue(radioId, { value: false });
|
||||
}
|
||||
}
|
||||
},
|
||||
focus() {
|
||||
setTimeout(() => event.target.focus({ preventScroll: false }), 0);
|
||||
},
|
||||
hidden() {
|
||||
event.target.style.visibility = detail.hidden
|
||||
? "hidden"
|
||||
: "visible";
|
||||
storage.setValue(id, { hidden: detail.hidden });
|
||||
},
|
||||
editable() {
|
||||
event.target.disabled = !detail.editable;
|
||||
},
|
||||
};
|
||||
Object.keys(detail)
|
||||
.filter(name => name in actions)
|
||||
.forEach(name => actions[name]());
|
||||
});
|
||||
|
||||
this._setEventListeners(
|
||||
element,
|
||||
[
|
||||
["change", "Validate"],
|
||||
["change", "Action"],
|
||||
["focus", "Focus"],
|
||||
["blur", "Blur"],
|
||||
["mousedown", "Mouse Down"],
|
||||
["mouseenter", "Mouse Enter"],
|
||||
["mouseleave", "Mouse Exit"],
|
||||
["mouseup", "MouseUp"],
|
||||
],
|
||||
event => event.target.checked
|
||||
);
|
||||
}
|
||||
|
||||
this.container.appendChild(element);
|
||||
return this.container;
|
||||
}
|
||||
|
@ -816,6 +1101,7 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||
const selectElement = document.createElement("select");
|
||||
selectElement.disabled = this.data.readOnly;
|
||||
selectElement.name = this.data.fieldName;
|
||||
selectElement.setAttribute("id", id);
|
||||
|
||||
if (!this.data.combo) {
|
||||
// List boxes have a size and (optionally) multiple selection.
|
||||
|
@ -836,11 +1122,77 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||
selectElement.appendChild(optionElement);
|
||||
}
|
||||
|
||||
selectElement.addEventListener("input", function (event) {
|
||||
function getValue(event) {
|
||||
const options = event.target.options;
|
||||
const value = options[options.selectedIndex].value;
|
||||
storage.setValue(id, { value });
|
||||
});
|
||||
return options[options.selectedIndex].value;
|
||||
}
|
||||
|
||||
if (this.enableScripting && this.hasJSActions) {
|
||||
selectElement.addEventListener("updateFromSandbox", event => {
|
||||
const { detail } = event;
|
||||
const actions = {
|
||||
value() {
|
||||
const options = event.target.options;
|
||||
const value = detail.value;
|
||||
const i = options.indexOf(value);
|
||||
if (i !== -1) {
|
||||
options.selectedIndex = i;
|
||||
storage.setValue(id, { value });
|
||||
}
|
||||
},
|
||||
focus() {
|
||||
setTimeout(() => event.target.focus({ preventScroll: false }), 0);
|
||||
},
|
||||
hidden() {
|
||||
event.target.style.visibility = detail.hidden
|
||||
? "hidden"
|
||||
: "visible";
|
||||
storage.setValue(id, { hidden: detail.hidden });
|
||||
},
|
||||
editable() {
|
||||
event.target.disabled = !detail.editable;
|
||||
},
|
||||
};
|
||||
Object.keys(detail)
|
||||
.filter(name => name in actions)
|
||||
.forEach(name => actions[name]());
|
||||
});
|
||||
|
||||
selectElement.addEventListener("input", function (event) {
|
||||
const value = getValue(event);
|
||||
storage.setValue(id, { value });
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("dispatchEventInSandbox", {
|
||||
detail: {
|
||||
id,
|
||||
name: "Keystroke",
|
||||
changeEx: value,
|
||||
willCommit: true,
|
||||
commitKey: 1,
|
||||
keyDown: false,
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
this._setEventListeners(
|
||||
selectElement,
|
||||
[
|
||||
["focus", "Focus"],
|
||||
["blur", "Blur"],
|
||||
["mousedown", "Mouse Down"],
|
||||
["mouseenter", "Mouse Enter"],
|
||||
["mouseleave", "Mouse Exit"],
|
||||
["mouseup", "MouseUp"],
|
||||
],
|
||||
event => event.target.checked
|
||||
);
|
||||
} else {
|
||||
selectElement.addEventListener("input", function (event) {
|
||||
storage.setValue(id, { value: getValue(event) });
|
||||
});
|
||||
}
|
||||
|
||||
this.container.appendChild(selectElement);
|
||||
return this.container;
|
||||
|
@ -1599,6 +1951,7 @@ class AnnotationLayer {
|
|||
parameters.annotationStorage || new AnnotationStorage(),
|
||||
enableScripting: parameters.enableScripting,
|
||||
hasJSActions: parameters.hasJSActions,
|
||||
mouseState: parameters.mouseState,
|
||||
});
|
||||
if (element.isRenderable) {
|
||||
const rendered = element.render();
|
||||
|
|
|
@ -913,6 +913,7 @@ class Doc extends PDFObject {
|
|||
const field = this.getField(fieldName);
|
||||
if (field) {
|
||||
field.value = field.defaultValue;
|
||||
field.valueAsString = field.value;
|
||||
mustCalculate = true;
|
||||
}
|
||||
}
|
||||
|
@ -920,6 +921,7 @@ class Doc extends PDFObject {
|
|||
mustCalculate = this._fields.size !== 0;
|
||||
for (const field of this._fields.values()) {
|
||||
field.value = field.defaultValue;
|
||||
field.valueAsString = field.value;
|
||||
}
|
||||
}
|
||||
if (mustCalculate) {
|
||||
|
|
|
@ -66,7 +66,9 @@ class Field extends PDFObject {
|
|||
this.type = data.type;
|
||||
this.userName = data.userName;
|
||||
this.value = data.value || "";
|
||||
this.valueAsString = data.valueAsString;
|
||||
|
||||
// Need getter/setter
|
||||
this._valueAsString = data.valueAsString;
|
||||
|
||||
// Private
|
||||
this._document = data.doc;
|
||||
|
@ -107,6 +109,28 @@ class Field extends PDFObject {
|
|||
}
|
||||
}
|
||||
|
||||
get valueAsString() {
|
||||
return this._valueAsString;
|
||||
}
|
||||
|
||||
set valueAsString(val) {
|
||||
this._valueAsString = val ? val.toString() : "";
|
||||
}
|
||||
|
||||
_getFunction(code, actionName) {
|
||||
try {
|
||||
// This eval is running in a sandbox so it's safe to use Function
|
||||
// eslint-disable-next-line no-new-func
|
||||
return Function("event", `with (this) {${code}}`).bind(this._document);
|
||||
} catch (error) {
|
||||
const value =
|
||||
`"${error.toString()}" for action ` +
|
||||
`"${actionName}" in object ${this._id}.`;
|
||||
this._send({ command: "error", value });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
setAction(cTrigger, cScript) {
|
||||
if (typeof cTrigger !== "string" || typeof cScript !== "string") {
|
||||
return;
|
||||
|
@ -114,10 +138,10 @@ class Field extends PDFObject {
|
|||
if (!(cTrigger in this._actions)) {
|
||||
this._actions[cTrigger] = [];
|
||||
}
|
||||
this._actions[cTrigger].push(
|
||||
// eslint-disable-next-line no-new-func
|
||||
Function("event", `with (this) {${cScript}}`).bind(this._document)
|
||||
);
|
||||
const fun = this._getFunction(cScript, cTrigger);
|
||||
if (fun) {
|
||||
this._actions[cTrigger].push(fun);
|
||||
}
|
||||
}
|
||||
|
||||
setFocus() {
|
||||
|
@ -127,16 +151,13 @@ class Field extends PDFObject {
|
|||
_createActionsMap(actions) {
|
||||
const actionsMap = new Map();
|
||||
if (actions) {
|
||||
const doc = this._document;
|
||||
for (const [eventType, actionsForEvent] of Object.entries(actions)) {
|
||||
// This stuff is running in a sandbox so it's safe to use Function
|
||||
actionsMap.set(
|
||||
eventType,
|
||||
actionsForEvent.map(action =>
|
||||
// eslint-disable-next-line no-new-func
|
||||
Function("event", `with (this) {${action}}`).bind(doc)
|
||||
)
|
||||
);
|
||||
const functions = actionsForEvent
|
||||
.map(action => this._getFunction(action, eventType))
|
||||
.filter(fun => !!fun);
|
||||
if (functions.length > 0) {
|
||||
actionsMap.set(eventType, functions);
|
||||
}
|
||||
}
|
||||
}
|
||||
return actionsMap;
|
||||
|
|
|
@ -54,7 +54,7 @@ class ProxyHandler {
|
|||
obj[prop] = value;
|
||||
if (obj._send && obj._id !== null && typeof old !== "function") {
|
||||
const data = { id: obj._id };
|
||||
data[prop] = value;
|
||||
data[prop] = obj[prop];
|
||||
|
||||
// send the updated value to the other side
|
||||
obj._send(data);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue