Skip to content
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

add onCancel option #14

Merged
merged 1 commit into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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<boolean>` with `onEdit`
Expand Down
18 changes: 18 additions & 0 deletions demo/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
79 changes: 44 additions & 35 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,42 +15,51 @@
<body>

<div class='container-fluid'>
<h1>malle demo page</h1>
<h2>API documentation</h2>
<p>If you are looking for the complete API documentation, with all possible options and interfaces with their description, it is available here: <a href='/malle/api'>full API documentation</a>.</p>
<h2>Minimal example</h2>
<p>The text <span id='minimal' class='fw-bold'>in bold</span> is malleable. Try clicking on it.</p>
<h2>Selecting input type</h2>
<p class='tip'>Use <code>data-ma-type</code> attribute to select the appropriate input type, or set it in the options with <code>inputType</code>.</p>
<p><code>data-ma-type='text'</code><br>This is the default type and doesn't need to be specified. <span data-malleable='true'>Some malleable text.</span></p>
<p><code>data-ma-type='number'</code><br>Click on the number: <span data-malleable='true' data-ma-type='number'>3</span></p>
<p><code>data-ma-type='email'</code><br>Email example: <span data-malleable='true' data-ma-type='email'>[email protected]</span></p>
<p><code>data-ma-type='url'</code><br>Url example: <span data-malleable='true' data-ma-type='url'>https://www.deltablot.com</span></p>
<h3>Select example</h3>
<p>Select example. Here we set <code>inputType: InputType.Select</code> and <code>selectOptions: [{value: '1', text: 'Blah'}]</code> in the options.<br>Best country: <span class='malleableSelect'>France</span></p>
<h3>Textarea example</h3>
<p class='malleableTextarea'>This is a malleable paragraph that will transform into a textarea with action buttons. Settings are defined in JS options here.</p>
<h3>Datetime example</h3>
<p>Using <code>data-ma-type='datetime-local'</code>. Upcoming deadline: <span data-malleable='true' data-ma-type='datetime-local'>2022-07-14T13:37</span>.</p>
<div class='mb-5'>
<h1>malle demo page</h1>
<h2>API documentation</h2>
<p>If you are looking for the complete API documentation, with all possible options and interfaces with their description, it is available here:</p>
<a href='/malle/api'><button class='btn btn-primary'>Complete API documentation</button></a>
<h2>Minimal example</h2>
<p>The text <span id='minimal' class='fw-bold'>in bold</span> is malleable. Try clicking on it.</p>
<h2>Selecting input type</h2>
<p class='tip'>Use <code>data-ma-type</code> attribute to select the appropriate input type, or set it in the options with <code>inputType</code>.</p>
<p><code>data-ma-type='text'</code><br>This is the default type and doesn't need to be specified. <span data-malleable='true'>Some malleable text.</span></p>
<p><code>data-ma-type='number'</code><br>Click on the number: <span data-malleable='true' data-ma-type='number'>3</span></p>
<p><code>data-ma-type='email'</code><br>Email example: <span data-malleable='true' data-ma-type='email'>[email protected]</span></p>
<p><code>data-ma-type='url'</code><br>Url example: <span data-malleable='true' data-ma-type='url'>https://www.deltablot.com</span></p>
<h3>Select example</h3>
<p>Select example. Here we set <code>inputType: InputType.Select</code> and <code>selectOptions: [{value: '1', text: 'Blah'}]</code> in the options.<br>Best country: <span class='malleableSelect'>France</span></p>
<h3>Textarea example</h3>
<p class='malleableTextarea'>This is a malleable paragraph that will transform into a textarea with action buttons. Settings are defined in JS options here.</p>
<h3>Datetime example</h3>
<p>Using <code>data-ma-type='datetime-local'</code>. Upcoming deadline: <span data-malleable='true' data-ma-type='datetime-local'>2022-07-14T13:37</span>.</p>

<h2>Selecting behavior on blur</h2>
<p class='tip'>Use <code>data-ma-blur</code> attribute to select the appropriate behavior when user clicks outside the input, or set it in the options with <code>onBlur</code>.</p>
<p><code>data-ma-blur='cancel'</code><br><span data-malleable='true' data-ma-blur='cancel'>Clicking outside the input will cancel edition.</span></p>
<p><code>data-ma-blur='submit'</code><br><span data-malleable='true' data-ma-blur='submit'>Clicking outside the input will submit changes.</span></p>
<p><code>data-ma-blur='ignore'</code><br><span data-malleable='true' data-ma-blur='ignore'>Clicking outside the input will do nothing.</span></p>
<h2>Selecting behavior on Enter keypress</h2>
<p class='tip'>Use <code>data-ma-enter</code> attribute to select the appropriate behavior when user presses the Enter key, or set it in the options with <code>onEnter</code>.</p>
<p><code>data-ma-enter='cancel'</code><br><span data-malleable='true' data-ma-enter='cancel'>Pressing Enter will cancel edition.</span></p>
<p><code>data-ma-enter='submit'</code><br><span data-malleable='true' data-ma-enter='submit'>Pressing Enter will submit changes.</span></p>
<p><code>data-ma-enter='ignore'</code><br><span data-malleable='true' data-ma-enter='ignore'>Pressing Enter will do nothing.</span></p>
<h2>Setting behavior for both Blur action and Enter keypress</h2>
<p data-malleable='true' data-ma-enter='submit' data-ma-blur='submit'>This text will submit onBlur and also onEnter.</p>
<h3>Same with Escape keypress</h3>
<p class='tip'>The default behavior is to Cancel action, but here is an example to ignore an Escape keypress:</p>
<p><code>data-ma-escape='ignore'</code><br><span data-malleable='true' data-ma-escape='ignore'>Pressing Escape will do nothing.</span></p>
<p><code>data-ma-escape='cancel'</code><br><span data-malleable='true' data-ma-escape='cancel'>Pressing Escape will cancel edition (default behavior, attribute doesn't need to be added).</span></p>
<h2>Adding a placeholder</h2>
<p><code>data-ma-placeholder='[email protected]'</code><br>Your email is: <span data-malleable='true' data-ma-placeholder='[email protected]' data-ma-type='email'>[email protected]</span></p>
<h2>Selecting behavior on blur</h2>
<p class='tip'>Use <code>data-ma-blur</code> attribute to select the appropriate behavior when user clicks outside the input, or set it in the options with <code>onBlur</code>.</p>
<p><code>data-ma-blur='cancel'</code><br><span data-malleable='true' data-ma-blur='cancel'>Clicking outside the input will cancel edition.</span></p>
<p><code>data-ma-blur='submit'</code><br><span data-malleable='true' data-ma-blur='submit'>Clicking outside the input will submit changes.</span></p>
<p><code>data-ma-blur='ignore'</code><br><span data-malleable='true' data-ma-blur='ignore'>Clicking outside the input will do nothing.</span></p>
<h2>Selecting behavior on Enter keypress</h2>
<p class='tip'>Use <code>data-ma-enter</code> attribute to select the appropriate behavior when user presses the Enter key, or set it in the options with <code>onEnter</code>.</p>
<p><code>data-ma-enter='cancel'</code><br><span data-malleable='true' data-ma-enter='cancel'>Pressing Enter will cancel edition.</span></p>
<p><code>data-ma-enter='submit'</code><br><span data-malleable='true' data-ma-enter='submit'>Pressing Enter will submit changes.</span></p>
<p><code>data-ma-enter='ignore'</code><br><span data-malleable='true' data-ma-enter='ignore'>Pressing Enter will do nothing.</span></p>
<h2>Setting behavior for both Blur action and Enter keypress</h2>
<p data-malleable='true' data-ma-enter='submit' data-ma-blur='submit'>This text will submit onBlur and also onEnter.</p>
<h3>Same with Escape keypress</h3>
<p class='tip'>The default behavior is to Cancel action, but here is an example to ignore an Escape keypress:</p>
<p><code>data-ma-escape='ignore'</code><br><span data-malleable='true' data-ma-escape='ignore'>Pressing Escape will do nothing.</span></p>
<p><code>data-ma-escape='cancel'</code><br><span data-malleable='true' data-ma-escape='cancel'>Pressing Escape will cancel edition (default behavior, attribute doesn't need to be added).</span></p>
<h2>Adding a placeholder</h2>
<p><code>data-ma-placeholder='[email protected]'</code><br>Your email is: <span data-malleable='true' data-ma-placeholder='[email protected]' data-ma-type='email'>[email protected]</span></p>
<h2>Running a function when the Cancel button is pressed</h2>
<p>The <code>onCancel</code> function will run when an <code>Action.Cancel</code> action is triggered, like clicking the Cancel button. It must return <code>true</code> for the input to be reverted to the original element.
<a href='https://deltablot.github.io/malle/api/interfaces/Options.html#onCancel.onCancel-1' target='_blank'>See API documentation</a>
<br>
<p class='onCancel fw-bold'>click me and then click cancel while looking at the console output</p>
</p>
</div>
</div>

<script src='demo.js' type='module'></script>
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
103 changes: 93 additions & 10 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>;
// The classes added to the form element.
formClasses?: Array<string>;
// The classes added to the input element.
inputClasses?: Array<string>,
// 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<string>;
/**
* 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<string>;
// The classes added to the input element.
// @example ['form-control']
inputClasses?: Array<string>,
// 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<boolean>;
/**
* 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<boolean>;
// 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<SelectOptions> | Promise<Array<SelectOptions>>;
// 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<string>;
// The text added on hover of the malleable element. Uses the `title` attribute.
tooltip?: string;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
Loading