From 6f714c4661a50f719c6f6359458b702a44e16525 Mon Sep 17 00:00:00 2001 From: Nicolas CARPi <3043706+NicolasCARPi@users.noreply.github.com> Date: Mon, 25 Mar 2024 02:08:22 +0100 Subject: [PATCH] add onCancel option (#14) * Add `onCancel` option to give a function to run when a Cancel action is triggered. fix #13 * Improve doc by fixing a few errors and adding default values and code examples --- CHANGELOG.md | 4 ++ demo/demo.js | 18 ++++++++ demo/index.html | 79 +++++++++++++++++++---------------- package-lock.json | 4 +- package.json | 2 +- src/main.ts | 103 +++++++++++++++++++++++++++++++++++++++++----- 6 files changed, 162 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b17b0f6..a35c814 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog for malle +## 2.6.0 + +* Add `onCancel` option to give a function to run when a Cancel action is triggered. fix #13 + ## 2.5.2 * Allow returning a `Promise` with `onEdit` diff --git a/demo/demo.js b/demo/demo.js index 92efcb2..ceed749 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -66,6 +66,24 @@ new Malle({ // instead of using listenNow, we call listen() right after instanciation }).listen(); +// onCancel +new Malle({ + fun: value => { + return new Promise(resolve => resolve(value)); + }, + formClasses: ['d-inline-flex'], + debug: true, + onCancel: () => { + console.log('a cancel action has been detected'); + return true; + }, + onBlur: Action.Cancel, + cancel: 'Cancel', + cancelClasses: ['btn', 'btn-danger'], + listenOn: '.onCancel', + listenNow: true, +}); + new Malle({ fun: value => { return new Promise(resolve => resolve(value)); diff --git a/demo/index.html b/demo/index.html index 783fd21..49ad008 100644 --- a/demo/index.html +++ b/demo/index.html @@ -15,42 +15,51 @@
-

malle demo page

-

API documentation

-

If you are looking for the complete API documentation, with all possible options and interfaces with their description, it is available here: full API documentation.

-

Minimal example

-

The text in bold is malleable. Try clicking on it.

-

Selecting input type

-

Use data-ma-type attribute to select the appropriate input type, or set it in the options with inputType.

-

data-ma-type='text'
This is the default type and doesn't need to be specified. Some malleable text.

-

data-ma-type='number'
Click on the number: 3

-

data-ma-type='email'
Email example: contact@example.org

-

data-ma-type='url'
Url example: https://www.deltablot.com

-

Select example

-

Select example. Here we set inputType: InputType.Select and selectOptions: [{value: '1', text: 'Blah'}] in the options.
Best country: France

-

Textarea example

-

This is a malleable paragraph that will transform into a textarea with action buttons. Settings are defined in JS options here.

-

Datetime example

-

Using data-ma-type='datetime-local'. Upcoming deadline: 2022-07-14T13:37.

+
+

malle demo page

+

API documentation

+

If you are looking for the complete API documentation, with all possible options and interfaces with their description, it is available here:

+ +

Minimal example

+

The text in bold is malleable. Try clicking on it.

+

Selecting input type

+

Use data-ma-type attribute to select the appropriate input type, or set it in the options with inputType.

+

data-ma-type='text'
This is the default type and doesn't need to be specified. Some malleable text.

+

data-ma-type='number'
Click on the number: 3

+

data-ma-type='email'
Email example: contact@example.org

+

data-ma-type='url'
Url example: https://www.deltablot.com

+

Select example

+

Select example. Here we set inputType: InputType.Select and selectOptions: [{value: '1', text: 'Blah'}] in the options.
Best country: France

+

Textarea example

+

This is a malleable paragraph that will transform into a textarea with action buttons. Settings are defined in JS options here.

+

Datetime example

+

Using data-ma-type='datetime-local'. Upcoming deadline: 2022-07-14T13:37.

-

Selecting behavior on blur

-

Use data-ma-blur attribute to select the appropriate behavior when user clicks outside the input, or set it in the options with onBlur.

-

data-ma-blur='cancel'
Clicking outside the input will cancel edition.

-

data-ma-blur='submit'
Clicking outside the input will submit changes.

-

data-ma-blur='ignore'
Clicking outside the input will do nothing.

-

Selecting behavior on Enter keypress

-

Use data-ma-enter attribute to select the appropriate behavior when user presses the Enter key, or set it in the options with onEnter.

-

data-ma-enter='cancel'
Pressing Enter will cancel edition.

-

data-ma-enter='submit'
Pressing Enter will submit changes.

-

data-ma-enter='ignore'
Pressing Enter will do nothing.

-

Setting behavior for both Blur action and Enter keypress

-

This text will submit onBlur and also onEnter.

-

Same with Escape keypress

-

The default behavior is to Cancel action, but here is an example to ignore an Escape keypress:

-

data-ma-escape='ignore'
Pressing Escape will do nothing.

-

data-ma-escape='cancel'
Pressing Escape will cancel edition (default behavior, attribute doesn't need to be added).

-

Adding a placeholder

-

data-ma-placeholder='you@example.com'
Your email is: niko@example.com

+

Selecting behavior on blur

+

Use data-ma-blur attribute to select the appropriate behavior when user clicks outside the input, or set it in the options with onBlur.

+

data-ma-blur='cancel'
Clicking outside the input will cancel edition.

+

data-ma-blur='submit'
Clicking outside the input will submit changes.

+

data-ma-blur='ignore'
Clicking outside the input will do nothing.

+

Selecting behavior on Enter keypress

+

Use data-ma-enter attribute to select the appropriate behavior when user presses the Enter key, or set it in the options with onEnter.

+

data-ma-enter='cancel'
Pressing Enter will cancel edition.

+

data-ma-enter='submit'
Pressing Enter will submit changes.

+

data-ma-enter='ignore'
Pressing Enter will do nothing.

+

Setting behavior for both Blur action and Enter keypress

+

This text will submit onBlur and also onEnter.

+

Same with Escape keypress

+

The default behavior is to Cancel action, but here is an example to ignore an Escape keypress:

+

data-ma-escape='ignore'
Pressing Escape will do nothing.

+

data-ma-escape='cancel'
Pressing Escape will cancel edition (default behavior, attribute doesn't need to be added).

+

Adding a placeholder

+

data-ma-placeholder='you@example.com'
Your email is: niko@example.com

+

Running a function when the Cancel button is pressed

+

The onCancel function will run when an Action.Cancel action is triggered, like clicking the Cancel button. It must return true for the input to be reverted to the original element. + See API documentation +
+

click me and then click cancel while looking at the console output

+

+
diff --git a/package-lock.json b/package-lock.json index c3fae91..b3e4ea8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@deltablot/malle", - "version": "2.5.2", + "version": "2.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@deltablot/malle", - "version": "2.5.2", + "version": "2.6.0", "license": "MIT", "devDependencies": { "@types/jest": "^27.4.1", diff --git a/package.json b/package.json index b432854..4f1cc13 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@deltablot/malle", - "version": "2.5.2", + "version": "2.6.0", "description": "Make text elements malleable, without dependencies.", "main": "dist/main.js", "typings": "dist/main.d.ts", diff --git a/src/main.ts b/src/main.ts index 71330ed..26856a0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -59,50 +59,125 @@ export interface Options { */ before?(original: HTMLElement, event:Event): boolean; // The text displayed on Cancel button. + // @example Abort cancel?: string; // The classes added to Cancel button. + // @example ['btn', 'btn-secondary'] cancelClasses?: Array; - // The classes added to the form element. - formClasses?: Array; - // The classes added to the input element. - inputClasses?: Array, // Enabling debug mode will produce verbose output in the console. + // @default false debug?: boolean; // This is where you define the type of event that will trigger malle. + // @default EventType.Click event?: EventType; // Should the newly created input grab focus? - inputType?: InputType; - // This is the user function that is called on submit. + // @default true focus?: boolean; - // Define the type of the input element. + // The classes added to the form element. + // @example ['d-inline-flex'] + formClasses?: Array; + /** + * This is the main and only mandatory option parameter. It is the user function that is called when the Submit action happens. + * @example with a custom function + * // this is the user function that will process the new value + * // typically this will POST to some endpoint and get some json back + * // it receives the event + * const myCustomFunction = (value, orig) => { + * console.log(`New text: ${value}`); + * // do something with that value, like POSTing it somewhere + * return new Promise(resolve => resolve(value)); + * }; + * + * new Malle({ + * fun: myCustomFunction, + * }).listen(); + */ fun(value: string, original: HTMLElement, event:Event, input: HTMLInputElement|HTMLSelectElement): Promise; + // The classes added to the input element. + // @example ['form-control'] + inputClasses?: Array, + // Define the type of the input element. + // @default InputType.Text + inputType?: InputType; // Start listening immediatly or not. + // @default false listenNow?: boolean; // HTML selector to target malleable elements on the page. listenOn?: string; // What Action should be taken when focus of the input is lost. onBlur?: Action; - // This function runs right after the form is created. + /** + * A function that runs when a Cancel action is performed. Must return `true` or the input is not reverted to the original element. + * @example + * ```javascript + * onCancel: (original, event, input) => { + * console.log('a cancel action has been detected'); + * return true; + * }, + * ``` + */ + onCancel?(original: HTMLElement, event:Event, input: HTMLInputElement|HTMLSelectElement): boolean | Promise; + /** + * This function runs right after the form is created. Its return value has no impact. + * @example + * ```javascript + * onEdit: (original, event, input) => { + * console.log('this will run after the input is present on the page'); + * return true; + * }, + * ``` + */ onEdit?(original: HTMLElement, event:Event, input: HTMLInputElement|HTMLSelectElement): boolean | Promise; // What Action should be taken when the Enter key is pressed? + // @default Action.Submit onEnter?: Action; // What Action should be taken when the Escape key is pressed? + // @default Action.Cancel onEscape?: Action; // A text that is shown on empty input. placeholder?: string; // Do nothing if new value is the same as the old value. + // @default true requireDiff?: boolean; // Use innerHTML instead of innerText (only use if the return value is trusted HTML). + // @default false returnedValueIsTrustedHtml?: boolean; - // An array of options for InputType.Select. Can also be a Promise and fetched asynchronously. + /* + * An array of options for InputType.Select. Can also be a Promise and fetched asynchronously. + * @example Directly give the options to use + * ```javascript + * selectOptions: [ + * { value: '1', text: 'Rivoli' }, + * { value: '2', text: 'Austerlitz' }, + * { value: '3', text: 'Marengo', selected: true }, + * ], + * ``` + * @example Fetch the options with an HTTP request or any other function + * ```javascript + * // Change the keys used to lookup value and text + * selectOptionsValueKey: 'id', + * selectOptionsTextKey: 'title', + * // this promises to return an Array with objects that have the keys "id" and "title" + * selectOptions: Something.getOptions(), + * ``` + */ selectOptions?: Array | Promise>; // What is the name of the key to use to lookup the values in the selectOptions array? + // @default value selectOptionsValueKey?: string; // What is the name of the key to use to lookup the option text in the selectOptions array? + // @default text selectOptionsTextKey?: string; // The text on the Submit button. + // @example Save changes submit?: string; - // The classes added to the submit button. + /** + * The classes added to the submit button. + * @example With bootstrap classes + * ``` + * submitClasses: ['btn', 'btn-primary', 'mt-2'], + * ``` + */ submitClasses?: Array; // The text added on hover of the malleable element. Uses the `title` attribute. tooltip?: string; @@ -145,6 +220,7 @@ export class Malle { listenNow: false, listenOn: '[data-malleable="true"]', onBlur: Action.Submit, + onCancel: undefined, onEdit: undefined, onEnter: Action.Submit, onEscape: Action.Cancel, @@ -230,6 +306,13 @@ export class Malle { cancel(event: Event): boolean { event.preventDefault(); this.debug(event.toString()); + // execute the before hook + if (typeof this.opt.onCancel === 'function') { + this.debug('running onCancel function'); + if (this.opt.onCancel(this.original, event, this.input) !== true) { + return; + } + } this.debug('reverting to original element'); this.form.replaceWith(this.original); return true;