-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: add an onClick option for a form field's mouse-up action #1790
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -118,15 +118,56 @@ These options are accepted by `formPushButton`: | |
|
|
||
| - `label` [_string_] - Sets the label text. You can also set an icon, but for | ||
| this you will need to 'expert-up' and dig deeper into the PDF Reference manual. | ||
| - `onClick` [_string | function_] - JavaScript to run when the button is | ||
| clicked (its mouse-up action). If a `format` option is also given, its | ||
| keystroke/format validation actions are added alongside this one rather | ||
| than replacing it. | ||
|
|
||
| ```js | ||
| var opts = { | ||
| backgroundColor: 'yellow', | ||
| label: 'Test Button' | ||
| label: 'Test Button', | ||
| onClick: 'app.alert("clicked");' | ||
| }; | ||
| doc.formPushButton('btn1', 10, 200, 100, 30, opts); | ||
| ``` | ||
|
|
||
| `onClick` also accepts a plain function, called with Acrobat's own `app`, | ||
| `getField`, `display` and `event` passed in as arguments, in that order (and | ||
| `this` bound to the Document, exactly as Acrobat itself binds it). Declare | ||
| only the leading parameters your handler actually uses — `function (app) {}` | ||
| or even `function () {}` are both fine, since the call always passes all of | ||
| them regardless of how many the handler declares; the rest are simply | ||
| ignored, the same way `array.map(item => ...)` can ignore the `index` and | ||
| `array` parameters its callback type also offers. This runs inside the PDF | ||
| viewer's own JavaScript engine, not wherever the PDF was generated, so it | ||
| can't close over outside variables — use only plain function syntax (not | ||
| e.g. arrow functions, which also can't bind `this`) for the widest viewer | ||
| support: | ||
|
|
||
| ```js | ||
| doc.formPushButton('btn1', 10, 200, 100, 30, { | ||
| label: 'Test Button', | ||
| onClick: function (app, getField) { | ||
| app.alert('clicked'); | ||
| this.getField('otherField').value = 'updated from btn1'; | ||
| } | ||
|
Comment on lines
+151
to
+154
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. param getField is not used. Is really necessary? |
||
| }); | ||
| ``` | ||
|
|
||
| TypeScript projects can import `AcrobatOnClick` and the other types this | ||
| signature uses from `pdfkit/types/acrobat-js` — a small, best-effort set of | ||
| types for the handful of Acrobat globals most `onClick` handlers need, kept | ||
| separate from pdfkit's own types so nothing is declared globally: | ||
|
Comment on lines
+158
to
+161
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nop. Types will be handled separately. Do not expose it for now. It can be documented internally |
||
|
|
||
| ```ts | ||
| import type { AcrobatOnClick } from 'pdfkit/types/acrobat-js'; | ||
|
|
||
| const onClick: AcrobatOnClick = function (app) { | ||
| app.alert('clicked'); | ||
| }; | ||
| ``` | ||
|
|
||
| #### Radio Button Field Options | ||
|
|
||
| These options are accepted by `formRadioButton`: | ||
|
|
@@ -302,7 +343,7 @@ The output of this example looks like this. | |
|
|
||
| ### Advanced Form Field Use | ||
|
|
||
| Older implementations used to pass all unknown options to the internal PDF object structure. A small set of direct PDF dictionary escape hatches is still recognized: `Ff`, `MK.CA`, and `AA` when a `format` option is used but its use is discouraged and likely will be removed in future versions. | ||
| Older implementations used to pass all unknown options to the internal PDF object structure. A small set of direct PDF dictionary escape hatches is still recognized: `Ff` and `MK.CA`, but their use is discouraged and they may be removed in future versions. A previously-recognized `AA` escape hatch (only reachable together with a `format` option) has been replaced by the `onClick` option above, which needs no PDF dictionary knowledge and works on its own. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nop. This is not a changelog and no need to justify |
||
|
|
||
| If an option is not supported, open an issue on Github and it will be considered for addition to the API. | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -108,6 +108,31 @@ function mapStrings(options, pdfObject) { | |
| } | ||
| } | ||
|
|
||
| function mapActions(options, pdfObject) { | ||
| if (options.onClick) { | ||
| // A function is stringified and immediately invoked with `this` bound to | ||
| // the Document (exactly as Acrobat itself binds it in any field action) | ||
| // and Acrobat's own `app`, `getField`, `display` and `event` globals | ||
| // passed in as arguments, so authors can write the action as a real, | ||
| // typed function (see types/acrobat-js.d.ts) instead of a hand-built | ||
| // string or relying on ambient global declarations that risk colliding | ||
| // with an unrelated identifier elsewhere in their project. It still runs | ||
| // inside Acrobat's own JavaScript engine, not wherever the PDF was | ||
| // generated, so it can't close over outside variables, and only plain | ||
| // function syntax (not arrow functions or other syntax Acrobat's engine | ||
| // may not support) should be relied on. | ||
| const js = | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keep it simple, pass the base minimum arguments to get it working |
||
| typeof options.onClick === 'function' | ||
| ? `(${options.onClick}).call(this, app, getField, display, event);` | ||
| : options.onClick; | ||
| pdfObject.AA = pdfObject.AA ?? {}; | ||
| pdfObject.AA.U = { | ||
| S: 'JavaScript', | ||
| JS: new String(js), | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| function mapFormat(options, pdfObject) { | ||
| const f = options.format; | ||
| if (f?.type) { | ||
|
|
@@ -148,7 +173,7 @@ function mapFormat(options, pdfObject) { | |
| params = String([String(p.nDec), p.sepComma ? '0' : '1'].join(',')); | ||
| } | ||
| } | ||
| pdfObject.AA = options.AA ?? {}; | ||
| pdfObject.AA = pdfObject.AA ?? {}; | ||
| pdfObject.AA.K = { | ||
| S: 'JavaScript', | ||
| JS: new String(`${fnKeystroke}(${params});`), | ||
|
|
@@ -311,6 +336,7 @@ export default { | |
| this._mapFont(options, pdfObject); | ||
| mapStrings(options, pdfObject); | ||
| this._mapColors(options, pdfObject); | ||
| mapActions(options, pdfObject); | ||
| mapFormat(options, pdfObject); | ||
|
|
||
| pdfObject.T = new String(name); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -84,6 +84,10 @@ | |
| "require": "./js/output.cjs", | ||
| "default": "./js/output.mjs" | ||
| }, | ||
| "./types/acrobat-js": { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nop |
||
| "types": "./types/acrobat-js.d.ts", | ||
| "default": "./types/acrobat-js.d.ts" | ||
| }, | ||
| "./standard-fonts/Courier": { | ||
| "require": "./js/standard-fonts/Courier.cjs", | ||
| "default": "./js/standard-fonts/Courier.mjs" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| /** | ||
| * Minimal, best-effort types for the small subset of Adobe Acrobat's own | ||
| * JavaScript API commonly needed to write a form field's `onClick` action | ||
| * (see docs/forms.md). This is not a full Acrobat SDK type surface -- only | ||
| * the handful of globals most `onClick` handlers reach for. Contributions | ||
| * extending it are welcome. | ||
| * | ||
| * These aren't ambient/global declarations: `app`, `getField`, `display` and | ||
| * `event` only exist inside a PDF viewer's own JavaScript engine at the | ||
| * moment the action runs, never in the Node or browser code that builds the | ||
| * PDF, so declaring them as globals would risk colliding with unrelated | ||
| * identifiers elsewhere in a project (a bare global `event`, for example, | ||
| * collides with the DOM lib's own deprecated `window.event`). | ||
| * | ||
| * Instead, write `onClick` as a function that takes them as parameters -- | ||
| * pdfkit calls the generated action with the real Acrobat globals in that | ||
| * position (and `this` bound to the Document, exactly as Acrobat itself | ||
| * binds it in any field action), so this works exactly like referencing | ||
| * them as globals would, without ever declaring one: | ||
| * | ||
| * import type { AcrobatOnClick } from 'pdfkit/types/acrobat-js'; | ||
| * | ||
| * const onClick: AcrobatOnClick = function (app, getField, display) { | ||
| * app.alert('clicked'); | ||
| * this.getField('otherField').value = 'updated from btn1'; | ||
| * }; | ||
| * | ||
| * Declare only the leading parameters your handler actually uses -- | ||
| * `function (app) {}` or even `function () {}` are both valid AcrobatOnClick | ||
| * values. The call always passes all of them (`this`, `app`, `getField`, | ||
| * `display`, `event`, in that order); a handler that declares fewer simply | ||
| * never sees the rest, the same way `array.map(item => ...)` can ignore the | ||
| * `index` and `array` parameters its callback type also offers. | ||
| * | ||
| * (`this` isn't available in an arrow function, and Acrobat's own JS engine | ||
| * may not support arrow function syntax at all -- write `onClick` as a | ||
| * plain `function` for the widest viewer support.) | ||
| */ | ||
|
|
||
| /** | ||
| * Partial: Acrobat's real `app` object has many more methods (`execDialog`, | ||
| * `launchURL`, `response`, `thermometer`, ...). Only the ones common enough | ||
| * to include here are listed. | ||
| */ | ||
| export interface AcrobatApp { | ||
| alert( | ||
| message: string, | ||
| icon?: number, | ||
| type?: number, | ||
| title?: string, | ||
| ): number; | ||
| execMenuItem(name: string): void; | ||
| } | ||
|
|
||
| /** Partial: a real field object has many more properties than these. */ | ||
| export interface AcrobatField { | ||
| value: string | number; | ||
| display: number; | ||
| readonly: boolean; | ||
| hidden: boolean; | ||
| } | ||
|
|
||
| /** Complete: this is Acrobat's full, fixed set of `display` constants. */ | ||
| export interface AcrobatDisplay { | ||
| visible: 0; | ||
| hidden: 1; | ||
| noPrint: 2; | ||
| noView: 3; | ||
| } | ||
|
|
||
| export type AcrobatGetField = (name: string) => AcrobatField; | ||
|
|
||
| /** Partial: a real field-action event object has more properties than these. */ | ||
| export interface AcrobatEvent { | ||
| target: AcrobatField; | ||
| value: string | number; | ||
| rc: boolean; | ||
| willCommit: boolean; | ||
| } | ||
|
|
||
| /** | ||
| * Partial: the Document object Acrobat binds `this` to in any field action. | ||
| * A real Document has hundreds of members; only these two are declared here. | ||
| */ | ||
| export interface AcrobatDocument { | ||
| getField: AcrobatGetField; | ||
| numPages: number; | ||
| } | ||
|
|
||
| export type AcrobatOnClick = ( | ||
| this: AcrobatDocument, | ||
| app: AcrobatApp, | ||
| getField: AcrobatGetField, | ||
| display: AcrobatDisplay, | ||
| event: AcrobatEvent, | ||
| ) => void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simplify the description. Be objective hiding internal details, just with enough information to user create correct functions