From 92f1643836b9b41676ee05a6a5790214f90a7760 Mon Sep 17 00:00:00 2001 From: "Carlos A." Date: Thu, 17 Oct 2024 13:33:13 +0200 Subject: [PATCH] add the override action --- README.md | 39 + dist/powerbuttons.compress.js | 2 +- dist/powerbuttons.full.js | 69 +- dist/powerbuttons.js | 69 +- dist/powerbuttons.min.js | 2 +- dist/powerbuttons.module.js | 1235 +++++++++++++++++ dist/powerbuttons.raw.js | 88 +- src/init.js | 2 +- src/plugins/00.actionoverride.js | 70 + ...{00.actionverify.js => 05.actionverify.js} | 4 - src/plugins/80.actionformbutton.js | 2 +- src/powerbuttons.js | 9 +- 12 files changed, 1560 insertions(+), 31 deletions(-) create mode 100644 dist/powerbuttons.module.js create mode 100644 src/plugins/00.actionoverride.js rename src/plugins/{00.actionverify.js => 05.actionverify.js} (95%) diff --git a/README.md b/README.md index 5202022..c0e5d1a 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,23 @@ The default values for the options of the _showmessage_ button are the next: data-showmessage-footer="true">test showmessage ``` +### Override Actions + +This is a special case for the actions chain that is intended to override the next actions chained for this button. If is useful to enable a simple workflow for the power-actions. + +The basic syntax to use this plugin is to include the attribute _data-override_ in the button tag (along with other actions) such as the next one: +```html + +``` + +**Use case**: A submit button that may overwrite a file. It is possible to use the `confirm` action to ask the user if he is sure to overwrite the file. But if the file did not exist, the app should not ask for confirmation. In this case, the `override` action can be used to skip the `confirm` action if the file does not exist (__*__ in the declarative way, the `override` action is the first action to be executed). +```html + +``` + ### FormSet Button The _formset_ button is a button that sets the values of a form fields prior to executing its real action. It is useful for (e.g.) setting a hidden field in a form prior to submitting it, or pre-filling a form. @@ -335,6 +352,7 @@ If using the declarative method, there is a fixed order: 1. _confirm_ 1. _asynctask_ 1. _showmessage_ +1. _override_ 1. _formset_ 1. _formbutton_ (set the field values that depend on a function) 1. the real action (i.e. `onclick` handler) @@ -432,6 +450,27 @@ footer: true The equivalent for html5 attributes are the snake-case version of each of them, with the prefix `data-showmessage-`; e.g. `data-showmessage-button-accept`, `data-showmessage-title`, etc. +#### _override_ + +```javascript +// The function to call to check for overriding the next actions. It must return a true or false value. If it is an string, it will be evaluated as javascript, using _eval_ +override: null, +// The form to bind the verification to. If it is a string, it will be interpreted as a selector (it is not verified if it is a form or any other object). If null, the verification will be bound to the document +form: null, +// The content of the message to show to the user if `override` evaluates to true (it can be either plain text or a HTML fragment) +overridden: null, +// A custom content to show to the user under the message when `override` evaluates to true (it can be either plain text or a HTML fragment) +customContent: null, +// The content of the title of the dialog when `override` evaluates to true (it can be either plain text or a HTML fragment) +title: null, +// The content for the button that confirms the action (it can be either plain text or a HTML fragment) +buttonAccept: "Accept", +// If falshi (i.e. null, 0, false, "false"), the esc key will not close the dialog (it will close it if true) +escapeKey: true, +``` + +The equivalent for html5 attributes are the snake-case version of each of them, with the prefix `data-override-`; e.g. `data-override-button-accept`, `data-override-title`, etc. + #### _formset_ ```javascript diff --git a/dist/powerbuttons.compress.js b/dist/powerbuttons.compress.js index 08f8a55..52616df 100644 --- a/dist/powerbuttons.compress.js +++ b/dist/powerbuttons.compress.js @@ -1,2 +1,2 @@ /* Copyright 2021 Carlos A. (https://github.com/dealfonso); License: http://www.apache.org/licenses/LICENSE-2.0 */ -!function(exports){var exports;function pascalToSnake(t){return t.replace(/[A-Z]/g,t=>"_"+t.toLowerCase()).replace(/^_*/,"")}function pascalToKebab(t){return t.replace(/[A-Z]/g,t=>"-"+t.toLowerCase()).replace(/^-*/,"")}function snakeCaseToCamel(t){t.replace(/-([a-z])/g,t=>t[1].toUpperCase())}function pascalToCamel(t){return t.charAt(0).toLowerCase()+t.slice(1)}function CamelToCamel(t){return t.charAt(0).toUpperCase()+t.slice(1)}function isElement(t){t instanceof Element||0 instanceof HTMLDocument}function parseBoolean(t){return"boolean"==typeof t?t:"string"==typeof t?"true"===(t=t.toLowerCase())||"yes"===t||"1"===t:!!t}function createTag(t,e={},n=null){var o=t.split("#");let s=null;var i,o=(t=1==o.length?o[0]:(o[1]=o[1].split("."),s=o[1][0],[o[0],...o[1].slice(1)].join("."))).split("."),l=(""===(t=o[0])&&(t="div"),"string"==typeof e&&(n=e,e={}),null!==n&&(e.innerHTML=n),null!==s&&(e.id=s),e.className=[e.className,...o.slice(1)].filter(function(t){return""!==(""+t).trim()}).join(" "),document.createElement(t));for(i in e)void 0!==l[i]?l[i]=e[i]:l.setAttribute(i,e[i]);return l}function appendToElement(t,...e){e=e.filter(t=>null!=t);return t.append(...e),t}function searchForm(t){let e=null;return null!==t&&void 0===(e=document.forms[t])&&null===(e=document.querySelector(t))&&console.warn(`form ${t} not found`),null!==e&&"form"!==e.tagName.toLowerCase()&&console.warn(`form ${t} is not a form`),e}function getValueWithJavascriptSupport(value,context=null){if("function"==typeof value)return value.bind(context);if("string"==typeof value){let internalValue=value.trim();if(internalValue.startsWith("javascript:"))try{let f=internalValue.substring(11);value=function(){return eval(f)}.bind(context)}catch(e){console.error(`Error executing javascript code ${internalValue.substring(11)}, error: `+e),value=null}}return value}function promiseForEvent(e,n){let o=null;var t=new Promise(t=>{o=t});return e.addEventListener(n,function t(){e.removeEventListener(n,t),o()}),t}function isEmpty(t){return null==t||(t instanceof Array?0===t.length:t instanceof Object?0===Object.keys(t).length:"string"==typeof t&&""===t.trim())}void 0===exports&&(exports=window),exports.powerButtons=function(t,e=null,n=null){let o=null,s=[],i={};if("string"==typeof t)if(null===(o=function(t){for(var e in PowerButtons.actionsRegistered)if(t.toLocaleLowerCase()===e.toLocaleLowerCase())return e;return null}(t))){if(0===(s=document.querySelectorAll(t)).length)return void console.error(`Parameter ${t} is neither the name of a registered plugin nor a valid selector`);2this._handleButton(-1,null,e)),parseBoolean(s.header)&&(t=createTag(".modal-header"),null!==s.title&&t.append(appendToElement(createTag(".modal-title"),appendToElement(createTag("h5",s.title)))),parseBoolean(s.close))&&t.append(e);var i=[];if(null!==s.buttons)for(let o=0;ot.trim()).filter(t=>""!==t).join("."),n=(!1===s.footer&&(e+=".mx-1"),createTag("button.btn"+(e=""!==e?"."+e:e)+".button"+o,{type:"button"},t.text));n.addEventListener("click",function(){this._handleButton(o,t,n),void 0!==t.handler&&null!==t.handler&&t.handler(o,t,n)}.bind(this)),i.push(n)}let n=null,o=(parseBoolean(s.footer)&&(n=appendToElement(createTag(".modal-footer"),...i)),null);if(parseBoolean(s.body)&&(o=createTag(".modal-body"),null===t&&parseBoolean(s.close)&&o.append(appendToElement(createTag(".text-end"),e)),null!==s.message&&o.append(createTag("p.message.text-center",s.message)),null!==s.customContent&&o.append(createTag(".custom-content.mx-auto",s.customContent)),null===n)){let t=s.buttonPanelClasses.map(t=>t.trim()).filter(t=>""!==t).join(".");""!==t&&(t="."+t),appendToElement(o,appendToElement(createTag(".buttons"+t),...i))}return appendToElement(createTag(".modal.fade",{tabindex:"-1",role:"dialog","aria-hidden":"true","data-keyboard":"false"}),appendToElement(createTag(".modal-dialog.modal-dialog-centered",{role:"document"}),appendToElement(createTag(".modal-content"),t,o,n)))}}function confirmDialog(t,e="this action needs confirmation",n=null,o=null,s=!0){e=new Dialog({title:e,message:t,buttons:[{text:"Cancel",class:"btn-secondary",handler:o},{text:"Confirm",class:"btn-primary",handler:n}],escapeKeyCancels:s});return e.show(),e}function alertDialog(t,e="Alert",n=null){e=new Dialog({title:e,message:t,buttons:[{text:"Accept",class:"btn-primary",handler:n}],escapeKeyCancels:!0});return e.show(),e}function loadingDialog(t,e=null,n=null){"function"==typeof e&&(n=e,e=null);t=new Dialog({title:null,message:t,buttons:[{text:"Cancel",class:"btn-primary"}],customContent:e,escapeKeyCancels:!1,close:!1,header:!1,footer:!1},n);return t.show(),t}void 0===exports.powerButtons.utils&&(exports.powerButtons.utils={}),Object.assign(exports.powerButtons.utils,{confirmDialog:confirmDialog,alertDialog:alertDialog,loadingDialog:loadingDialog});class PowerButtons{static actionsRegistered={};static registerAction(t){this.actionsRegistered[t.NAME.toLowerCase()]=t,void 0===exports.powerButtons&&(exports.powerButtons={}),void 0===exports.powerButtons.defaults&&(exports.powerButtons.defaults={}),exports.powerButtons.defaults[t.NAME.toLowerCase()]=Object.assign({},t.DEFAULTS)}static getActionSettings(t,e){if(void 0===this.actionsRegistered[t.NAME.toLowerCase()])return console.error(`The action ${t.NAME} is not registered`),{};let n={};return void 0!==exports.powerButtons&&void 0!==exports.powerButtons.defaults&&void 0!==exports.powerButtons.defaults[t.NAME.toLowerCase()]&&(n=exports.powerButtons.defaults[t.NAME.toLowerCase()]),Object.assign({},t.DEFAULTS,n,e)}static addAction(t,e={}){PowerButtons.addActionSupport(t).appendAction(e)}static addActionSupport(t){return void 0===t._powerButtons?t._powerButtons=new PowerButtons(t):t._powerButtons.reset(),t._powerButtons}el=null;current_action=0;actions=[];back_onclick=null;constructor(t){(t._powerButtons=this).el=t,this.current_action=0,this.actions=[],this.back_onclick=null,void 0!==t.onclick&&null!==t.onclick&&(this.back_onclick=t.onclick,t.onclick=null),t.addEventListener("click",this.handlerClick.bind(this))}appendAction(t={}){if(void 0===t.type)throw"The type of the action is mandatory";this.actions.push(t)}handlerClick(t){if(this.current_action>=this.actions.length)this.current_action=0,"function"!=typeof this.back_onclick||this.back_onclick()||t.preventDefault();else{var e=this.actions[this.current_action],n=(t.preventDefault(),t.stopImmediatePropagation(),function(){this.current_action++,this.current_action>=this.actions.length&&void 0!==this.el.click?this.el.click():this.el.dispatchEvent(new Event(t.type,t))}.bind(this)),o=this.constructor.actionsRegistered[e.type];if(void 0===o)throw`The action ${e.type} is not registered`;o.execute(this.el,e,n,()=>this.reset())}}reset(){this.current_action=0}static discoverAll(){Object.entries(this.actionsRegistered).forEach(([,t])=>{t.discoverAll()})}static discover(e,n={}){Object.entries(this.actionsRegistered).forEach(([,t])=>{t.discover(e,n)})}}class Action{static NAME=null;static register(){PowerButtons.registerAction(this)}static DEFAULTS={};static extractOptions(t,e=null,n=null){null===e&&(e=this.NAME.toLowerCase()),null===n&&((n={})[e]=e);var o,s={};for(o in this.DEFAULTS){var i=void 0!==n[i=o]?n[i]:e+CamelToCamel(i);void 0!==t.dataset[i]&&(s[o]=t.dataset[i])}return s}static initialize(t,e={}){PowerButtons.addAction(t,Object.assign({type:this.NAME.toLowerCase()},e))}static discoverAll(){var t=this.NAME.toLowerCase();this.discover(document.querySelectorAll(`[data-${t}]`))}static discover(t,e={},n=!0){void 0===t.length&&(t=[t]);var o,s,i=this.NAME.toLowerCase();for(o of t)n&&void 0!==o._powerButtons&&void 0!==o._powerButtons._discover&&-1!==o._powerButtons._discover.indexOf(i)||void 0!==o.dataset[i]&&(s=Object.assign(this.extractOptions(o,i),e),this.initialize(o,s),void 0!==o._powerButtons)&&(void 0===o._powerButtons._discover&&(o._powerButtons._discover=[]),o._powerButtons._discover.includes(i)||o._powerButtons._discover.push(i))}static execute(t,e,n,o){throw new Error("The execute method must be implemented by the derived class")}}class ActionVerify extends Action{static NAME="Verify";static DEFAULTS={verify:null,form:null,verified:null,notVerified:"The condition for this action is not met",customContentVerified:null,customContentNotVerified:null,titleNotVerified:"The action requires verification",titleVerified:null,buttonAccept:"Accept",buttonClose:!1,escapeKey:!0,header:!0,footer:!0};static execute(el,options,onNextAction,onCancelActions){let settings=PowerButtons.getActionSettings(this,options),result=null,bindObject=searchForm(settings.form);null===bindObject&&(bindObject=document);try{result="function"==typeof settings.verify?settings.verify.bind(bindObject)():"string"==typeof settings.verify?function(){return eval(settings.verify)}.bind(bindObject)():parseBoolean(settings.verify)}catch(e){console.error("Error executing verification function",e),result=!1}let dialog=null,onVerificationSuccess=onNextAction,onVerificationFailure=onCancelActions;result?null===settings.verified&&null===settings.customContentVerified&&null===settings.titleVerified||(dialog=Dialog.create({title:settings.titleVerified,message:settings.verified,customContent:settings.customContentVerified,buttons:[settings.buttonAccept],escapeKeyCancels:settings.escapeKey,close:settings.buttonClose},null,function(t){null!==onVerificationSuccess&&onVerificationSuccess()})):null===settings.notVerified&&null===settings.customContentNotVerified&&null===settings.titleNotVerified||(dialog=Dialog.create({title:settings.titleNotVerified,message:settings.notVerified,customContent:settings.customContentNotVerified,buttons:[settings.buttonAccept],escapeKeyCancels:settings.escapeKey,close:settings.buttonClose},null,function(t){null!==onVerificationFailure&&onVerificationFailure()})),null!==dialog?dialog.show():result?null!==onVerificationSuccess&&onVerificationSuccess():null!==onVerificationFailure&&onVerificationFailure()}}ActionVerify.register();class ActionConfirm extends Action{static NAME="Confirm";static DEFAULTS={confirm:"Please confirm this action",customContent:null,title:"The action requires confirmation",buttonConfirm:"Confirm",buttonCancel:"Cancel",buttonClose:!0,escapeKey:!0};static extractOptions(t,e=null,n=null){t=super.extractOptions(t,e,n);return""==t.confirm.trim()&&delete t.confirm,t}static execute(t,e,n,o){e=PowerButtons.getActionSettings(this,e);Dialog.create({title:e.title,message:e.confirm,customContent:e.customContent,buttons:[e.buttonConfirm,e.buttonCancel],escapeKeyCancels:e.escapeKey,close:e.buttonClose},null,function(t){0===t?null!==n&&n():null!==o&&o()}).show()}}ActionConfirm.register();class ActionAsyncTask extends Action{static NAME="AsyncTask";static DEFAULTS={task:null,message:"Please wait...",customContent:null,title:null,buttonCancel:"Cancel",cancel:null,header:!0,footer:!0};static extractOptions(t,e=null,n){return super.extractOptions(t,e,{task:"asynctask"})}static execute(el,options,onNextAction,onCancelActions){let settings=PowerButtons.getActionSettings(this,options);if(null===settings.task)console.error("The task to execute cannot be null");else{let task=null;if("string"==typeof settings.task)task=async function(){return eval(settings.task)};else{if("function"!=typeof settings.task)return void console.error("The task to execute must be either a string or a function");task=settings.task}let buttons=[],cancelHandler=null,dialog=(null!==settings.cancel&&(buttons=[settings.buttonCancel],"string"==typeof settings.cancel?cancelHandler=function(){eval(settings.cancel)}:"function"==typeof settings.cancel?cancelHandler=settings.cancel:console.error("The cancel handler must be either a string or a function")),Dialog.create({title:settings.title,message:settings.message,customContent:settings.customContent,buttons:buttons,escapeKeyCancels:!1,close:!1,header:void 0!==options.header?settings.header:null!==settings.title&&""!=settings.title,footer:void 0!==options.footer?settings.footer:null!==cancelHandler},function(){cancelHandler(),onCancelActions()},function(t){null!==onNextAction&&onNextAction()}));dialog.show().then(function(){task().finally(function(){dialog.hide()})})}}}ActionAsyncTask.register();class ActionShowMessage extends Action{static NAME="ShowMessage";static DEFAULTS={showmessage:"This is a message",customContent:null,title:null,buttonAccept:"Accept",escapeKey:!0,buttonClose:!0,header:!0,footer:!0};static execute(t,e,n,o){var s=PowerButtons.getActionSettings(this,e);Dialog.create({title:s.title,message:s.showmessage,customContent:s.customContent,buttons:[s.buttonAccept],escapeKeyCancels:s.escapeKey,close:s.buttonClose,header:void 0!==e.header?s.header:null!==s.title&&""!=s.title,footer:void 0!==e.footer?s.footer:null!==s.buttonAccept&&""!=s.buttonAccept},null,function(t){null!==n&&n()}).show()}}ActionShowMessage.register();class ActionFormset extends Action{static NAME="Formset";static DEFAULTS={form:null,fields:{}};static extractOptions(e,n=null,t){var o,s=super.extractOptions(e,n,{form:"formset"}),i={};for(o in e.dataset)if(o.startsWith(n)){let t=o.substring(n.length);""!==t&&t[0]===t[0].toUpperCase()&&(i[t=t.toLocaleLowerCase()]=e.dataset[o])}return void 0===s.fields&&(s.fields={}),Object.assign(s.fields,i),s}static execute(t,e,n,o){var s,i=PowerButtons.getActionSettings(this,e);let l=null,r=[],a=[];if(""==i.form)null!==t.form?l=t.form:a=Array.from(document.querySelectorAll("input")).filter(t=>null===t.form);else if(null===(l=searchForm(i.form)))return void console.error("Form not found "+i.form);for(s in(a=null!==l?Array.from(l.elements):a).forEach(t=>{""!==t.name&&(r[t.name.toLocaleLowerCase()]=t),""!==t.id&&(r[t.id.toLocaleLowerCase()]=t)}),i.fields)if(void 0!==r[s]){var c=i.fields[s];let t=getValueWithJavascriptSupport(c,null!==l?l:r);if("function"==typeof t)try{t=t()}catch(t){console.error("Error executing "+c,t);continue}r[s].value=t}n()}}ActionFormset.register();class ActionFormButton extends Action{static NAME="FormButton";static DEFAULTS={formbutton:null,method:"post",formClass:"formbutton",convertCase:"none",formId:null,fields:{}};static extractOptions(e,n=null,t=null){var o,s=super.extractOptions(e,n,t),i={};for(o in n+="Field",e.dataset)if(o.startsWith(n)){let t=o.substring(n.length);if(""!==t&&t[0]===t[0].toUpperCase()){switch(s.convertCase){case"kebab":t=pascalToKebab(t);break;case"snake":t=pascalToSnake(t);break;case"camel":t=pascalToCamel(t)}i[t]=e.dataset[o]}}return void 0===s.fields&&(s.fields={}),Object.assign(s.fields,i),s}static initialize(t,e={}){for(var n=PowerButtons.getActionSettings(this,e),o=document.createElement("form"),s=(o.method=n.method,isEmpty(n.formbutton)||(o.action=n.formbutton),null!==n.formId&&(o.id=n.formId),n.formClass.split(" ")),i=0;i"_"+t.toLowerCase()).replace(/^_*/,"")}function pascalToKebab(t){return t.replace(/[A-Z]/g,t=>"-"+t.toLowerCase()).replace(/^-*/,"")}function snakeCaseToCamel(t){t.replace(/-([a-z])/g,t=>t[1].toUpperCase())}function pascalToCamel(t){return t.charAt(0).toLowerCase()+t.slice(1)}function CamelToCamel(t){return t.charAt(0).toUpperCase()+t.slice(1)}function isElement(t){t instanceof Element||0 instanceof HTMLDocument}function parseBoolean(t){return"boolean"==typeof t?t:"string"==typeof t?"true"===(t=t.toLowerCase())||"yes"===t||"1"===t:!!t}function createTag(t,e={},n=null){var o=t.split("#");let s=null;var i,o=(t=1==o.length?o[0]:(o[1]=o[1].split("."),s=o[1][0],[o[0],...o[1].slice(1)].join("."))).split("."),l=(""===(t=o[0])&&(t="div"),"string"==typeof e&&(n=e,e={}),null!==n&&(e.innerHTML=n),null!==s&&(e.id=s),e.className=[e.className,...o.slice(1)].filter(function(t){return""!==(""+t).trim()}).join(" "),document.createElement(t));for(i in e)void 0!==l[i]?l[i]=e[i]:l.setAttribute(i,e[i]);return l}function appendToElement(t,...e){e=e.filter(t=>null!=t);return t.append(...e),t}function searchForm(t){let e=null;return null!==t&&void 0===(e=document.forms[t])&&null===(e=document.querySelector(t))&&console.warn(`form ${t} not found`),null!==e&&"form"!==e.tagName.toLowerCase()&&console.warn(`form ${t} is not a form`),e}function getValueWithJavascriptSupport(value,context=null){if("function"==typeof value)return value.bind(context);if("string"==typeof value){let internalValue=value.trim();if(internalValue.startsWith("javascript:"))try{let f=internalValue.substring(11);value=function(){return eval(f)}.bind(context)}catch(e){console.error(`Error executing javascript code ${internalValue.substring(11)}, error: `+e),value=null}}return value}function promiseForEvent(e,n){let o=null;var t=new Promise(t=>{o=t});return e.addEventListener(n,function t(){e.removeEventListener(n,t),o()}),t}function isEmpty(t){return null==t||(t instanceof Array?0===t.length:t instanceof Object?0===Object.keys(t).length:"string"==typeof t&&""===t.trim())}void 0===exports&&(exports=window),exports.powerButtons=function(t,e=null,n=null){let o=null,s=[],i={};if("string"==typeof t)if(null===(o=function(t){for(var e in PowerButtons.actionsRegistered)if(t.toLocaleLowerCase()===e.toLocaleLowerCase())return e;return null}(t))){if(0===(s=document.querySelectorAll(t)).length)return void console.error(`Parameter ${t} is neither the name of a registered plugin nor a valid selector`);2this._handleButton(-1,null,e)),parseBoolean(s.header)&&(t=createTag(".modal-header"),null!==s.title&&t.append(appendToElement(createTag(".modal-title"),appendToElement(createTag("h5",s.title)))),parseBoolean(s.close))&&t.append(e);var i=[];if(null!==s.buttons)for(let o=0;ot.trim()).filter(t=>""!==t).join("."),n=(!1===s.footer&&(e+=".mx-1"),createTag("button.btn"+(e=""!==e?"."+e:e)+".button"+o,{type:"button"},t.text));n.addEventListener("click",function(){this._handleButton(o,t,n),void 0!==t.handler&&null!==t.handler&&t.handler(o,t,n)}.bind(this)),i.push(n)}let n=null,o=(parseBoolean(s.footer)&&(n=appendToElement(createTag(".modal-footer"),...i)),null);if(parseBoolean(s.body)&&(o=createTag(".modal-body"),null===t&&parseBoolean(s.close)&&o.append(appendToElement(createTag(".text-end"),e)),null!==s.message&&o.append(createTag("p.message.text-center",s.message)),null!==s.customContent&&o.append(createTag(".custom-content.mx-auto",s.customContent)),null===n)){let t=s.buttonPanelClasses.map(t=>t.trim()).filter(t=>""!==t).join(".");""!==t&&(t="."+t),appendToElement(o,appendToElement(createTag(".buttons"+t),...i))}return appendToElement(createTag(".modal.fade",{tabindex:"-1",role:"dialog","aria-hidden":"true","data-keyboard":"false"}),appendToElement(createTag(".modal-dialog.modal-dialog-centered",{role:"document"}),appendToElement(createTag(".modal-content"),t,o,n)))}}function confirmDialog(t,e="this action needs confirmation",n=null,o=null,s=!0){e=new Dialog({title:e,message:t,buttons:[{text:"Cancel",class:"btn-secondary",handler:o},{text:"Confirm",class:"btn-primary",handler:n}],escapeKeyCancels:s});return e.show(),e}function alertDialog(t,e="Alert",n=null){e=new Dialog({title:e,message:t,buttons:[{text:"Accept",class:"btn-primary",handler:n}],escapeKeyCancels:!0});return e.show(),e}function loadingDialog(t,e=null,n=null){"function"==typeof e&&(n=e,e=null);t=new Dialog({title:null,message:t,buttons:[{text:"Cancel",class:"btn-primary"}],customContent:e,escapeKeyCancels:!1,close:!1,header:!1,footer:!1},n);return t.show(),t}void 0===exports.powerButtons.utils&&(exports.powerButtons.utils={}),Object.assign(exports.powerButtons.utils,{confirmDialog:confirmDialog,alertDialog:alertDialog,loadingDialog:loadingDialog});class PowerButtons{static actionsRegistered={};static registerAction(t){this.actionsRegistered[t.NAME.toLowerCase()]=t,void 0===exports.powerButtons&&(exports.powerButtons={}),void 0===exports.powerButtons.defaults&&(exports.powerButtons.defaults={}),exports.powerButtons.defaults[t.NAME.toLowerCase()]=Object.assign({},t.DEFAULTS)}static getActionSettings(t,e){if(void 0===this.actionsRegistered[t.NAME.toLowerCase()])return console.error(`The action ${t.NAME} is not registered`),{};let n={};return void 0!==exports.powerButtons&&void 0!==exports.powerButtons.defaults&&void 0!==exports.powerButtons.defaults[t.NAME.toLowerCase()]&&(n=exports.powerButtons.defaults[t.NAME.toLowerCase()]),Object.assign({},t.DEFAULTS,n,e)}static addAction(t,e={}){PowerButtons.addActionSupport(t).appendAction(e)}static addActionSupport(t){return void 0===t._powerButtons?t._powerButtons=new PowerButtons(t):t._powerButtons.reset(),t._powerButtons}el=null;current_action=0;actions=[];back_onclick=null;constructor(t){(t._powerButtons=this).el=t,this.current_action=0,this.actions=[],this.back_onclick=null,void 0!==t.onclick&&null!==t.onclick&&(this.back_onclick=t.onclick,t.onclick=null),t.addEventListener("click",this.handlerClick.bind(this))}appendAction(t={}){if(void 0===t.type)throw"The type of the action is mandatory";this.actions.push(t)}handlerClick(e){if(this.current_action>=this.actions.length)this.current_action=0,"function"!=typeof this.back_onclick||this.back_onclick()||e.preventDefault();else{var t=this.actions[this.current_action],n=(e.preventDefault(),e.stopImmediatePropagation(),function(t=!1){t?this.current_action=this.actions.length:this.current_action++,this.current_action>=this.actions.length&&void 0!==this.el.click?this.el.click():this.el.dispatchEvent(new Event(e.type,e))}.bind(this)),o=this.constructor.actionsRegistered[t.type];if(void 0===o)throw`The action ${t.type} is not registered`;o.execute(this.el,t,n,()=>this.reset())}}reset(){this.current_action=0}static discoverAll(){Object.entries(this.actionsRegistered).forEach(([,t])=>{t.discoverAll()})}static discover(e,n={}){Object.entries(this.actionsRegistered).forEach(([,t])=>{t.discover(e,n)})}}class Action{static NAME=null;static register(){PowerButtons.registerAction(this)}static DEFAULTS={};static extractOptions(t,e=null,n=null){null===e&&(e=this.NAME.toLowerCase()),null===n&&((n={})[e]=e);var o,s={};for(o in this.DEFAULTS){var i=void 0!==n[i=o]?n[i]:e+CamelToCamel(i);void 0!==t.dataset[i]&&(s[o]=t.dataset[i])}return s}static initialize(t,e={}){PowerButtons.addAction(t,Object.assign({type:this.NAME.toLowerCase()},e))}static discoverAll(){var t=this.NAME.toLowerCase();this.discover(document.querySelectorAll(`[data-${t}]`))}static discover(t,e={},n=!0){void 0===t.length&&(t=[t]);var o,s,i=this.NAME.toLowerCase();for(o of t)n&&void 0!==o._powerButtons&&void 0!==o._powerButtons._discover&&-1!==o._powerButtons._discover.indexOf(i)||void 0!==o.dataset[i]&&(s=Object.assign(this.extractOptions(o,i),e),this.initialize(o,s),void 0!==o._powerButtons)&&(void 0===o._powerButtons._discover&&(o._powerButtons._discover=[]),o._powerButtons._discover.includes(i)||o._powerButtons._discover.push(i))}static execute(t,e,n,o){throw new Error("The execute method must be implemented by the derived class")}}class ActionOverride extends Action{static NAME="Override";static DEFAULTS={override:null,form:null,overridden:null,customContent:null,title:null,buttonAccept:"Accept",escapeKey:!0};static execute(el,options,onNextAction,onCancelActions){let settings=PowerButtons.getActionSettings(this,options),result=null,bindObject=searchForm(settings.form);null===bindObject&&(bindObject=document);try{result="function"==typeof settings.override?settings.override.bind(bindObject)():"string"==typeof settings.override?function(){return eval(settings.override)}.bind(bindObject)():parseBoolean(settings.override)}catch(e){console.error("Error executing override function",e),result=!1}if(result)if(null!==settings.overridden||null!==settings.customContent||null!==settings.title){let dialog=null;dialog=Dialog.create({title:settings.title,message:settings.overridden,customContent:settings.customContent,buttons:[settings.buttonAccept],escapeKeyCancels:settings.escapeKey,close:settings.buttonClose},null,function(t){onNextAction(!0)}),dialog.show()}else onNextAction(!0);else onNextAction()}}ActionOverride.register();class ActionVerify extends Action{static NAME="Verify";static DEFAULTS={verify:null,form:null,verified:null,notVerified:"The condition for this action is not met",customContentVerified:null,customContentNotVerified:null,titleNotVerified:"The action requires verification",titleVerified:null,buttonAccept:"Accept",buttonClose:!1,escapeKey:!0};static execute(el,options,onNextAction,onCancelActions){let settings=PowerButtons.getActionSettings(this,options),result=null,bindObject=searchForm(settings.form);null===bindObject&&(bindObject=document);try{result="function"==typeof settings.verify?settings.verify.bind(bindObject)():"string"==typeof settings.verify?function(){return eval(settings.verify)}.bind(bindObject)():parseBoolean(settings.verify)}catch(e){console.error("Error executing verification function",e),result=!1}let dialog=null,onVerificationSuccess=onNextAction,onVerificationFailure=onCancelActions;result?null===settings.verified&&null===settings.customContentVerified&&null===settings.titleVerified||(dialog=Dialog.create({title:settings.titleVerified,message:settings.verified,customContent:settings.customContentVerified,buttons:[settings.buttonAccept],escapeKeyCancels:settings.escapeKey,close:settings.buttonClose},null,function(t){null!==onVerificationSuccess&&onVerificationSuccess()})):null===settings.notVerified&&null===settings.customContentNotVerified&&null===settings.titleNotVerified||(dialog=Dialog.create({title:settings.titleNotVerified,message:settings.notVerified,customContent:settings.customContentNotVerified,buttons:[settings.buttonAccept],escapeKeyCancels:settings.escapeKey,close:settings.buttonClose},null,function(t){null!==onVerificationFailure&&onVerificationFailure()})),null!==dialog?dialog.show():result?null!==onVerificationSuccess&&onVerificationSuccess():null!==onVerificationFailure&&onVerificationFailure()}}ActionVerify.register();class ActionConfirm extends Action{static NAME="Confirm";static DEFAULTS={confirm:"Please confirm this action",customContent:null,title:"The action requires confirmation",buttonConfirm:"Confirm",buttonCancel:"Cancel",buttonClose:!0,escapeKey:!0};static extractOptions(t,e=null,n=null){t=super.extractOptions(t,e,n);return""==t.confirm.trim()&&delete t.confirm,t}static execute(t,e,n,o){e=PowerButtons.getActionSettings(this,e);Dialog.create({title:e.title,message:e.confirm,customContent:e.customContent,buttons:[e.buttonConfirm,e.buttonCancel],escapeKeyCancels:e.escapeKey,close:e.buttonClose},null,function(t){0===t?null!==n&&n():null!==o&&o()}).show()}}ActionConfirm.register();class ActionAsyncTask extends Action{static NAME="AsyncTask";static DEFAULTS={task:null,message:"Please wait...",customContent:null,title:null,buttonCancel:"Cancel",cancel:null,header:!0,footer:!0};static extractOptions(t,e=null,n){return super.extractOptions(t,e,{task:"asynctask"})}static execute(el,options,onNextAction,onCancelActions){let settings=PowerButtons.getActionSettings(this,options);if(null===settings.task)console.error("The task to execute cannot be null");else{let task=null;if("string"==typeof settings.task)task=async function(){return eval(settings.task)};else{if("function"!=typeof settings.task)return void console.error("The task to execute must be either a string or a function");task=settings.task}let buttons=[],cancelHandler=null,dialog=(null!==settings.cancel&&(buttons=[settings.buttonCancel],"string"==typeof settings.cancel?cancelHandler=function(){eval(settings.cancel)}:"function"==typeof settings.cancel?cancelHandler=settings.cancel:console.error("The cancel handler must be either a string or a function")),Dialog.create({title:settings.title,message:settings.message,customContent:settings.customContent,buttons:buttons,escapeKeyCancels:!1,close:!1,header:void 0!==options.header?settings.header:null!==settings.title&&""!=settings.title,footer:void 0!==options.footer?settings.footer:null!==cancelHandler},function(){cancelHandler(),onCancelActions()},function(t){null!==onNextAction&&onNextAction()}));dialog.show().then(function(){task().finally(function(){dialog.hide()})})}}}ActionAsyncTask.register();class ActionShowMessage extends Action{static NAME="ShowMessage";static DEFAULTS={showmessage:"This is a message",customContent:null,title:null,buttonAccept:"Accept",escapeKey:!0,buttonClose:!0,header:!0,footer:!0};static execute(t,e,n,o){var s=PowerButtons.getActionSettings(this,e);Dialog.create({title:s.title,message:s.showmessage,customContent:s.customContent,buttons:[s.buttonAccept],escapeKeyCancels:s.escapeKey,close:s.buttonClose,header:void 0!==e.header?s.header:null!==s.title&&""!=s.title,footer:void 0!==e.footer?s.footer:null!==s.buttonAccept&&""!=s.buttonAccept},null,function(t){null!==n&&n()}).show()}}ActionShowMessage.register();class ActionFormset extends Action{static NAME="Formset";static DEFAULTS={form:null,fields:{}};static extractOptions(e,n=null,t){var o,s=super.extractOptions(e,n,{form:"formset"}),i={};for(o in e.dataset)if(o.startsWith(n)){let t=o.substring(n.length);""!==t&&t[0]===t[0].toUpperCase()&&(i[t=t.toLocaleLowerCase()]=e.dataset[o])}return void 0===s.fields&&(s.fields={}),Object.assign(s.fields,i),s}static execute(t,e,n,o){var s,i=PowerButtons.getActionSettings(this,e);let l=null,r=[],a=[];if(""==i.form)null!==t.form?l=t.form:a=Array.from(document.querySelectorAll("input")).filter(t=>null===t.form);else if(null===(l=searchForm(i.form)))return void console.error("Form not found "+i.form);for(s in(a=null!==l?Array.from(l.elements):a).forEach(t=>{""!==t.name&&(r[t.name.toLocaleLowerCase()]=t),""!==t.id&&(r[t.id.toLocaleLowerCase()]=t)}),i.fields)if(void 0!==r[s]){var c=i.fields[s];let t=getValueWithJavascriptSupport(c,null!==l?l:r);if("function"==typeof t)try{t=t()}catch(t){console.error("Error executing "+c,t);continue}r[s].value=t}n()}}ActionFormset.register();class ActionFormButton extends Action{static NAME="FormButton";static DEFAULTS={formbutton:null,method:"post",formClass:"formbutton",convertCase:"none",formId:null,fields:{}};static extractOptions(e,n=null,t=null){var o,s=super.extractOptions(e,n,t),i={};for(o in n+="Field",e.dataset)if(o.startsWith(n)){let t=o.substring(n.length);if(""!==t&&t[0]===t[0].toUpperCase()){switch(s.convertCase){case"kebab":t=pascalToKebab(t);break;case"snake":t=pascalToSnake(t);break;case"camel":t=pascalToCamel(t)}i[t]=e.dataset[o]}}return void 0===s.fields&&(s.fields={}),Object.assign(s.fields,i),s}static initialize(t,e={}){for(var n=PowerButtons.getActionSettings(this,e),o=document.createElement("form"),s=(o.method=n.method,isEmpty(n.formbutton)||(o.action=n.formbutton),null!==n.formId&&(o.id=n.formId),n.formClass.split(" ")),i=0;i= this.actions.length) { if (this.el.click !== undefined) { this.el.click() @@ -754,6 +758,61 @@ throw new Error("The execute method must be implemented by the derived class") } } + class ActionOverride extends Action { + static NAME = "Override"; + static DEFAULTS = { + override: null, + form: null, + overridden: null, + customContent: null, + title: null, + buttonAccept: "Accept", + escapeKey: true + }; + static execute(el, options, onNextAction, onCancelActions) { + let settings = PowerButtons.getActionSettings(this, options); + let result = null; + let bindObject = searchForm(settings.form); + if (bindObject === null) { + bindObject = document + } + try { + if (typeof settings.override === "function") { + result = settings.override.bind(bindObject)() + } else if (typeof settings.override === "string") { + result = function () { + return eval(settings.override) + }.bind(bindObject)() + } else { + result = parseBoolean(settings.override) + } + } catch (e) { + console.error("Error executing override function", e); + result = false + } + if (result) { + if (settings.overridden !== null || settings.customContent !== null || settings.title !== null) { + let dialog = null; + dialog = Dialog.create({ + title: settings.title, + message: settings.overridden, + customContent: settings.customContent, + buttons: [settings.buttonAccept], + escapeKeyCancels: settings.escapeKey, + close: settings.buttonClose + }, null, function (result) { + onNextAction(true) + }); + dialog.show() + } else { + onNextAction(true) + } + } else { + onNextAction() + } + } + } + ActionOverride.register(); class ActionVerify extends Action { static NAME = "Verify"; static DEFAULTS = { @@ -767,9 +826,7 @@ titleVerified: null, buttonAccept: "Accept", buttonClose: false, - escapeKey: true, - header: true, - footer: true + escapeKey: true }; static execute(el, options, onNextAction, onCancelActions) { let settings = PowerButtons.getActionSettings(this, options); diff --git a/dist/powerbuttons.js b/dist/powerbuttons.js index e918c3b..a754b8b 100644 --- a/dist/powerbuttons.js +++ b/dist/powerbuttons.js @@ -100,7 +100,7 @@ PowerButtons.discover(els, options); } }; - exports.powerButtons.version = "2.1.0"; + exports.powerButtons.version = "2.1.2"; exports.powerButtons.plugins = function () { return Object.keys(PowerButtons.actionsRegistered); }; @@ -657,8 +657,12 @@ let currentActionSettings = this.actions[this.current_action]; e.preventDefault(); e.stopImmediatePropagation(); - let onNextAction = function () { - this.current_action++; + let onNextAction = function (override = false) { + if (override) { + this.current_action = this.actions.length; + } else { + this.current_action++; + } if (this.current_action >= this.actions.length) { if (this.el.click !== undefined) { this.el.click(); @@ -754,6 +758,61 @@ throw new Error("The execute method must be implemented by the derived class"); } } + class ActionOverride extends Action { + static NAME = "Override"; + static DEFAULTS = { + override: null, + form: null, + overridden: null, + customContent: null, + title: null, + buttonAccept: "Accept", + escapeKey: true + }; + static execute(el, options, onNextAction, onCancelActions) { + let settings = PowerButtons.getActionSettings(this, options); + let result = null; + let bindObject = searchForm(settings.form); + if (bindObject === null) { + bindObject = document; + } + try { + if (typeof settings.override === "function") { + result = settings.override.bind(bindObject)(); + } else if (typeof settings.override === "string") { + result = function () { + return eval(settings.override); + }.bind(bindObject)(); + } else { + result = parseBoolean(settings.override); + } + } catch (e) { + console.error("Error executing override function", e); + result = false; + } + if (result) { + if (settings.overridden !== null || settings.customContent !== null || settings.title !== null) { + let dialog = null; + dialog = Dialog.create({ + title: settings.title, + message: settings.overridden, + customContent: settings.customContent, + buttons: [settings.buttonAccept], + escapeKeyCancels: settings.escapeKey, + close: settings.buttonClose + }, null, function (result) { + onNextAction(true); + }); + dialog.show(); + } else { + onNextAction(true); + } + } else { + onNextAction(); + } + } + } + ActionOverride.register(); class ActionVerify extends Action { static NAME = "Verify"; static DEFAULTS = { @@ -767,9 +826,7 @@ titleVerified: null, buttonAccept: "Accept", buttonClose: false, - escapeKey: true, - header: true, - footer: true + escapeKey: true }; static execute(el, options, onNextAction, onCancelActions) { let settings = PowerButtons.getActionSettings(this, options); diff --git a/dist/powerbuttons.min.js b/dist/powerbuttons.min.js index 292152c..1da8598 100644 --- a/dist/powerbuttons.min.js +++ b/dist/powerbuttons.min.js @@ -1,2 +1,2 @@ /* Copyright 2021 Carlos A. (https://github.com/dealfonso); License: http://www.apache.org/licenses/LICENSE-2.0 */ -(function(exports){"use strict";if(typeof exports==="undefined"){var exports=window}exports.powerButtons=function(t,n=null,e=null){let i=null;let o=[];let s={};function l(t){for(let e in PowerButtons.actionsRegistered){if(t.toLocaleLowerCase()===e.toLocaleLowerCase()){return e}}return null}if(typeof t==="string"){i=l(t);if(i===null){o=document.querySelectorAll(t);if(o.length===0){console.error(`Parameter ${t} is neither the name of a registered plugin nor a valid selector`);return}if(arguments.length>2){console.warn(`Ignoring extra parameters`)}s=n}else{let t=false;if(typeof n==="string"){o=document.querySelectorAll(n);t=true}else if(n instanceof HTMLElement){o=[n];t=true}else if(n.length!==undefined){t=true;for(let e in n){if(!(n[e]instanceof HTMLElement)){t=false;break}}if(t){o=n}}if(!t){console.error(`Parameter ${n} is neither a valid selector, a list of elements or an HTMLElement`);return}s=e}}else if(t instanceof HTMLElement){o=[t]}else if(t.length!==undefined){for(let e in t){if(!(t[e]instanceof HTMLElement)){console.error(`Parameter ${t} is neither a valid selector, a list of elements or an HTMLElement`);return}}o=t}else{console.error(`Parameter ${t} is neither a valid selector, a list of elements or an HTMLElement`);return}if(s===null){s={}}if(typeof s!=="object"){console.error(`Options parameter must be an object`);return}if(i!==null){let t=PowerButtons.actionsRegistered[i];for(let e of o){t.initialize(e,s)}}else{PowerButtons.discover(o,s)}};exports.powerButtons.version="2.1.0";exports.powerButtons.plugins=function(){return Object.keys(PowerButtons.actionsRegistered)};exports.powerButtons.discoverAll=function(){PowerButtons.discoverAll()};exports.powerButtons.discover=function(e,t){PowerButtons.discover(e,t)};if(document.addEventListener!==undefined){document.addEventListener("DOMContentLoaded",function(e){PowerButtons.discoverAll()})}if(exports.$!==undefined){exports.$.fn.powerButtons=function(e,t={}){exports.powerButtons(e,this,t);return this};exports.$.fn.powerButtons.version=exports.powerButtons.version;exports.$.fn.powerButtons.plugins=exports.powerButtons.plugins}function pascalToSnake(e){return e.replace(/[A-Z]/g,e=>`_${e.toLowerCase()}`).replace(/^_*/,"")}function pascalToKebab(e){return e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`).replace(/^-*/,"")}function snakeCaseToCamel(e){return e.replace(/-([a-z])/g,e=>e[1].toUpperCase())}function pascalToCamel(e){return e.charAt(0).toLowerCase()+e.slice(1)}function CamelToCamel(e){return e.charAt(0).toUpperCase()+e.slice(1)}function isElement(e){return e instanceof Element||e instanceof HTMLDocument}function parseBoolean(e){if(typeof e==="boolean"){return e}if(typeof e==="string"){e=e.toLowerCase();if(e==="true"||e==="yes"||e==="1"){return true}return false}return!!e}function createTag(e,t={},n=null){let i=e.split("#");let o=null;if(i.length==1){e=i[0]}else{i[1]=i[1].split(".");o=i[1][0];e=[i[0],...i[1].slice(1)].join(".")}let s=e.split(".");e=s[0];if(e===""){e="div"}if(typeof t==="string"){n=t;t={}}if(n!==null){t.innerHTML=n}if(o!==null){t.id=o}t.className=[t.className,...s.slice(1)].filter(function(e){return`${e}`.trim()!==""}).join(" ");let l=document.createElement(e);for(let e in t){if(l[e]!==undefined){l[e]=t[e]}else{l.setAttribute(e,t[e])}}return l}function appendToElement(e,...t){let n=t.filter(e=>e!==null&&e!==undefined);e.append(...n);return e}function searchForm(e){let t=null;if(e!==null){t=document.forms[e];if(t===undefined){t=document.querySelector(e);if(t===null){console.warn(`form ${e} not found`)}}}if(t!==null){if(t.tagName.toLowerCase()!=="form"){console.warn(`form ${e} is not a form`)}}return t}function getValueWithJavascriptSupport(value,context=null){if(typeof value==="function"){return value.bind(context)}if(typeof value==="string"){let internalValue=value.trim();if(internalValue.startsWith("javascript:")){try{let f=internalValue.substring(11);value=function(){return eval(f)}.bind(context)}catch(e){console.error(`Error executing javascript code ${internalValue.substring(11)}, error: ${e}`);value=null}}}return value}function promiseForEvent(e,t){let n=null;let i=new Promise(e=>{n=e});let o=function(){e.removeEventListener(t,o);n()};e.addEventListener(t,o);return i}function isEmpty(e){if(e===null||e===undefined){return true}if(e instanceof Array){return e.length===0}if(e instanceof Object){return Object.keys(e).length===0}if(typeof e==="string"){return e.trim()===""}return false}class DialogLegacy{DEFAULTS={message:"Main message",buttonCount:2};constructor(e={},t=null,n=null){this.options={...this.DEFAULTS,...e};this.buttonCount=this.options.buttons.length;this.options.buttons=["Accept","Cancel"];this.result=null;this.onButton=t;this.onHidden=n}dispose(){}show(e=null,t=null){if(e!==null){this.onButton=e}if(t!==null){this.onHidden=t}switch(this.buttonCount){case 0:case 1:alert(this.message);this.result=0;break;case 2:this.result=confirm(this.options.message)?0:1;break;default:throw`Unsupported button count ${this.buttonCount}`}if(this.onButton!==null){this.onButton(this.result,{button:this.result,text:this.options.buttons[this.result]},null)}if(this.onHidden!==null){this.onHidden(this.result,{button:this.result,text:this.options.buttons[this.result]},null)}}}class Dialog{static create(e={},t=null,n=null){if(exports.bootstrap===undefined||exports.bootstrap.Modal===undefined){return new DialogLegacy(e,t,n)}if(e.selector!==undefined&&e.selector!==null||e.dialogFunction!==undefined&&e.dialogFunction!==null){throw new Error("not implemented, yet")}return new Dialog(e,t,n)}DEFAULTS={title:"Title",message:"Main message",customContent:null,buttons:["Accept"],buttonClasses:["btn-primary","btn-secondary"],buttonPanelClasses:["text-end"],escapeKeyCancels:true,header:true,footer:true,body:true,close:true};dialog=null;options=null;modal=null;onButton=null;result=null;onHidden=null;onButton=null;constructor(e={},t=null,n=null){if(exports.bootstrap===undefined||exports.bootstrap.Modal===undefined){throw new Error("Bootstrap is required to use this class")}this.options={...this.DEFAULTS,...e};let i=[];for(let t=0;t=0){this.onHidden(this.result,{button:this.result,text:this.options.buttons[this.result]})}else{this.onHidden(this.result,null)}}}dispose(){if(this.modal!==null){this.modal.dispose();this.modal=null}this.dialog.remove();this.dialog=null}show(e=null,t=null){if(this.dialog===null){this.dialog=this._build_dialog(this.options)}if(this.modal===null){this.modal=new bootstrap.Modal(this.dialog,{backdrop:this.options.escapeKeyCancels?true:"static",keyboard:this.options.escapeKeyCancels})}this.dialog.addEventListener("hidden.bs.modal",this._hiddenHandler);this.result=null;if(e!==null){this.onButton=e}if(t!==null){this.onHidden=t}this.modal.show();return promiseForEvent(this.dialog,"shown.bs.modal")}hide(){this.modal.hide();return promiseForEvent(this.dialog,"hidden.bs.modal")}_handleButton(e,t,n){this.result=e;let i=true;if(this.onButton!==null){i=!(this.onButton(e,t,n)===false)}if(i){this.hide()}}_build_dialog(o={}){let e=null;let t=null;if(parseBoolean(o.close)){t=createTag("button.close.btn-close",{type:"button","aria-label":"Close"});t.addEventListener("click",()=>this._handleButton(-1,null,t))}if(parseBoolean(o.header)){e=createTag(".modal-header");if(o.title!==null){e.append(appendToElement(createTag(".modal-title"),appendToElement(createTag("h5",o.title))))}if(parseBoolean(o.close)){e.append(t)}}let s=[];if(o.buttons!==null){for(let i=0;ie.trim()).filter(e=>e!=="").join(".");if(o.footer===false){t+=".mx-1"}if(t!==""){t="."+t}let n=createTag("button.btn"+t+".button"+i,{type:"button"},e.text);n.addEventListener("click",function(){this._handleButton(i,e,n);if(e.handler!==undefined&&e.handler!==null){e.handler(i,e,n)}}.bind(this));s.push(n)}}let n=null;if(parseBoolean(o.footer)){n=appendToElement(createTag(".modal-footer"),...s)}let i=null;if(parseBoolean(o.body)){i=createTag(".modal-body");if(e===null){if(parseBoolean(o.close)){i.append(appendToElement(createTag(".text-end"),t))}}if(o.message!==null){i.append(createTag("p.message.text-center",o.message))}if(o.customContent!==null){i.append(createTag(".custom-content.mx-auto",o.customContent))}if(n===null){let e=o.buttonPanelClasses.map(e=>e.trim()).filter(e=>e!=="").join(".");if(e!==""){e="."+e}appendToElement(i,appendToElement(createTag(".buttons"+e),...s))}}let l=appendToElement(createTag(".modal.fade",{tabindex:"-1",role:"dialog","aria-hidden":"true","data-keyboard":"false"}),appendToElement(createTag(".modal-dialog.modal-dialog-centered",{role:"document"}),appendToElement(createTag(".modal-content"),e,i,n)));return l}}function confirmDialog(e,t="this action needs confirmation",n=null,i=null,o=true){let s=new Dialog({title:t,message:e,buttons:[{text:"Cancel",class:"btn-secondary",handler:i},{text:"Confirm",class:"btn-primary",handler:n}],escapeKeyCancels:o});s.show();return s}function alertDialog(e,t="Alert",n=null){let i=new Dialog({title:t,message:e,buttons:[{text:"Accept",class:"btn-primary",handler:n}],escapeKeyCancels:true});i.show();return i}function loadingDialog(e,t=null,n=null){if(typeof t==="function"){n=t;t=null}let i=new Dialog({title:null,message:e,buttons:[{text:"Cancel",class:"btn-primary"}],customContent:t,escapeKeyCancels:false,close:false,header:false,footer:false},n);i.show();return i}if(exports.powerButtons.utils===undefined){exports.powerButtons.utils={}}Object.assign(exports.powerButtons.utils,{confirmDialog:confirmDialog,alertDialog:alertDialog,loadingDialog:loadingDialog});class PowerButtons{static actionsRegistered={};static registerAction(e){this.actionsRegistered[e.NAME.toLowerCase()]=e;if(exports.powerButtons===undefined){exports.powerButtons={}}if(exports.powerButtons.defaults===undefined){exports.powerButtons.defaults={}}exports.powerButtons.defaults[e.NAME.toLowerCase()]=Object.assign({},e.DEFAULTS)}static getActionSettings(e,t){if(this.actionsRegistered[e.NAME.toLowerCase()]===undefined){console.error(`The action ${e.NAME} is not registered`);return{}}let n={};if(exports.powerButtons!==undefined&&exports.powerButtons.defaults!==undefined&&exports.powerButtons.defaults[e.NAME.toLowerCase()]!==undefined){n=exports.powerButtons.defaults[e.NAME.toLowerCase()]}return Object.assign({},e.DEFAULTS,n,t)}static addAction(e,t={}){let n=PowerButtons.addActionSupport(e);n.appendAction(t)}static addActionSupport(e){if(e._powerButtons===undefined){e._powerButtons=new PowerButtons(e)}else{e._powerButtons.reset()}return e._powerButtons}el=null;current_action=0;actions=[];back_onclick=null;constructor(e){e._powerButtons=this;this.el=e;this.current_action=0;this.actions=[];this.back_onclick=null;if(e.onclick!==undefined&&e.onclick!==null){this.back_onclick=e.onclick;e.onclick=null}e.addEventListener("click",this.handlerClick.bind(this))}appendAction(e={}){if(e.type===undefined){throw"The type of the action is mandatory"}this.actions.push(e)}handlerClick(e){if(this.current_action>=this.actions.length){this.current_action=0;if(typeof this.back_onclick==="function"){if(!this.back_onclick()){e.preventDefault()}}return}let t=this.actions[this.current_action];e.preventDefault();e.stopImmediatePropagation();let n=function(){this.current_action++;if(this.current_action>=this.actions.length){if(this.el.click!==undefined){this.el.click()}else{this.el.dispatchEvent(new Event(e.type,e))}}else{this.el.dispatchEvent(new Event(e.type,e))}}.bind(this);let i=this.constructor.actionsRegistered[t.type];if(i===undefined){throw`The action ${t.type} is not registered`}i.execute(this.el,t,n,()=>this.reset())}reset(){this.current_action=0}static discoverAll(){Object.entries(this.actionsRegistered).forEach(([e,t])=>{t.discoverAll()})}static discover(n,i={}){Object.entries(this.actionsRegistered).forEach(([e,t])=>{t.discover(n,i)})}}class Action{static NAME=null;static register(){PowerButtons.registerAction(this)}static DEFAULTS={};static extractOptions(n,i=null,o=null){if(i===null){i=this.NAME.toLowerCase()}if(o===null){o={};o[i]=i}let s={};for(let t in this.DEFAULTS){let e=t;if(o[e]!==undefined){e=o[e]}else{e=i+CamelToCamel(e)}if(n.dataset[e]!==undefined){s[t]=n.dataset[e]}}return s}static initialize(e,t={}){PowerButtons.addAction(e,Object.assign({type:this.NAME.toLowerCase()},t))}static discoverAll(){let e=this.NAME.toLowerCase();this.discover(document.querySelectorAll(`[data-${e}]`))}static discover(e,n={},i=true){if(e.length===undefined){e=[e]}let o=this.NAME.toLowerCase();for(let t of e){if(i&&t._powerButtons!==undefined&&t._powerButtons._discover!==undefined&&t._powerButtons._discover.indexOf(o)!==-1){continue}if(t.dataset[o]===undefined){continue}let e=Object.assign(this.extractOptions(t,o),n);this.initialize(t,e);if(t._powerButtons!==undefined){if(t._powerButtons._discover===undefined){t._powerButtons._discover=[]}if(!t._powerButtons._discover.includes(o)){t._powerButtons._discover.push(o)}}}}static execute(e,t,n,i){throw new Error("The execute method must be implemented by the derived class")}}class ActionVerify extends Action{static NAME="Verify";static DEFAULTS={verify:null,form:null,verified:null,notVerified:"The condition for this action is not met",customContentVerified:null,customContentNotVerified:null,titleNotVerified:"The action requires verification",titleVerified:null,buttonAccept:"Accept",buttonClose:false,escapeKey:true,header:true,footer:true};static execute(el,options,onNextAction,onCancelActions){let settings=PowerButtons.getActionSettings(this,options);let result=null;let bindObject=searchForm(settings.form);if(bindObject===null){bindObject=document}try{if(typeof settings.verify==="function"){result=settings.verify.bind(bindObject)()}else if(typeof settings.verify==="string"){result=function(){return eval(settings.verify)}.bind(bindObject)()}else{result=parseBoolean(settings.verify)}}catch(e){console.error("Error executing verification function",e);result=false}let dialog=null;let onVerificationSuccess=onNextAction;let onVerificationFailure=onCancelActions;if(result){if(settings.verified!==null||settings.customContentVerified!==null||settings.titleVerified!==null){dialog=Dialog.create({title:settings.titleVerified,message:settings.verified,customContent:settings.customContentVerified,buttons:[settings.buttonAccept],escapeKeyCancels:settings.escapeKey,close:settings.buttonClose},null,function(e){if(onVerificationSuccess!==null){onVerificationSuccess()}})}}else{if(settings.notVerified!==null||settings.customContentNotVerified!==null||settings.titleNotVerified!==null){dialog=Dialog.create({title:settings.titleNotVerified,message:settings.notVerified,customContent:settings.customContentNotVerified,buttons:[settings.buttonAccept],escapeKeyCancels:settings.escapeKey,close:settings.buttonClose},null,function(e){if(onVerificationFailure!==null){onVerificationFailure()}})}}if(dialog!==null){dialog.show()}else{if(result){if(onVerificationSuccess!==null){onVerificationSuccess()}}else{if(onVerificationFailure!==null){onVerificationFailure()}}}}}ActionVerify.register();class ActionConfirm extends Action{static NAME="Confirm";static DEFAULTS={confirm:"Please confirm this action",customContent:null,title:"The action requires confirmation",buttonConfirm:"Confirm",buttonCancel:"Cancel",buttonClose:true,escapeKey:true};static extractOptions(e,t=null,n=null){let i=super.extractOptions(e,t,n);if(i.confirm.trim()==""){delete i.confirm}return i}static execute(e,t,n,i){let o=PowerButtons.getActionSettings(this,t);let s=Dialog.create({title:o.title,message:o.confirm,customContent:o.customContent,buttons:[o.buttonConfirm,o.buttonCancel],escapeKeyCancels:o.escapeKey,close:o.buttonClose},null,function(e){if(e===0){if(n!==null){n()}}else{if(i!==null){i()}}});s.show()}}ActionConfirm.register();class ActionAsyncTask extends Action{static NAME="AsyncTask";static DEFAULTS={task:null,message:"Please wait...",customContent:null,title:null,buttonCancel:"Cancel",cancel:null,header:true,footer:true};static extractOptions(e,t=null,n=null){return super.extractOptions(e,t,{task:"asynctask"})}static execute(el,options,onNextAction,onCancelActions){let settings=PowerButtons.getActionSettings(this,options);if(settings.task===null){console.error("The task to execute cannot be null");return}let task=null;if(typeof settings.task==="string"){task=async function(){return await eval(settings.task)}}else if(typeof settings.task==="function"){task=settings.task}else{console.error("The task to execute must be either a string or a function");return}let buttons=[];let cancelHandler=null;if(settings.cancel!==null){buttons=[settings.buttonCancel];if(typeof settings.cancel==="string"){cancelHandler=function(){eval(settings.cancel)}}else if(typeof settings.cancel==="function"){cancelHandler=settings.cancel}else{console.error("The cancel handler must be either a string or a function")}}let dialog=Dialog.create({title:settings.title,message:settings.message,customContent:settings.customContent,buttons:buttons,escapeKeyCancels:false,close:false,header:options.header!==undefined?settings.header:settings.title!==null&&settings.title!="",footer:options.footer!==undefined?settings.footer:cancelHandler!==null},function(){cancelHandler();onCancelActions()},function(e){if(onNextAction!==null){onNextAction()}});dialog.show().then(function(){task().finally(function(){dialog.hide()})})}}ActionAsyncTask.register();class ActionShowMessage extends Action{static NAME="ShowMessage";static DEFAULTS={showmessage:"This is a message",customContent:null,title:null,buttonAccept:"Accept",escapeKey:true,buttonClose:true,header:true,footer:true};static execute(e,t,n,i){let o=PowerButtons.getActionSettings(this,t);let s=Dialog.create({title:o.title,message:o.showmessage,customContent:o.customContent,buttons:[o.buttonAccept],escapeKeyCancels:o.escapeKey,close:o.buttonClose,header:t.header!==undefined?o.header:o.title!==null&&o.title!="",footer:t.footer!==undefined?o.footer:o.buttonAccept!==null&&o.buttonAccept!=""},null,function(e){if(n!==null){n()}});s.show()}}ActionShowMessage.register();class ActionFormset extends Action{static NAME="Formset";static DEFAULTS={form:null,fields:{}};static extractOptions(n,i=null,e=null){let t=super.extractOptions(n,i,{form:"formset"});let o={};for(let t in n.dataset){if(t.startsWith(i)){let e=t.substring(i.length);if(e===""){continue}if(e[0]!==e[0].toUpperCase()){continue}e=e.toLocaleLowerCase();o[e]=n.dataset[t]}}if(t.fields===undefined){t.fields={}}Object.assign(t.fields,o);return t}static execute(e,t,n,i){let o=PowerButtons.getActionSettings(this,t);let s=null;let l=[];let r=[];if(o.form==""){if(e.form!==null){s=e.form}else{r=Array.from(document.querySelectorAll("input")).filter(e=>e.form===null)}}else{s=searchForm(o.form);if(s===null){console.error(`Form not found ${o.form}`);return}}if(s!==null){r=Array.from(s.elements)}r.forEach(e=>{if(e.name!==""){l[e.name.toLocaleLowerCase()]=e}if(e.id!==""){l[e.id.toLocaleLowerCase()]=e}});for(let n in o.fields){if(l[n]!==undefined){let t=o.fields[n];let e=getValueWithJavascriptSupport(t,s!==null?s:l);if(typeof e==="function"){try{e=e()}catch(e){console.error(`Error executing ${t}`,e);continue}}l[n].value=e}}n()}}ActionFormset.register();class ActionFormButton extends Action{static NAME="FormButton";static DEFAULTS={formbutton:null,method:"post",formClass:"formbutton",convertCase:"none",formId:null,fields:{}};static extractOptions(n,i=null,e=null){let o=super.extractOptions(n,i,e);let s={};i=i+"Field";for(let t in n.dataset){if(t.startsWith(i)){let e=t.substring(i.length);if(e===""){continue}if(e[0]!==e[0].toUpperCase()){continue}switch(o.convertCase){case"kebab":e=pascalToKebab(e);break;case"snake":e=pascalToSnake(e);break;case"camel":e=pascalToCamel(e);break;case"pascal":break}s[e]=n.dataset[t]}}if(o.fields===undefined){o.fields={}}Object.assign(o.fields,s);return o}static initialize(e,t={}){let n=PowerButtons.getActionSettings(this,t);let i=document.createElement("form");i.method=n.method;if(!isEmpty(n.formbutton)){i.action=n.formbutton}if(n.formId!==null){i.id=n.formId}let o=n.formClass.split(" ");for(var s=0;s0){n.fields=r;n._formObject=i;super.initialize(e,n)}}static execute(e,t,n,i){let o=PowerButtons.getActionSettings(this,t);let s=false;for(let t in o.fields){try{let e=o.fields[t]();o._formObject[t].value=e}catch(e){console.error(`Error obtaining value for field ${t}: ${e}`);s=true}}if(s){i()}else{n()}}}ActionFormButton.register()})(window); +(function(exports){"use strict";if(typeof exports==="undefined"){var exports=window}exports.powerButtons=function(t,n=null,e=null){let i=null;let o=[];let s={};function l(t){for(let e in PowerButtons.actionsRegistered){if(t.toLocaleLowerCase()===e.toLocaleLowerCase()){return e}}return null}if(typeof t==="string"){i=l(t);if(i===null){o=document.querySelectorAll(t);if(o.length===0){console.error(`Parameter ${t} is neither the name of a registered plugin nor a valid selector`);return}if(arguments.length>2){console.warn(`Ignoring extra parameters`)}s=n}else{let t=false;if(typeof n==="string"){o=document.querySelectorAll(n);t=true}else if(n instanceof HTMLElement){o=[n];t=true}else if(n.length!==undefined){t=true;for(let e in n){if(!(n[e]instanceof HTMLElement)){t=false;break}}if(t){o=n}}if(!t){console.error(`Parameter ${n} is neither a valid selector, a list of elements or an HTMLElement`);return}s=e}}else if(t instanceof HTMLElement){o=[t]}else if(t.length!==undefined){for(let e in t){if(!(t[e]instanceof HTMLElement)){console.error(`Parameter ${t} is neither a valid selector, a list of elements or an HTMLElement`);return}}o=t}else{console.error(`Parameter ${t} is neither a valid selector, a list of elements or an HTMLElement`);return}if(s===null){s={}}if(typeof s!=="object"){console.error(`Options parameter must be an object`);return}if(i!==null){let t=PowerButtons.actionsRegistered[i];for(let e of o){t.initialize(e,s)}}else{PowerButtons.discover(o,s)}};exports.powerButtons.version="2.1.2";exports.powerButtons.plugins=function(){return Object.keys(PowerButtons.actionsRegistered)};exports.powerButtons.discoverAll=function(){PowerButtons.discoverAll()};exports.powerButtons.discover=function(e,t){PowerButtons.discover(e,t)};if(document.addEventListener!==undefined){document.addEventListener("DOMContentLoaded",function(e){PowerButtons.discoverAll()})}if(exports.$!==undefined){exports.$.fn.powerButtons=function(e,t={}){exports.powerButtons(e,this,t);return this};exports.$.fn.powerButtons.version=exports.powerButtons.version;exports.$.fn.powerButtons.plugins=exports.powerButtons.plugins}function pascalToSnake(e){return e.replace(/[A-Z]/g,e=>`_${e.toLowerCase()}`).replace(/^_*/,"")}function pascalToKebab(e){return e.replace(/[A-Z]/g,e=>`-${e.toLowerCase()}`).replace(/^-*/,"")}function snakeCaseToCamel(e){return e.replace(/-([a-z])/g,e=>e[1].toUpperCase())}function pascalToCamel(e){return e.charAt(0).toLowerCase()+e.slice(1)}function CamelToCamel(e){return e.charAt(0).toUpperCase()+e.slice(1)}function isElement(e){return e instanceof Element||e instanceof HTMLDocument}function parseBoolean(e){if(typeof e==="boolean"){return e}if(typeof e==="string"){e=e.toLowerCase();if(e==="true"||e==="yes"||e==="1"){return true}return false}return!!e}function createTag(e,t={},n=null){let i=e.split("#");let o=null;if(i.length==1){e=i[0]}else{i[1]=i[1].split(".");o=i[1][0];e=[i[0],...i[1].slice(1)].join(".")}let s=e.split(".");e=s[0];if(e===""){e="div"}if(typeof t==="string"){n=t;t={}}if(n!==null){t.innerHTML=n}if(o!==null){t.id=o}t.className=[t.className,...s.slice(1)].filter(function(e){return`${e}`.trim()!==""}).join(" ");let l=document.createElement(e);for(let e in t){if(l[e]!==undefined){l[e]=t[e]}else{l.setAttribute(e,t[e])}}return l}function appendToElement(e,...t){let n=t.filter(e=>e!==null&&e!==undefined);e.append(...n);return e}function searchForm(e){let t=null;if(e!==null){t=document.forms[e];if(t===undefined){t=document.querySelector(e);if(t===null){console.warn(`form ${e} not found`)}}}if(t!==null){if(t.tagName.toLowerCase()!=="form"){console.warn(`form ${e} is not a form`)}}return t}function getValueWithJavascriptSupport(value,context=null){if(typeof value==="function"){return value.bind(context)}if(typeof value==="string"){let internalValue=value.trim();if(internalValue.startsWith("javascript:")){try{let f=internalValue.substring(11);value=function(){return eval(f)}.bind(context)}catch(e){console.error(`Error executing javascript code ${internalValue.substring(11)}, error: ${e}`);value=null}}}return value}function promiseForEvent(e,t){let n=null;let i=new Promise(e=>{n=e});let o=function(){e.removeEventListener(t,o);n()};e.addEventListener(t,o);return i}function isEmpty(e){if(e===null||e===undefined){return true}if(e instanceof Array){return e.length===0}if(e instanceof Object){return Object.keys(e).length===0}if(typeof e==="string"){return e.trim()===""}return false}class DialogLegacy{DEFAULTS={message:"Main message",buttonCount:2};constructor(e={},t=null,n=null){this.options={...this.DEFAULTS,...e};this.buttonCount=this.options.buttons.length;this.options.buttons=["Accept","Cancel"];this.result=null;this.onButton=t;this.onHidden=n}dispose(){}show(e=null,t=null){if(e!==null){this.onButton=e}if(t!==null){this.onHidden=t}switch(this.buttonCount){case 0:case 1:alert(this.message);this.result=0;break;case 2:this.result=confirm(this.options.message)?0:1;break;default:throw`Unsupported button count ${this.buttonCount}`}if(this.onButton!==null){this.onButton(this.result,{button:this.result,text:this.options.buttons[this.result]},null)}if(this.onHidden!==null){this.onHidden(this.result,{button:this.result,text:this.options.buttons[this.result]},null)}}}class Dialog{static create(e={},t=null,n=null){if(exports.bootstrap===undefined||exports.bootstrap.Modal===undefined){return new DialogLegacy(e,t,n)}if(e.selector!==undefined&&e.selector!==null||e.dialogFunction!==undefined&&e.dialogFunction!==null){throw new Error("not implemented, yet")}return new Dialog(e,t,n)}DEFAULTS={title:"Title",message:"Main message",customContent:null,buttons:["Accept"],buttonClasses:["btn-primary","btn-secondary"],buttonPanelClasses:["text-end"],escapeKeyCancels:true,header:true,footer:true,body:true,close:true};dialog=null;options=null;modal=null;onButton=null;result=null;onHidden=null;onButton=null;constructor(e={},t=null,n=null){if(exports.bootstrap===undefined||exports.bootstrap.Modal===undefined){throw new Error("Bootstrap is required to use this class")}this.options={...this.DEFAULTS,...e};let i=[];for(let t=0;t=0){this.onHidden(this.result,{button:this.result,text:this.options.buttons[this.result]})}else{this.onHidden(this.result,null)}}}dispose(){if(this.modal!==null){this.modal.dispose();this.modal=null}this.dialog.remove();this.dialog=null}show(e=null,t=null){if(this.dialog===null){this.dialog=this._build_dialog(this.options)}if(this.modal===null){this.modal=new bootstrap.Modal(this.dialog,{backdrop:this.options.escapeKeyCancels?true:"static",keyboard:this.options.escapeKeyCancels})}this.dialog.addEventListener("hidden.bs.modal",this._hiddenHandler);this.result=null;if(e!==null){this.onButton=e}if(t!==null){this.onHidden=t}this.modal.show();return promiseForEvent(this.dialog,"shown.bs.modal")}hide(){this.modal.hide();return promiseForEvent(this.dialog,"hidden.bs.modal")}_handleButton(e,t,n){this.result=e;let i=true;if(this.onButton!==null){i=!(this.onButton(e,t,n)===false)}if(i){this.hide()}}_build_dialog(o={}){let e=null;let t=null;if(parseBoolean(o.close)){t=createTag("button.close.btn-close",{type:"button","aria-label":"Close"});t.addEventListener("click",()=>this._handleButton(-1,null,t))}if(parseBoolean(o.header)){e=createTag(".modal-header");if(o.title!==null){e.append(appendToElement(createTag(".modal-title"),appendToElement(createTag("h5",o.title))))}if(parseBoolean(o.close)){e.append(t)}}let s=[];if(o.buttons!==null){for(let i=0;ie.trim()).filter(e=>e!=="").join(".");if(o.footer===false){t+=".mx-1"}if(t!==""){t="."+t}let n=createTag("button.btn"+t+".button"+i,{type:"button"},e.text);n.addEventListener("click",function(){this._handleButton(i,e,n);if(e.handler!==undefined&&e.handler!==null){e.handler(i,e,n)}}.bind(this));s.push(n)}}let n=null;if(parseBoolean(o.footer)){n=appendToElement(createTag(".modal-footer"),...s)}let i=null;if(parseBoolean(o.body)){i=createTag(".modal-body");if(e===null){if(parseBoolean(o.close)){i.append(appendToElement(createTag(".text-end"),t))}}if(o.message!==null){i.append(createTag("p.message.text-center",o.message))}if(o.customContent!==null){i.append(createTag(".custom-content.mx-auto",o.customContent))}if(n===null){let e=o.buttonPanelClasses.map(e=>e.trim()).filter(e=>e!=="").join(".");if(e!==""){e="."+e}appendToElement(i,appendToElement(createTag(".buttons"+e),...s))}}let l=appendToElement(createTag(".modal.fade",{tabindex:"-1",role:"dialog","aria-hidden":"true","data-keyboard":"false"}),appendToElement(createTag(".modal-dialog.modal-dialog-centered",{role:"document"}),appendToElement(createTag(".modal-content"),e,i,n)));return l}}function confirmDialog(e,t="this action needs confirmation",n=null,i=null,o=true){let s=new Dialog({title:t,message:e,buttons:[{text:"Cancel",class:"btn-secondary",handler:i},{text:"Confirm",class:"btn-primary",handler:n}],escapeKeyCancels:o});s.show();return s}function alertDialog(e,t="Alert",n=null){let i=new Dialog({title:t,message:e,buttons:[{text:"Accept",class:"btn-primary",handler:n}],escapeKeyCancels:true});i.show();return i}function loadingDialog(e,t=null,n=null){if(typeof t==="function"){n=t;t=null}let i=new Dialog({title:null,message:e,buttons:[{text:"Cancel",class:"btn-primary"}],customContent:t,escapeKeyCancels:false,close:false,header:false,footer:false},n);i.show();return i}if(exports.powerButtons.utils===undefined){exports.powerButtons.utils={}}Object.assign(exports.powerButtons.utils,{confirmDialog:confirmDialog,alertDialog:alertDialog,loadingDialog:loadingDialog});class PowerButtons{static actionsRegistered={};static registerAction(e){this.actionsRegistered[e.NAME.toLowerCase()]=e;if(exports.powerButtons===undefined){exports.powerButtons={}}if(exports.powerButtons.defaults===undefined){exports.powerButtons.defaults={}}exports.powerButtons.defaults[e.NAME.toLowerCase()]=Object.assign({},e.DEFAULTS)}static getActionSettings(e,t){if(this.actionsRegistered[e.NAME.toLowerCase()]===undefined){console.error(`The action ${e.NAME} is not registered`);return{}}let n={};if(exports.powerButtons!==undefined&&exports.powerButtons.defaults!==undefined&&exports.powerButtons.defaults[e.NAME.toLowerCase()]!==undefined){n=exports.powerButtons.defaults[e.NAME.toLowerCase()]}return Object.assign({},e.DEFAULTS,n,t)}static addAction(e,t={}){let n=PowerButtons.addActionSupport(e);n.appendAction(t)}static addActionSupport(e){if(e._powerButtons===undefined){e._powerButtons=new PowerButtons(e)}else{e._powerButtons.reset()}return e._powerButtons}el=null;current_action=0;actions=[];back_onclick=null;constructor(e){e._powerButtons=this;this.el=e;this.current_action=0;this.actions=[];this.back_onclick=null;if(e.onclick!==undefined&&e.onclick!==null){this.back_onclick=e.onclick;e.onclick=null}e.addEventListener("click",this.handlerClick.bind(this))}appendAction(e={}){if(e.type===undefined){throw"The type of the action is mandatory"}this.actions.push(e)}handlerClick(t){if(this.current_action>=this.actions.length){this.current_action=0;if(typeof this.back_onclick==="function"){if(!this.back_onclick()){t.preventDefault()}}return}let e=this.actions[this.current_action];t.preventDefault();t.stopImmediatePropagation();let n=function(e=false){if(e){this.current_action=this.actions.length}else{this.current_action++}if(this.current_action>=this.actions.length){if(this.el.click!==undefined){this.el.click()}else{this.el.dispatchEvent(new Event(t.type,t))}}else{this.el.dispatchEvent(new Event(t.type,t))}}.bind(this);let i=this.constructor.actionsRegistered[e.type];if(i===undefined){throw`The action ${e.type} is not registered`}i.execute(this.el,e,n,()=>this.reset())}reset(){this.current_action=0}static discoverAll(){Object.entries(this.actionsRegistered).forEach(([e,t])=>{t.discoverAll()})}static discover(n,i={}){Object.entries(this.actionsRegistered).forEach(([e,t])=>{t.discover(n,i)})}}class Action{static NAME=null;static register(){PowerButtons.registerAction(this)}static DEFAULTS={};static extractOptions(n,i=null,o=null){if(i===null){i=this.NAME.toLowerCase()}if(o===null){o={};o[i]=i}let s={};for(let t in this.DEFAULTS){let e=t;if(o[e]!==undefined){e=o[e]}else{e=i+CamelToCamel(e)}if(n.dataset[e]!==undefined){s[t]=n.dataset[e]}}return s}static initialize(e,t={}){PowerButtons.addAction(e,Object.assign({type:this.NAME.toLowerCase()},t))}static discoverAll(){let e=this.NAME.toLowerCase();this.discover(document.querySelectorAll(`[data-${e}]`))}static discover(e,n={},i=true){if(e.length===undefined){e=[e]}let o=this.NAME.toLowerCase();for(let t of e){if(i&&t._powerButtons!==undefined&&t._powerButtons._discover!==undefined&&t._powerButtons._discover.indexOf(o)!==-1){continue}if(t.dataset[o]===undefined){continue}let e=Object.assign(this.extractOptions(t,o),n);this.initialize(t,e);if(t._powerButtons!==undefined){if(t._powerButtons._discover===undefined){t._powerButtons._discover=[]}if(!t._powerButtons._discover.includes(o)){t._powerButtons._discover.push(o)}}}}static execute(e,t,n,i){throw new Error("The execute method must be implemented by the derived class")}}class ActionOverride extends Action{static NAME="Override";static DEFAULTS={override:null,form:null,overridden:null,customContent:null,title:null,buttonAccept:"Accept",escapeKey:true};static execute(el,options,onNextAction,onCancelActions){let settings=PowerButtons.getActionSettings(this,options);let result=null;let bindObject=searchForm(settings.form);if(bindObject===null){bindObject=document}try{if(typeof settings.override==="function"){result=settings.override.bind(bindObject)()}else if(typeof settings.override==="string"){result=function(){return eval(settings.override)}.bind(bindObject)()}else{result=parseBoolean(settings.override)}}catch(e){console.error("Error executing override function",e);result=false}if(result){if(settings.overridden!==null||settings.customContent!==null||settings.title!==null){let dialog=null;dialog=Dialog.create({title:settings.title,message:settings.overridden,customContent:settings.customContent,buttons:[settings.buttonAccept],escapeKeyCancels:settings.escapeKey,close:settings.buttonClose},null,function(e){onNextAction(true)});dialog.show()}else{onNextAction(true)}}else{onNextAction()}}}ActionOverride.register();class ActionVerify extends Action{static NAME="Verify";static DEFAULTS={verify:null,form:null,verified:null,notVerified:"The condition for this action is not met",customContentVerified:null,customContentNotVerified:null,titleNotVerified:"The action requires verification",titleVerified:null,buttonAccept:"Accept",buttonClose:false,escapeKey:true};static execute(el,options,onNextAction,onCancelActions){let settings=PowerButtons.getActionSettings(this,options);let result=null;let bindObject=searchForm(settings.form);if(bindObject===null){bindObject=document}try{if(typeof settings.verify==="function"){result=settings.verify.bind(bindObject)()}else if(typeof settings.verify==="string"){result=function(){return eval(settings.verify)}.bind(bindObject)()}else{result=parseBoolean(settings.verify)}}catch(e){console.error("Error executing verification function",e);result=false}let dialog=null;let onVerificationSuccess=onNextAction;let onVerificationFailure=onCancelActions;if(result){if(settings.verified!==null||settings.customContentVerified!==null||settings.titleVerified!==null){dialog=Dialog.create({title:settings.titleVerified,message:settings.verified,customContent:settings.customContentVerified,buttons:[settings.buttonAccept],escapeKeyCancels:settings.escapeKey,close:settings.buttonClose},null,function(e){if(onVerificationSuccess!==null){onVerificationSuccess()}})}}else{if(settings.notVerified!==null||settings.customContentNotVerified!==null||settings.titleNotVerified!==null){dialog=Dialog.create({title:settings.titleNotVerified,message:settings.notVerified,customContent:settings.customContentNotVerified,buttons:[settings.buttonAccept],escapeKeyCancels:settings.escapeKey,close:settings.buttonClose},null,function(e){if(onVerificationFailure!==null){onVerificationFailure()}})}}if(dialog!==null){dialog.show()}else{if(result){if(onVerificationSuccess!==null){onVerificationSuccess()}}else{if(onVerificationFailure!==null){onVerificationFailure()}}}}}ActionVerify.register();class ActionConfirm extends Action{static NAME="Confirm";static DEFAULTS={confirm:"Please confirm this action",customContent:null,title:"The action requires confirmation",buttonConfirm:"Confirm",buttonCancel:"Cancel",buttonClose:true,escapeKey:true};static extractOptions(e,t=null,n=null){let i=super.extractOptions(e,t,n);if(i.confirm.trim()==""){delete i.confirm}return i}static execute(e,t,n,i){let o=PowerButtons.getActionSettings(this,t);let s=Dialog.create({title:o.title,message:o.confirm,customContent:o.customContent,buttons:[o.buttonConfirm,o.buttonCancel],escapeKeyCancels:o.escapeKey,close:o.buttonClose},null,function(e){if(e===0){if(n!==null){n()}}else{if(i!==null){i()}}});s.show()}}ActionConfirm.register();class ActionAsyncTask extends Action{static NAME="AsyncTask";static DEFAULTS={task:null,message:"Please wait...",customContent:null,title:null,buttonCancel:"Cancel",cancel:null,header:true,footer:true};static extractOptions(e,t=null,n=null){return super.extractOptions(e,t,{task:"asynctask"})}static execute(el,options,onNextAction,onCancelActions){let settings=PowerButtons.getActionSettings(this,options);if(settings.task===null){console.error("The task to execute cannot be null");return}let task=null;if(typeof settings.task==="string"){task=async function(){return await eval(settings.task)}}else if(typeof settings.task==="function"){task=settings.task}else{console.error("The task to execute must be either a string or a function");return}let buttons=[];let cancelHandler=null;if(settings.cancel!==null){buttons=[settings.buttonCancel];if(typeof settings.cancel==="string"){cancelHandler=function(){eval(settings.cancel)}}else if(typeof settings.cancel==="function"){cancelHandler=settings.cancel}else{console.error("The cancel handler must be either a string or a function")}}let dialog=Dialog.create({title:settings.title,message:settings.message,customContent:settings.customContent,buttons:buttons,escapeKeyCancels:false,close:false,header:options.header!==undefined?settings.header:settings.title!==null&&settings.title!="",footer:options.footer!==undefined?settings.footer:cancelHandler!==null},function(){cancelHandler();onCancelActions()},function(e){if(onNextAction!==null){onNextAction()}});dialog.show().then(function(){task().finally(function(){dialog.hide()})})}}ActionAsyncTask.register();class ActionShowMessage extends Action{static NAME="ShowMessage";static DEFAULTS={showmessage:"This is a message",customContent:null,title:null,buttonAccept:"Accept",escapeKey:true,buttonClose:true,header:true,footer:true};static execute(e,t,n,i){let o=PowerButtons.getActionSettings(this,t);let s=Dialog.create({title:o.title,message:o.showmessage,customContent:o.customContent,buttons:[o.buttonAccept],escapeKeyCancels:o.escapeKey,close:o.buttonClose,header:t.header!==undefined?o.header:o.title!==null&&o.title!="",footer:t.footer!==undefined?o.footer:o.buttonAccept!==null&&o.buttonAccept!=""},null,function(e){if(n!==null){n()}});s.show()}}ActionShowMessage.register();class ActionFormset extends Action{static NAME="Formset";static DEFAULTS={form:null,fields:{}};static extractOptions(n,i=null,e=null){let t=super.extractOptions(n,i,{form:"formset"});let o={};for(let t in n.dataset){if(t.startsWith(i)){let e=t.substring(i.length);if(e===""){continue}if(e[0]!==e[0].toUpperCase()){continue}e=e.toLocaleLowerCase();o[e]=n.dataset[t]}}if(t.fields===undefined){t.fields={}}Object.assign(t.fields,o);return t}static execute(e,t,n,i){let o=PowerButtons.getActionSettings(this,t);let s=null;let l=[];let r=[];if(o.form==""){if(e.form!==null){s=e.form}else{r=Array.from(document.querySelectorAll("input")).filter(e=>e.form===null)}}else{s=searchForm(o.form);if(s===null){console.error(`Form not found ${o.form}`);return}}if(s!==null){r=Array.from(s.elements)}r.forEach(e=>{if(e.name!==""){l[e.name.toLocaleLowerCase()]=e}if(e.id!==""){l[e.id.toLocaleLowerCase()]=e}});for(let n in o.fields){if(l[n]!==undefined){let t=o.fields[n];let e=getValueWithJavascriptSupport(t,s!==null?s:l);if(typeof e==="function"){try{e=e()}catch(e){console.error(`Error executing ${t}`,e);continue}}l[n].value=e}}n()}}ActionFormset.register();class ActionFormButton extends Action{static NAME="FormButton";static DEFAULTS={formbutton:null,method:"post",formClass:"formbutton",convertCase:"none",formId:null,fields:{}};static extractOptions(n,i=null,e=null){let o=super.extractOptions(n,i,e);let s={};i=i+"Field";for(let t in n.dataset){if(t.startsWith(i)){let e=t.substring(i.length);if(e===""){continue}if(e[0]!==e[0].toUpperCase()){continue}switch(o.convertCase){case"kebab":e=pascalToKebab(e);break;case"snake":e=pascalToSnake(e);break;case"camel":e=pascalToCamel(e);break;case"pascal":break}s[e]=n.dataset[t]}}if(o.fields===undefined){o.fields={}}Object.assign(o.fields,s);return o}static initialize(e,t={}){let n=PowerButtons.getActionSettings(this,t);let i=document.createElement("form");i.method=n.method;if(!isEmpty(n.formbutton)){i.action=n.formbutton}if(n.formId!==null){i.id=n.formId}let o=n.formClass.split(" ");for(var s=0;s0){n.fields=r;n._formObject=i;super.initialize(e,n)}}static execute(e,t,n,i){let o=PowerButtons.getActionSettings(this,t);let s=false;for(let t in o.fields){try{let e=o.fields[t]();o._formObject[t].value=e}catch(e){console.error(`Error obtaining value for field ${t}: ${e}`);s=true}}if(s){i()}else{n()}}}ActionFormButton.register()})(window); diff --git a/dist/powerbuttons.module.js b/dist/powerbuttons.module.js new file mode 100644 index 0000000..0fd23d5 --- /dev/null +++ b/dist/powerbuttons.module.js @@ -0,0 +1,1235 @@ +/** + Copyright 2023 Carlos A. (https://github.com/dealfonso) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +if (typeof imports === "undefined") { var imports = {}; } +(function (exports) { + "use strict"; + if (typeof exports === "undefined") { + var exports = window + } + exports.powerButtons = function (param1, param2 = null, param3 = null) { + let pluginName = null; + let els = []; + let options = {}; + + function registeredPlugin(pluginName) { + for (let actionName in PowerButtons.actionsRegistered) { + if (pluginName.toLocaleLowerCase() === actionName.toLocaleLowerCase()) { + return actionName + } + } + return null + } + if (typeof param1 === "string") { + pluginName = registeredPlugin(param1); + if (pluginName === null) { + els = document.querySelectorAll(param1); + if (els.length === 0) { + console.error(`Parameter ${param1} is neither the name of a registered plugin nor a valid selector`); + return + } + if (arguments.length > 2) { + console.warn(`Ignoring extra parameters`) + } + options = param2 + } else { + let valid = false; + if (typeof param2 === "string") { + els = document.querySelectorAll(param2); + valid = true + } else if (param2 instanceof HTMLElement) { + els = [param2]; + valid = true + } else if (param2.length !== undefined) { + valid = true; + for (let e in param2) { + if (!(param2[e] instanceof HTMLElement)) { + valid = false; + break + } + } + if (valid) { + els = param2 + } + } + if (!valid) { + console.error(`Parameter ${param2} is neither a valid selector, a list of elements or an HTMLElement`); + return + } + options = param3 + } + } else if (param1 instanceof HTMLElement) { + els = [param1] + } else if (param1.length !== undefined) { + for (let e in param1) { + if (!(param1[e] instanceof HTMLElement)) { + console.error(`Parameter ${param1} is neither a valid selector, a list of elements or an HTMLElement`); + return + } + } + els = param1 + } else { + console.error(`Parameter ${param1} is neither a valid selector, a list of elements or an HTMLElement`); + return + } + if (options === null) { + options = {} + } + if (typeof options !== "object") { + console.error(`Options parameter must be an object`); + return + } + if (pluginName !== null) { + let plugin = PowerButtons.actionsRegistered[pluginName]; + for (let el of els) { + plugin.initialize(el, options) + } + } else { + PowerButtons.discover(els, options) + } + }; + exports.powerButtons.version = "2.1.2"; + exports.powerButtons.plugins = function () { + return Object.keys(PowerButtons.actionsRegistered) + }; + exports.powerButtons.discoverAll = function () { + PowerButtons.discoverAll() + }; + exports.powerButtons.discover = function (els, options) { + PowerButtons.discover(els, options) + }; + if (document.addEventListener !== undefined) { + document.addEventListener("DOMContentLoaded", function (e) { + PowerButtons.discoverAll() + }) + } + if (exports.$ !== undefined) { + exports.$.fn.powerButtons = function (pluginName, options = {}) { + exports.powerButtons(pluginName, this, options); + return this + }; + exports.$.fn.powerButtons.version = exports.powerButtons.version; + exports.$.fn.powerButtons.plugins = exports.powerButtons.plugins + } + + function pascalToSnake(str) { + return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`).replace(/^_*/, "") + } + + function pascalToKebab(str) { + return str.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`).replace(/^-*/, "") + } + + function snakeCaseToCamel(str) { + return str.replace(/-([a-z])/g, g => g[1].toUpperCase()) + } + + function pascalToCamel(str) { + return str.charAt(0).toLowerCase() + str.slice(1) + } + + function CamelToCamel(str) { + return str.charAt(0).toUpperCase() + str.slice(1) + } + + function isElement(el) { + return el instanceof Element || el instanceof HTMLDocument + } + + function parseBoolean(value) { + if (typeof value === "boolean") { + return value + } + if (typeof value === "string") { + value = value.toLowerCase(); + if (value === "true" || value === "yes" || value === "1") { + return true + } + return false + } + return !!value + } + + function createTag(tag, props = {}, html = null) { + let parts_id = tag.split("#"); + let id = null; + if (parts_id.length == 1) { + tag = parts_id[0] + } else { + parts_id[1] = parts_id[1].split("."); + id = parts_id[1][0]; + tag = [parts_id[0], ...parts_id[1].slice(1)].join(".") + } + let parts = tag.split("."); + tag = parts[0]; + if (tag === "") { + tag = "div" + } + if (typeof props === "string") { + html = props; + props = {} + } + if (html !== null) { + props.innerHTML = html + } + if (id !== null) { + props.id = id + } + props.className = [props.className, ...parts.slice(1)].filter(function (e) { + return `${e}`.trim() !== "" + }).join(" "); + let el = document.createElement(tag); + for (let prop in props) { + if (el[prop] !== undefined) { + el[prop] = props[prop] + } else { + el.setAttribute(prop, props[prop]) + } + } + return el + } + + function appendToElement(el, ...args) { + let filtered = args.filter(e => e !== null && e !== undefined); + el.append(...filtered); + return el + } + + function searchForm(formName) { + let formObject = null; + if (formName !== null) { + formObject = document.forms[formName]; + if (formObject === undefined) { + formObject = document.querySelector(formName); + if (formObject === null) { + console.warn(`form ${formName} not found`) + } + } + } + if (formObject !== null) { + if (formObject.tagName.toLowerCase() !== "form") { + console.warn(`form ${formName} is not a form`) + } + } + return formObject + } + + function getValueWithJavascriptSupport(value, context = null) { + if (typeof value === "function") { + return value.bind(context) + } + if (typeof value === "string") { + let internalValue = value.trim(); + if (internalValue.startsWith("javascript:")) { + try { + let f = internalValue.substring(11); + value = function () { + return eval(f) + }.bind(context) + } catch (e) { + console.error(`Error executing javascript code ${internalValue.substring(11)}, error: ${e}`); + value = null + } + } + } + return value + } + + function promiseForEvent(el, event) { + let resolveFunction = null; + let promise = new Promise(resolve => { + resolveFunction = resolve + }); + let handler = function () { + el.removeEventListener(event, handler); + resolveFunction() + }; + el.addEventListener(event, handler); + return promise + } + + function isEmpty(obj) { + if (obj === null || obj === undefined) { + return true + } + if (obj instanceof Array) { + return obj.length === 0 + } + if (obj instanceof Object) { + return Object.keys(obj).length === 0 + } + if (typeof obj === "string") { + return obj.trim() === "" + } + return false + } + class DialogLegacy { + DEFAULTS = { + message: "Main message", + buttonCount: 2 + }; + constructor(options = {}, onButton = null, onHidden = null) { + this.options = { + ...this.DEFAULTS, + ...options + }; + this.buttonCount = this.options.buttons.length; + this.options.buttons = ["Accept", "Cancel"]; + this.result = null; + this.onButton = onButton; + this.onHidden = onHidden + } + dispose() {} + show(onButton = null, onHidden = null) { + if (onButton !== null) { + this.onButton = onButton + } + if (onHidden !== null) { + this.onHidden = onHidden + } + switch (this.buttonCount) { + case 0: + case 1: + alert(this.message); + this.result = 0; + break; + case 2: + this.result = confirm(this.options.message) ? 0 : 1; + break; + default: + throw `Unsupported button count ${this.buttonCount}` + } + if (this.onButton !== null) { + this.onButton(this.result, { + button: this.result, + text: this.options.buttons[this.result] + }, null) + } + if (this.onHidden !== null) { + this.onHidden(this.result, { + button: this.result, + text: this.options.buttons[this.result] + }, null) + } + } + } + class Dialog { + static create(options = {}, onButton = null, onHidden = null) { + if (exports.bootstrap === undefined || exports.bootstrap.Modal === undefined) { + return new DialogLegacy(options, onButton, onHidden) + } + if (options.selector !== undefined && options.selector !== null || options.dialogFunction !== undefined && options.dialogFunction !== null) { + throw new Error("not implemented, yet") + } + return new Dialog(options, onButton, onHidden) + } + DEFAULTS = { + title: "Title", + message: "Main message", + customContent: null, + buttons: ["Accept"], + buttonClasses: ["btn-primary", "btn-secondary"], + buttonPanelClasses: ["text-end"], + escapeKeyCancels: true, + header: true, + footer: true, + body: true, + close: true + }; + dialog = null; + options = null; + modal = null; + onButton = null; + result = null; + onHidden = null; + onButton = null; + constructor(options = {}, onButton = null, onHidden = null) { + if (exports.bootstrap === undefined || exports.bootstrap.Modal === undefined) { + throw new Error("Bootstrap is required to use this class") + } + this.options = { + ...this.DEFAULTS, + ...options + }; + let parsedButtons = []; + for (let i = 0; i < this.options.buttons.length; i++) { + let button = this.options.buttons[i]; + if (typeof button === "string") { + button = { + text: button + } + } else { + if (button.text === undefined) { + button.text = `Button ${i}` + } + } + if (button.class === undefined) { + button.class = this.options.buttonClasses[Math.min(i, this.options.buttonClasses.length - 1)] + } + parsedButtons.push(button) + } + this.options.buttons = parsedButtons; + this.dialog = null; + this.modal = null; + this.result = null; + this.onButton = onButton; + this.onHidden = onHidden; + this._hiddenHandler = this._hiddenHandler.bind(this) + } + _hiddenHandler() { + this.dialog.removeEventListener("hidden.bs.modal", this._hiddenHandler); + if (this.onHidden !== null) { + if (this.result !== null && this.result >= 0) { + this.onHidden(this.result, { + button: this.result, + text: this.options.buttons[this.result] + }) + } else { + this.onHidden(this.result, null) + } + } + } + dispose() { + if (this.modal !== null) { + this.modal.dispose(); + this.modal = null + } + this.dialog.remove(); + this.dialog = null + } + show(onButton = null, onHidden = null) { + if (this.dialog === null) { + this.dialog = this._build_dialog(this.options) + } + if (this.modal === null) { + this.modal = new bootstrap.Modal(this.dialog, { + backdrop: this.options.escapeKeyCancels ? true : "static", + keyboard: this.options.escapeKeyCancels + }) + } + this.dialog.addEventListener("hidden.bs.modal", this._hiddenHandler); + this.result = null; + if (onButton !== null) { + this.onButton = onButton + } + if (onHidden !== null) { + this.onHidden = onHidden + } + this.modal.show(); + return promiseForEvent(this.dialog, "shown.bs.modal") + } + hide() { + this.modal.hide(); + return promiseForEvent(this.dialog, "hidden.bs.modal") + } + _handleButton(index, button, buttonObject) { + this.result = index; + let autoHide = true; + if (this.onButton !== null) { + autoHide = !(this.onButton(index, button, buttonObject) === false) + } + if (autoHide) { + this.hide() + } + } + _build_dialog(options = {}) { + let header = null; + let closeButton = null; + if (parseBoolean(options.close)) { + closeButton = createTag("button.close.btn-close", { + type: "button", + "aria-label": "Close" + }); + closeButton.addEventListener("click", () => this._handleButton(-1, null, closeButton)) + } + if (parseBoolean(options.header)) { + header = createTag(".modal-header"); + if (options.title !== null) { + header.append(appendToElement(createTag(".modal-title"), appendToElement(createTag("h5", options.title)))) + } + if (parseBoolean(options.close)) { + header.append(closeButton) + } + } + let buttons = []; + if (options.buttons !== null) { + for (let i = 0; i < options.buttons.length; i++) { + let button = options.buttons[i]; + let buttonClass = button.class.split(" ").map(e => e.trim()).filter(e => e !== "").join("."); + if (options.footer === false) { + buttonClass += ".mx-1" + } + if (buttonClass !== "") { + buttonClass = "." + buttonClass + } + let buttonObject = createTag("button.btn" + buttonClass + ".button" + i, { + type: "button" + }, button.text); + buttonObject.addEventListener("click", function () { + this._handleButton(i, button, buttonObject); + if (button.handler !== undefined && button.handler !== null) { + button.handler(i, button, buttonObject) + } + }.bind(this)); + buttons.push(buttonObject) + } + } + let footer = null; + if (parseBoolean(options.footer)) { + footer = appendToElement(createTag(".modal-footer"), ...buttons) + } + let body = null; + if (parseBoolean(options.body)) { + body = createTag(".modal-body"); + if (header === null) { + if (parseBoolean(options.close)) { + body.append(appendToElement(createTag(".text-end"), closeButton)) + } + } + if (options.message !== null) { + body.append(createTag("p.message.text-center", options.message)) + } + if (options.customContent !== null) { + body.append(createTag(".custom-content.mx-auto", options.customContent)) + } + if (footer === null) { + let buttonPanelClasses = options.buttonPanelClasses.map(e => e.trim()).filter(e => e !== "").join("."); + if (buttonPanelClasses !== "") { + buttonPanelClasses = "." + buttonPanelClasses + } + appendToElement(body, appendToElement(createTag(".buttons" + buttonPanelClasses), ...buttons)) + } + } + let dialog = appendToElement(createTag(".modal.fade", { + tabindex: "-1", + role: "dialog", + "aria-hidden": "true", + "data-keyboard": "false" + }), appendToElement(createTag(".modal-dialog.modal-dialog-centered", { + role: "document" + }), appendToElement(createTag(".modal-content"), header, body, footer))); + return dialog + } + } + + function confirmDialog(message, title = "this action needs confirmation", onConfirm = null, onCancel = null, cancellable = true) { + let dialog = new Dialog({ + title: title, + message: message, + buttons: [{ + text: "Cancel", + class: "btn-secondary", + handler: onCancel + }, { + text: "Confirm", + class: "btn-primary", + handler: onConfirm + }], + escapeKeyCancels: cancellable + }); + dialog.show(); + return dialog + } + + function alertDialog(message, title = "Alert", onAccept = null) { + let dialog = new Dialog({ + title: title, + message: message, + buttons: [{ + text: "Accept", + class: "btn-primary", + handler: onAccept + }], + escapeKeyCancels: true + }); + dialog.show(); + return dialog + } + + function loadingDialog(message, customContent = null, canCancel = null) { + if (typeof customContent === "function") { + canCancel = customContent; + customContent = null + } + let dialog = new Dialog({ + title: null, + message: message, + buttons: [{ + text: "Cancel", + class: "btn-primary" + }], + customContent: customContent, + escapeKeyCancels: false, + close: false, + header: false, + footer: false + }, canCancel); + dialog.show(); + return dialog + } + if (exports.powerButtons.utils === undefined) { + exports.powerButtons.utils = {} + } + Object.assign(exports.powerButtons.utils, { + confirmDialog: confirmDialog, + alertDialog: alertDialog, + loadingDialog: loadingDialog + }); + class PowerButtons { + static actionsRegistered = {}; + static registerAction(action) { + this.actionsRegistered[action.NAME.toLowerCase()] = action; + if (exports.powerButtons === undefined) { + exports.powerButtons = {} + } + if (exports.powerButtons.defaults === undefined) { + exports.powerButtons.defaults = {} + } + exports.powerButtons.defaults[action.NAME.toLowerCase()] = Object.assign({}, action.DEFAULTS) + } + static getActionSettings(action, options) { + if (this.actionsRegistered[action.NAME.toLowerCase()] === undefined) { + console.error(`The action ${action.NAME} is not registered`); + return {} + } + let defaultsWindow = {}; + if (exports.powerButtons !== undefined && exports.powerButtons.defaults !== undefined && exports.powerButtons.defaults[action.NAME.toLowerCase()] !== undefined) { + defaultsWindow = exports.powerButtons.defaults[action.NAME.toLowerCase()] + } + return Object.assign({}, action.DEFAULTS, defaultsWindow, options) + } + static addAction(el, options = {}) { + let powerButton = PowerButtons.addActionSupport(el); + powerButton.appendAction(options) + } + static addActionSupport(el) { + if (el._powerButtons === undefined) { + el._powerButtons = new PowerButtons(el) + } else { + el._powerButtons.reset() + } + return el._powerButtons + } + el = null; + current_action = 0; + actions = []; + back_onclick = null; + constructor(el) { + el._powerButtons = this; + this.el = el; + this.current_action = 0; + this.actions = []; + this.back_onclick = null; + if (el.onclick !== undefined && el.onclick !== null) { + this.back_onclick = el.onclick; + el.onclick = null + } + el.addEventListener("click", this.handlerClick.bind(this)) + } + appendAction(options = {}) { + if (options.type === undefined) { + throw "The type of the action is mandatory" + } + this.actions.push(options) + } + handlerClick(e) { + if (this.current_action >= this.actions.length) { + this.current_action = 0; + if (typeof this.back_onclick === "function") { + if (!this.back_onclick()) { + e.preventDefault() + } + } + return + } + let currentActionSettings = this.actions[this.current_action]; + e.preventDefault(); + e.stopImmediatePropagation(); + let onNextAction = function (override = false) { + if (override) { + this.current_action = this.actions.length + } else { + this.current_action++ + } + if (this.current_action >= this.actions.length) { + if (this.el.click !== undefined) { + this.el.click() + } else { + this.el.dispatchEvent(new Event(e.type, e)) + } + } else { + this.el.dispatchEvent(new Event(e.type, e)) + } + }.bind(this); + let action = this.constructor.actionsRegistered[currentActionSettings.type]; + if (action === undefined) { + throw `The action ${currentActionSettings.type} is not registered` + } + action.execute(this.el, currentActionSettings, onNextAction, () => this.reset()) + } + reset() { + this.current_action = 0 + } + static discoverAll() { + Object.entries(this.actionsRegistered).forEach(([key, action]) => { + action.discoverAll() + }) + } + static discover(els, options = {}) { + Object.entries(this.actionsRegistered).forEach(([key, action]) => { + action.discover(els, options) + }) + } + } + class Action { + static NAME = null; + static register() { + PowerButtons.registerAction(this) + } + static DEFAULTS = {}; + static extractOptions(el, prefix = null, map = null) { + if (prefix === null) { + prefix = this.NAME.toLowerCase() + } + if (map === null) { + map = {}; + map[prefix] = prefix + } + let options = {}; + for (let key in this.DEFAULTS) { + let targetKey = key; + if (map[targetKey] !== undefined) { + targetKey = map[targetKey] + } else { + targetKey = prefix + CamelToCamel(targetKey) + } + if (el.dataset[targetKey] !== undefined) { + options[key] = el.dataset[targetKey] + } + } + return options + } + static initialize(el, values = {}) { + PowerButtons.addAction(el, Object.assign({ + type: this.NAME.toLowerCase() + }, values)) + } + static discoverAll() { + let prefix = this.NAME.toLowerCase(); + this.discover(document.querySelectorAll(`[data-${prefix}]`)) + } + static discover(els, options = {}, skipInitialized = true) { + if (els.length === undefined) { + els = [els] + } + let prefix = this.NAME.toLowerCase(); + for (let el of els) { + if (skipInitialized && el._powerButtons !== undefined && el._powerButtons._discover !== undefined && el._powerButtons._discover.indexOf(prefix) !== -1) { + continue + } + if (el.dataset[prefix] === undefined) { + continue + } + let currentOptions = Object.assign(this.extractOptions(el, prefix), options); + this.initialize(el, currentOptions); + if (el._powerButtons !== undefined) { + if (el._powerButtons._discover === undefined) { + el._powerButtons._discover = [] + } + if (!el._powerButtons._discover.includes(prefix)) { + el._powerButtons._discover.push(prefix) + } + } + } + } + static execute(el, options, onNextAction, onCancelActions) { + throw new Error("The execute method must be implemented by the derived class") + } + } + class ActionOverride extends Action { + static NAME = "Override"; + static DEFAULTS = { + override: null, + form: null, + overridden: null, + customContent: null, + title: null, + buttonAccept: "Accept", + escapeKey: true + }; + static execute(el, options, onNextAction, onCancelActions) { + let settings = PowerButtons.getActionSettings(this, options); + let result = null; + let bindObject = searchForm(settings.form); + if (bindObject === null) { + bindObject = document + } + try { + if (typeof settings.override === "function") { + result = settings.override.bind(bindObject)() + } else if (typeof settings.override === "string") { + result = function () { + return eval(settings.override) + }.bind(bindObject)() + } else { + result = parseBoolean(settings.override) + } + } catch (e) { + console.error("Error executing override function", e); + result = false + } + if (result) { + if (settings.overridden !== null || settings.customContent !== null || settings.title !== null) { + let dialog = null; + dialog = Dialog.create({ + title: settings.title, + message: settings.overridden, + customContent: settings.customContent, + buttons: [settings.buttonAccept], + escapeKeyCancels: settings.escapeKey, + close: settings.buttonClose + }, null, function (result) { + onNextAction(true) + }); + dialog.show() + } else { + onNextAction(true) + } + } else { + onNextAction() + } + } + } + ActionOverride.register(); + class ActionVerify extends Action { + static NAME = "Verify"; + static DEFAULTS = { + verify: null, + form: null, + verified: null, + notVerified: "The condition for this action is not met", + customContentVerified: null, + customContentNotVerified: null, + titleNotVerified: "The action requires verification", + titleVerified: null, + buttonAccept: "Accept", + buttonClose: false, + escapeKey: true + }; + static execute(el, options, onNextAction, onCancelActions) { + let settings = PowerButtons.getActionSettings(this, options); + let result = null; + let bindObject = searchForm(settings.form); + if (bindObject === null) { + bindObject = document + } + try { + if (typeof settings.verify === "function") { + result = settings.verify.bind(bindObject)() + } else if (typeof settings.verify === "string") { + result = function () { + return eval(settings.verify) + }.bind(bindObject)() + } else { + result = parseBoolean(settings.verify) + } + } catch (e) { + console.error("Error executing verification function", e); + result = false + } + let dialog = null; + let onVerificationSuccess = onNextAction; + let onVerificationFailure = onCancelActions; + if (result) { + if (settings.verified !== null || settings.customContentVerified !== null || settings.titleVerified !== null) { + dialog = Dialog.create({ + title: settings.titleVerified, + message: settings.verified, + customContent: settings.customContentVerified, + buttons: [settings.buttonAccept], + escapeKeyCancels: settings.escapeKey, + close: settings.buttonClose + }, null, function (result) { + if (onVerificationSuccess !== null) { + onVerificationSuccess() + } + }) + } + } else { + if (settings.notVerified !== null || settings.customContentNotVerified !== null || settings.titleNotVerified !== null) { + dialog = Dialog.create({ + title: settings.titleNotVerified, + message: settings.notVerified, + customContent: settings.customContentNotVerified, + buttons: [settings.buttonAccept], + escapeKeyCancels: settings.escapeKey, + close: settings.buttonClose + }, null, function (result) { + if (onVerificationFailure !== null) { + onVerificationFailure() + } + }) + } + } + if (dialog !== null) { + dialog.show() + } else { + if (result) { + if (onVerificationSuccess !== null) { + onVerificationSuccess() + } + } else { + if (onVerificationFailure !== null) { + onVerificationFailure() + } + } + } + } + } + ActionVerify.register(); + class ActionConfirm extends Action { + static NAME = "Confirm"; + static DEFAULTS = { + confirm: "Please confirm this action", + customContent: null, + title: "The action requires confirmation", + buttonConfirm: "Confirm", + buttonCancel: "Cancel", + buttonClose: true, + escapeKey: true + }; + static extractOptions(el, prefix = null, map = null) { + let options = super.extractOptions(el, prefix, map); + if (options.confirm.trim() == "") { + delete options.confirm + } + return options + } + static execute(el, options, onNextAction, onCancelActions) { + let settings = PowerButtons.getActionSettings(this, options); + let dialog = Dialog.create({ + title: settings.title, + message: settings.confirm, + customContent: settings.customContent, + buttons: [settings.buttonConfirm, settings.buttonCancel], + escapeKeyCancels: settings.escapeKey, + close: settings.buttonClose + }, null, function (result) { + if (result === 0) { + if (onNextAction !== null) { + onNextAction() + } + } else { + if (onCancelActions !== null) { + onCancelActions() + } + } + }); + dialog.show() + } + } + ActionConfirm.register(); + class ActionAsyncTask extends Action { + static NAME = "AsyncTask"; + static DEFAULTS = { + task: null, + message: "Please wait...", + customContent: null, + title: null, + buttonCancel: "Cancel", + cancel: null, + header: true, + footer: true + }; + static extractOptions(el, prefix = null, map = null) { + return super.extractOptions(el, prefix, { + task: "asynctask" + }) + } + static execute(el, options, onNextAction, onCancelActions) { + let settings = PowerButtons.getActionSettings(this, options); + if (settings.task === null) { + console.error("The task to execute cannot be null"); + return + } + let task = null; + if (typeof settings.task === "string") { + task = async function () { + return await eval(settings.task) + } + } else if (typeof settings.task === "function") { + task = settings.task + } else { + console.error("The task to execute must be either a string or a function"); + return + } + let buttons = []; + let cancelHandler = null; + if (settings.cancel !== null) { + buttons = [settings.buttonCancel]; + if (typeof settings.cancel === "string") { + cancelHandler = function () { + eval(settings.cancel) + } + } else if (typeof settings.cancel === "function") { + cancelHandler = settings.cancel + } else { + console.error("The cancel handler must be either a string or a function") + } + } + let dialog = Dialog.create({ + title: settings.title, + message: settings.message, + customContent: settings.customContent, + buttons: buttons, + escapeKeyCancels: false, + close: false, + header: options.header !== undefined ? settings.header : settings.title !== null && settings.title != "", + footer: options.footer !== undefined ? settings.footer : cancelHandler !== null + }, function () { + cancelHandler(); + onCancelActions() + }, function (result) { + if (onNextAction !== null) { + onNextAction() + } + }); + dialog.show().then(function () { + task().finally(function () { + dialog.hide() + }) + }) + } + } + ActionAsyncTask.register(); + class ActionShowMessage extends Action { + static NAME = "ShowMessage"; + static DEFAULTS = { + showmessage: "This is a message", + customContent: null, + title: null, + buttonAccept: "Accept", + escapeKey: true, + buttonClose: true, + header: true, + footer: true + }; + static execute(el, options, onNextAction, onCancelActions) { + let settings = PowerButtons.getActionSettings(this, options); + let dialog = Dialog.create({ + title: settings.title, + message: settings.showmessage, + customContent: settings.customContent, + buttons: [settings.buttonAccept], + escapeKeyCancels: settings.escapeKey, + close: settings.buttonClose, + header: options.header !== undefined ? settings.header : settings.title !== null && settings.title != "", + footer: options.footer !== undefined ? settings.footer : settings.buttonAccept !== null && settings.buttonAccept != "" + }, null, function (result) { + if (onNextAction !== null) { + onNextAction() + } + }); + dialog.show() + } + } + ActionShowMessage.register(); + class ActionFormset extends Action { + static NAME = "Formset"; + static DEFAULTS = { + form: null, + fields: {} + }; + static extractOptions(el, prefix = null, map = null) { + let options = super.extractOptions(el, prefix, { + form: "formset" + }); + let fields = {}; + for (let key in el.dataset) { + if (key.startsWith(prefix)) { + let fieldname = key.substring(prefix.length); + if (fieldname === "") { + continue + } + if (fieldname[0] !== fieldname[0].toUpperCase()) { + continue + } + fieldname = fieldname.toLocaleLowerCase(); + fields[fieldname] = el.dataset[key] + } + } + if (options.fields === undefined) { + options.fields = {} + } + Object.assign(options.fields, fields); + return options + } + static execute(el, options, onNextAction, onCancelActions) { + let settings = PowerButtons.getActionSettings(this, options); + let formToSet = null; + let inputFields = []; + let elements = []; + if (settings.form == "") { + if (el.form !== null) { + formToSet = el.form + } else { + elements = Array.from(document.querySelectorAll("input")).filter(input => input.form === null) + } + } else { + formToSet = searchForm(settings.form); + if (formToSet === null) { + console.error(`Form not found ${settings.form}`); + return + } + } + if (formToSet !== null) { + elements = Array.from(formToSet.elements) + } + elements.forEach(element => { + if (element.name !== "") { + inputFields[element.name.toLocaleLowerCase()] = element + } + if (element.id !== "") { + inputFields[element.id.toLocaleLowerCase()] = element + } + }); + for (let field in settings.fields) { + if (inputFields[field] !== undefined) { + let value = settings.fields[field]; + let result = getValueWithJavascriptSupport(value, formToSet !== null ? formToSet : inputFields); + if (typeof result === "function") { + try { + result = result() + } catch (e) { + console.error(`Error executing ${value}`, e); + continue + } + } + inputFields[field].value = result + } + } + onNextAction() + } + } + ActionFormset.register(); + class ActionFormButton extends Action { + static NAME = "FormButton"; + static DEFAULTS = { + formbutton: null, + method: "post", + formClass: "formbutton", + convertCase: "none", + formId: null, + fields: {} + }; + static extractOptions(el, prefix = null, map = null) { + let options = super.extractOptions(el, prefix, map); + let fields = {}; + prefix = prefix + "Field"; + for (let key in el.dataset) { + if (key.startsWith(prefix)) { + let fieldname = key.substring(prefix.length); + if (fieldname === "") { + continue + } + if (fieldname[0] !== fieldname[0].toUpperCase()) { + continue + } + switch (options.convertCase) { + case "kebab": + fieldname = pascalToKebab(fieldname); + break; + case "snake": + fieldname = pascalToSnake(fieldname); + break; + case "camel": + fieldname = pascalToCamel(fieldname); + break; + case "pascal": + break + } + fields[fieldname] = el.dataset[key] + } + } + if (options.fields === undefined) { + options.fields = {} + } + Object.assign(options.fields, fields); + return options + } + static initialize(el, values = {}) { + let settings = PowerButtons.getActionSettings(this, values); + let form = document.createElement("form"); + form.method = settings.method; + if (!isEmpty(settings.formbutton)) { + form.action = settings.formbutton + } + if (settings.formId !== null) { + form.id = settings.formId + } + let cssClasses = settings.formClass.split(" "); + for (var i = 0; i < cssClasses.length; i++) { + if (!isEmpty(cssClasses[i])) { + form.classList.add(cssClasses[i]) + } + } + el.type = "submit"; + let fields = {}; + for (let fieldName in settings.fields) { + fields[fieldName] = getValueWithJavascriptSupport(settings.fields[fieldName], form) + } + let pendingFields = {}; + for (let fieldName in fields) { + let input = document.createElement("input"); + input.type = "hidden"; + input.name = fieldName; + if (typeof fields[fieldName] === "function") { + input.value = ""; + pendingFields[fieldName] = fields[fieldName] + } else { + input.value = fields[fieldName] + } + form.appendChild(input) + } + el.parentNode.replaceChild(form, el); + form.appendChild(el); + if (Object.keys(pendingFields).length > 0) { + settings.fields = pendingFields; + settings._formObject = form; + super.initialize(el, settings) + } + } + static execute(el, options, onNextAction, onCancelActions) { + let settings = PowerButtons.getActionSettings(this, options); + let error = false; + for (let fieldName in settings.fields) { + try { + let value = settings.fields[fieldName](); + settings._formObject[fieldName].value = value + } catch (e) { + console.error(`Error obtaining value for field ${fieldName}: ${e}`); + error = true + } + } + if (error) { + onCancelActions() + } else { + onNextAction() + } + } + } + ActionFormButton.register() +})(imports); diff --git a/dist/powerbuttons.raw.js b/dist/powerbuttons.raw.js index 3a888ef..3594879 100644 --- a/dist/powerbuttons.raw.js +++ b/dist/powerbuttons.raw.js @@ -104,7 +104,7 @@ exports.powerButtons = function(param1, param2 = null, param3 = null) { } }; -exports.powerButtons.version = '2.1.0'; +exports.powerButtons.version = '2.1.2'; exports.powerButtons.plugins = function() { return Object.keys(PowerButtons.actionsRegistered); } @@ -863,10 +863,15 @@ Object.assign(exports.powerButtons.utils, { /** * This is a handler for the accept button of the dialog (or the confirmation button), to execute the next action by simulating a click event + * @param {*} override, if true, it will override any other action and will assume that the callee was the last one */ - let onNextAction = function() { + let onNextAction = function(override = false) { // Continue with the action, by simulating the common click action - this.current_action++; + if (override) { + this.current_action = this.actions.length; + } else { + this.current_action++; + } // If it is the last confirmation, we'll execute the legacy click event (if exists; otherwise we'll dispatch an event to fire jquery events (click method already fires them)) if (this.current_action >= this.actions.length) { @@ -1053,7 +1058,76 @@ Object.assign(exports.powerButtons.utils, { throw new Error("The execute method must be implemented by the derived class"); } } -class ActionVerify extends Action { +class ActionOverride extends Action { + static NAME = "Override"; + + static DEFAULTS = { + // The function to call to check for overriding the next actions. It must return a true or false value. If it is an string, it will be evaluated as javascript, using _eval_ + override: null, + // The form to bind the verification to. If it is a string, it will be interpreted as a selector (it is not verified if it is a form or any other object). If null, the verification will be bound to the document + form: null, + // The content of the message to show to the user if `override` evaluates to true (it can be either plain text or a HTML fragment) + overridden: null, + // A custom content to show to the user under the message when `override` evaluates to true (it can be either plain text or a HTML fragment) + customContent: null, + // The content of the title of the dialog when `override` evaluates to true (it can be either plain text or a HTML fragment) + title: null, + // The content for the button that confirms the action (it can be either plain text or a HTML fragment) + buttonAccept: "Accept", + // If falshi (i.e. null, 0, false, "false"), the esc key will not close the dialog (it will close it if true) + escapeKey: true, + } + + static execute(el, options, onNextAction, onCancelActions) { + // We merge the options with the defaults to get a valid settings object + let settings = PowerButtons.getActionSettings(this, options); + + let result = null; + let bindObject = searchForm(settings.form); + if (bindObject === null) { + bindObject = document; + } + try { + if (typeof(settings.override) === 'function') { + result = settings.override.bind(bindObject)(); + } else if (typeof(settings.override) === 'string') { + result = function() { + return eval(settings.override) + }.bind(bindObject)(); + } else { + result = parseBoolean(settings.override); + } + } catch (e) { + console.error("Error executing override function", e); + result = false; + } + + if (result) { + if ((settings.overridden !== null) || (settings.customContent !== null) || (settings.title !== null)) { + let dialog = null; + dialog = Dialog.create({ + title: settings.title, + message: settings.overridden, + customContent: settings.customContent, + buttons: [ settings.buttonAccept ], + escapeKeyCancels: settings.escapeKey, + close: settings.buttonClose, + }, null, function(result) { + // Let's override the next actions, to get to the final action + onNextAction(true); + }); + dialog.show(); + } else { + // Let's override the next actions, to get to the final action + onNextAction(true); + } + } else { + onNextAction(); + } + } +} + +ActionOverride.register();class ActionVerify extends Action { static NAME = "Verify"; static DEFAULTS = { @@ -1079,10 +1153,6 @@ class ActionVerify extends Action { buttonClose: false, // If falshi (i.e. null, 0, false, "false"), the esc key will not close the dialog (it will close it if true) escapeKey: true, - // If falshi (i.e. null, 0, false, "false"), the head of the dialog will be hidden - header: true, - // If falshi (i.e. null, 0, false, "false"), the footer of the dialog will be hidden - footer: true } static execute(el, options, onNextAction, onCancelActions) { @@ -1631,4 +1701,4 @@ ActionFormset.register();class ActionFormButton extends Action { } } -ActionFormButton.register(); \ No newline at end of file +ActionFormButton.register(); diff --git a/src/init.js b/src/init.js index b89dd2d..a88d354 100644 --- a/src/init.js +++ b/src/init.js @@ -104,7 +104,7 @@ exports.powerButtons = function(param1, param2 = null, param3 = null) { } }; -exports.powerButtons.version = '2.1.1'; +exports.powerButtons.version = '2.1.2'; exports.powerButtons.plugins = function() { return Object.keys(PowerButtons.actionsRegistered); } diff --git a/src/plugins/00.actionoverride.js b/src/plugins/00.actionoverride.js new file mode 100644 index 0000000..cae554b --- /dev/null +++ b/src/plugins/00.actionoverride.js @@ -0,0 +1,70 @@ +class ActionOverride extends Action { + static NAME = "Override"; + + static DEFAULTS = { + // The function to call to check for overriding the next actions. It must return a true or false value. If it is an string, it will be evaluated as javascript, using _eval_ + override: null, + // The form to bind the verification to. If it is a string, it will be interpreted as a selector (it is not verified if it is a form or any other object). If null, the verification will be bound to the document + form: null, + // The content of the message to show to the user if `override` evaluates to true (it can be either plain text or a HTML fragment) + overridden: null, + // A custom content to show to the user under the message when `override` evaluates to true (it can be either plain text or a HTML fragment) + customContent: null, + // The content of the title of the dialog when `override` evaluates to true (it can be either plain text or a HTML fragment) + title: null, + // The content for the button that confirms the action (it can be either plain text or a HTML fragment) + buttonAccept: "Accept", + // If falshi (i.e. null, 0, false, "false"), the esc key will not close the dialog (it will close it if true) + escapeKey: true, + } + + static execute(el, options, onNextAction, onCancelActions) { + // We merge the options with the defaults to get a valid settings object + let settings = PowerButtons.getActionSettings(this, options); + + let result = null; + let bindObject = searchForm(settings.form); + if (bindObject === null) { + bindObject = document; + } + try { + if (typeof(settings.override) === 'function') { + result = settings.override.bind(bindObject)(); + } else if (typeof(settings.override) === 'string') { + result = function() { + return eval(settings.override) + }.bind(bindObject)(); + } else { + result = parseBoolean(settings.override); + } + } catch (e) { + console.error("Error executing override function", e); + result = false; + } + + if (result) { + if ((settings.overridden !== null) || (settings.customContent !== null) || (settings.title !== null)) { + let dialog = null; + dialog = Dialog.create({ + title: settings.title, + message: settings.overridden, + customContent: settings.customContent, + buttons: [ settings.buttonAccept ], + escapeKeyCancels: settings.escapeKey, + close: settings.buttonClose, + }, null, function(result) { + // Let's override the next actions, to get to the final action + onNextAction(true); + }); + dialog.show(); + } else { + // Let's override the next actions, to get to the final action + onNextAction(true); + } + } else { + onNextAction(); + } + } +} + +ActionOverride.register(); \ No newline at end of file diff --git a/src/plugins/00.actionverify.js b/src/plugins/05.actionverify.js similarity index 95% rename from src/plugins/00.actionverify.js rename to src/plugins/05.actionverify.js index 410d46c..d337c31 100644 --- a/src/plugins/00.actionverify.js +++ b/src/plugins/05.actionverify.js @@ -24,10 +24,6 @@ class ActionVerify extends Action { buttonClose: false, // If falshi (i.e. null, 0, false, "false"), the esc key will not close the dialog (it will close it if true) escapeKey: true, - // If falshi (i.e. null, 0, false, "false"), the head of the dialog will be hidden - header: true, - // If falshi (i.e. null, 0, false, "false"), the footer of the dialog will be hidden - footer: true } static execute(el, options, onNextAction, onCancelActions) { diff --git a/src/plugins/80.actionformbutton.js b/src/plugins/80.actionformbutton.js index c7d0e99..f0b6e1d 100644 --- a/src/plugins/80.actionformbutton.js +++ b/src/plugins/80.actionformbutton.js @@ -152,4 +152,4 @@ class ActionFormButton extends Action { } } -ActionFormButton.register(); \ No newline at end of file +ActionFormButton.register(); diff --git a/src/powerbuttons.js b/src/powerbuttons.js index 90355a9..4cf28b9 100644 --- a/src/powerbuttons.js +++ b/src/powerbuttons.js @@ -141,10 +141,15 @@ class PowerButtons { /** * This is a handler for the accept button of the dialog (or the confirmation button), to execute the next action by simulating a click event + * @param {*} override, if true, it will override any other action and will assume that the callee was the last one */ - let onNextAction = function() { + let onNextAction = function(override = false) { // Continue with the action, by simulating the common click action - this.current_action++; + if (override) { + this.current_action = this.actions.length; + } else { + this.current_action++; + } // If it is the last confirmation, we'll execute the legacy click event (if exists; otherwise we'll dispatch an event to fire jquery events (click method already fires them)) if (this.current_action >= this.actions.length) {