AutorÃa | Ultima modificación | Ver Log |
{"version":3,"file":"dynamicform.min.js","sources":["../src/dynamicform.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Display an embedded form, it is only loaded and reloaded inside its container\n *\n *\n * @module core_form/dynamicform\n * @copyright 2019 Marina Glancy\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL
v3 or later\n * See also https://docs.moodle.org/dev/Modal_and_AJAX_forms\n *\n * @example\n * import DynamicForm from 'core_form/dynamicform';\n *\n * const dynamicForm = new DynamicForm(document.querySelector('#mycontainer', 'pluginname\\\\form\\\\formname');\n * dynamicForm.addEventListener(dynamicForm.events.FORM_SUBMITTED, e => {\n * e.preventDefault();\n * window.console.log(e.detail);\n * dynamicForm.container.innerHTML = 'Thank you, your form is submitted!';\n * });\n * dynamicForm.load();\n *\n */\n\nimport * as FormChangeChecker from 'core_form/changechecker';\nimport * as FormEvents from 'core_form/events';\nimport Ajax from 'core/ajax';\nimport Fragment from 'core/fragment';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport Templates from 'core/templates';\nimport {getStrings} from 'core/str';\nimport {serialize} from './util';\n\n/**\n * @class core_form/dynamicform\n */\nexport default class DynamicForm {\n\n /**\n
* Various events that can be observed.\n *\n * @type {Object}\n */\n events = {\n // Form was successfully submitted - the response is passed to the event listener.\n // Cancellable (in order to prevent default behavior to clear the container).\n FORM_SUBMITTED: 'core_form_dynamicform_formsubmitted',\n // Cancel button was pressed.\n // Cancellable (in order to prevent default behavior to clear the container).\n FORM_CANCELLED: 'core_form_dynamicform_formcancelled',\n // User attempted to submit the form but there was client-side validation error.\n CLIENT_VALIDATION_ERROR: 'core_form_dynamicform_clientvalidationerror',\n // User attempted to submit the form but server returned validation error.\n SERVER_VALIDATION_ERROR: 'core_form_dynamicform_validationerror',\n // Error occurred while performing request to the server.\n // Cancellable (by default calls Notification.exception).\n ERROR: 'core_form
_dynamicform_error',\n // Right after user pressed no-submit button,\n // listen to this event if you want to add JS validation or processing for no-submit button.\n // Cancellable.\n NOSUBMIT_BUTTON_PRESSED: 'core_form_dynamicform_nosubmitbutton',\n // Right after user pressed submit button,\n // listen to this event if you want to add additional JS validation or confirmation dialog.\n // Cancellable.\n SUBMIT_BUTTON_PRESSED: 'core_form_dynamicform_submitbutton',\n // Right after user pressed cancel button,\n // listen to this event if you want to add confirmation dialog.\n // Cancellable.\n CANCEL_BUTTON_PRESSED: 'core_form_dynamicform_cancelbutton',\n };\n\n /**\n * Constructor\n *\n * Creates an instance\n *\n * @param {Element} container - the parent element for the form\n * @param {string} formClass full name of the php class that extends \\core_form\\modal , must be in autoloaded location\
n */\n constructor(container, formClass) {\n this.formClass = formClass;\n this.container = container;\n\n // Ensure strings required for shortforms are always available.\n getStrings([\n {key: 'collapseall', component: 'moodle'},\n {key: 'expandall', component: 'moodle'}\n ]).catch(Notification.exception);\n\n // Register delegated events handlers in vanilla JS.\n this.container.addEventListener('click', e => {\n if (e.target.matches('form input[type=submit][data-cancel]')) {\n e.preventDefault();\n const event = this.trigger(this.events.CANCEL_BUTTON_PRESSED, e.target);\n if (!event.defaultPrevented) {\n this.processCancelButton();\n }\n } else if (e.target.matches('form input[type=submit][data-no-submit=\"1\"]')) {\n e.preventDefault();\n const event = this.trigger(this.events.NOSUBMIT_BUTTON_PRESSED
, e.target);\n if (!event.defaultPrevented) {\n this.processNoSubmitButton(e.target);\n }\n }\n });\n\n this.container.addEventListener('submit', e => {\n if (e.target.matches('form')) {\n e.preventDefault();\n const event = this.trigger(this.events.SUBMIT_BUTTON_PRESSED);\n if (!event.defaultPrevented) {\n this.submitFormAjax();\n }\n }\n });\n }\n\n /**\n * Loads the form via AJAX and shows it inside a given container\n *\n * @param {Object} args\n * @return {Promise}\n * @public\n */\n load(args = null) {\n const formData = serialize(args || {});\n const pendingPromise = new Pending('core_form/dynamicform:load');\n return this.getBody(formData)\n .then((resp) => this.updateForm(resp))\n .then(pendingPromise.resolve);\n }\n\n /**\n * Triggers a custom
event\n *\n * @private\n * @param {String} eventName\n * @param {*} detail\n * @param {Boolean} cancelable\n * @return {CustomEvent<unknown>}\n */\n trigger(eventName, detail = null, cancelable = true) {\n const e = new CustomEvent(eventName, {detail, cancelable});\n this.container.dispatchEvent(e);\n return e;\n }\n\n /**\n * Add listener for an event\n *\n * @param {array} args\n * @example:\n * const dynamicForm = new DynamicForm(...);\n * dynamicForm.addEventListener(dynamicForm.events.FORM_SUBMITTED, e => {\n * e.preventDefault();\n * window.console.log(e.detail);\n * dynamicForm.container.innerHTML = 'Thank you, your form is submitted!';\n * });\n */\n addEventListener(...args) {\n this.container.addEventListener(...args);\n }\n\n /**\n * Get form body\n *\n * @param {String} formDataString form data in format of a query string\n * @private
\n * @return {Promise}\n */\n getBody(formDataString) {\n return Ajax.call([{\n methodname: 'core_form_dynamic_form',\n args: {\n formdata: formDataString,\n form: this.formClass,\n }\n }])[0]\n .then(response => {\n return {html: response.html, js: Fragment.processCollectedJavascript(response.javascript)};\n });\n }\n\n /**\n * On form submit\n *\n * @param {*} response Response received from the form's \"process\" method\n */\n onSubmitSuccess(response) {\n const event = this.trigger(this.events.FORM_SUBMITTED, response);\n if (event.defaultPrevented) {\n return;\n }\n\n // Default implementation is to remove the form. Event listener should either remove or reload the form\n // since its contents is no longer correct. For example, if an element was created as a result of\n // form submission, the \"id\" in the form wou
ld be still zero. Also the server-side validation\n // errors from the previous submission may still be present.\n this.container.innerHTML = '';\n }\n\n /**\n * On exception during form processing\n *\n * @private\n * @param {Object} exception\n */\n onSubmitError(exception) {\n const event = this.trigger(this.events.ERROR, exception);\n if (event.defaultPrevented) {\n return;\n }\n\n Notification.exception(exception);\n }\n\n /**\n * Click on a \"submit\" button that is marked in the form as registerNoSubmitButton()\n *\n * @method submitButtonPressed\n * @param {Element} button that was pressed\n * @fires event:formSubmittedByJavascript\n */\n processNoSubmitButton(button) {\n const pendingPromise = new Pending('core_form/dynamicform:nosubmit');\n const form = this.getFormNode();\n const formData = new URLSearchParams([...(new FormData(form)).entries()]);\n formDat
a.append(button.getAttribute('name'), button.getAttribute('value'));\n\n FormEvents.notifyFormSubmittedByJavascript(form, true);\n\n // Add the button name to the form data and submit it.\n this.disableButtons();\n\n this.getBody(formData.toString())\n .then(resp => this.updateForm(resp))\n .then(pendingPromise.resolve)\n .catch(exception => this.onSubmitError(exception));\n }\n\n /**\n * Get the form node from the Dialogue.\n *\n * @returns {HTMLFormElement}\n */\n getFormNode() {\n return this.container.querySelector('form');\n }\n\n /**\n * Notifies listeners that form dirty state should be reset.\n *\n * @fires event:formSubmittedByJavascript\n */\n notifyResetFormChanges() {\n FormEvents.notifyFormSubmittedByJavascript(this.getFormNode(), true);\n FormChangeChecker.resetFormDirtyState(this.getFormNode());\n }\n\n /**\n * Click on a \"cancel\" button\n */\n processCance
lButton() {\n // Notify listeners that the form is about to be submitted (this will reset atto autosave).\n this.notifyResetFormChanges();\n\n const event = this.trigger(this.events.FORM_CANCELLED);\n if (!event.defaultPrevented) {\n // By default removes the form from the DOM.\n this.container.innerHTML = '';\n }\n }\n\n /**\n * Update form contents\n *\n * @param {object} param\n * @param {string} param.html\n * @param {string} param.js\n * @returns {Promise}\n */\n updateForm({html, js}) {\n return Templates.replaceNodeContents(this.container, html, js);\n }\n\n /**\n * Validate form elements\n * @return {Boolean} Whether client-side validation has passed, false if there are errors\n * @fires event:formSubmittedByJavascript\n */\n validateElements() {\n // Notify listeners that the form is about to be submitted (this will reset atto autosave).\n FormEvents.notifyFormS
ubmittedByJavascript(this.getFormNode());\n\n // Now the change events have run, see if there are any \"invalid\" form fields.\n const invalid = [...this.container.querySelectorAll('[aria-invalid=\"true\"], .error')];\n\n // If we found invalid fields, focus on the first one and do not submit via ajax.\n if (invalid.length) {\n invalid[0].focus();\n return false;\n }\n\n return true;\n }\n\n /**\n * Disable buttons during form submission\n */\n disableButtons() {\n this.container.querySelectorAll('form input[type=\"submit\"]')\n .forEach(el => el.setAttribute('disabled', true));\n }\n\n /**\n * Enable buttons after form submission (on validation error)\n */\n enableButtons() {\n this.container.querySelectorAll('form input[type=\"submit\"]')\n .forEach(el => el.removeAttribute('disabled'));\n }\n\n /**\n * Submit the form via AJAX call to the core_form_dynamic_form WS
\n */\n async submitFormAjax() {\n // If we found invalid fields, focus on the first one and do not submit via ajax.\n if (!(await this.validateElements())) {\n this.trigger(this.events.CLIENT_VALIDATION_ERROR, null, false);\n return;\n }\n this.disableButtons();\n\n // Convert all the form elements values to a serialised string.\n const form = this.container.querySelector('form');\n const formData = new URLSearchParams([...(new FormData(form)).entries()]);\n\n // Now we can continue...\n Ajax.call([{\n methodname: 'core_form_dynamic_form',\n args: {\n formdata: formData.toString(),\n form: this.formClass\n }\n }])[0]\n .then((response) => {\n if (!response.submitted) {\n // Form was not submitted, it could be either because validation failed or because no-submit button was pressed.\n this.updateForm({
html: response.html, js: Fragment.processCollectedJavascript(response.javascript)});\n this.enableButtons();\n this.trigger(this.events.SERVER_VALIDATION_ERROR, null, false);\n } else {\n // Form was submitted properly.\n const data = JSON.parse(response.data);\n this.enableButtons();\n this.notifyResetFormChanges();\n this.onSubmitSuccess(data);\n }\n return null;\n })\n .catch(exception => this.onSubmitError(exception));\n }\n}\n"],"names":["constructor","container","formClass","FORM_SUBMITTED","FORM_CANCELLED","CLIENT_VALIDATION_ERROR","SERVER_VALIDATION_ERROR","ERROR","NOSUBMIT_BUTTON_PRESSED","SUBMIT_BUTTON_PRESSED","CANCEL_BUTTON_PRESSED","key","component","catch","Notification","exception","addEventListener","e","target","matches","preventDefault","this","trigger","events","defaultPrevented","processCancelButton","processNoSubmitButton","submitFormA
jax","load","args","formData","pendingPromise","Pending","getBody","then","resp","updateForm","resolve","eventName","CustomEvent","detail","cancelable","dispatchEvent","formDataString","Ajax","call","methodname","formdata","form","response","html","js","Fragment","processCollectedJavascript","javascript","onSubmitSuccess","innerHTML","onSubmitError","button","getFormNode","URLSearchParams","FormData","entries","append","getAttribute","FormEvents","notifyFormSubmittedByJavascript","disableButtons","toString","querySelector","notifyResetFormChanges","FormChangeChecker","resetFormDirtyState","Templates","replaceNodeContents","validateElements","invalid","querySelectorAll","length","focus","forEach","el","setAttribute","enableButtons","removeAttribute","submitted","data","JSON","parse"],"mappings":"yuDA6FIA,YAAYC,UAAWC,mCApCd,CAGLC,eAAgB,sCAGhBC,eAAgB,sCAEhBC,wBAAyB,8CAEzBC,wBAAyB,wCAGzBC,MAAO,8BAIPC,wBAAyB,uCAIzBC,sBAAuB,qCAIvBC,sBAAuB,4KAYlBR,UAAYA,eACZD,UAAYA,8BAGN,CACP,CAACU,IAAK,cAAeC,UAAW,UAChC,CAACD,IAAK,
YAAaC,UAAW,YAC/BC,MAAMC,sBAAaC,gBAGjBd,UAAUe,iBAAiB,SAASC,OACjCA,EAAEC,OAAOC,QAAQ,wCAAyC,CAC1DF,EAAEG,iBACYC,KAAKC,QAAQD,KAAKE,OAAOb,sBAAuBO,EAAEC,QACrDM,uBACFC,2BAEN,GAAIR,EAAEC,OAAOC,QAAQ,+CAAgD,CACxEF,EAAEG,iBACYC,KAAKC,QAAQD,KAAKE,OAAOf,wBAAyBS,EAAEC,QACvDM,uBACFE,sBAAsBT,EAAEC,iBAKpCjB,UAAUe,iBAAiB,UAAUC,OAClCA,EAAEC,OAAOC,QAAQ,QAAS,CAC1BF,EAAEG,iBACYC,KAAKC,QAAQD,KAAKE,OAAOd,uBAC5Be,uBACFG,qBAarBC,WAAKC,4DAAO,WACFC,UAAW,mBAAUD,MAAQ,IAC7BE,eAAiB,IAAIC,iBAAQ,qCAC5BX,KAAKY,QAAQH,UACnBI,MAAMC,MAASd,KAAKe,WAAWD,QAC/BD,KAAKH,eAAeM,SAYzBf,QAAQgB,iBACErB,EAAI,IAAIsB,YAAYD,UAAW,CAACE,8DADd,KACsBC,oFACzCxC,UAAUyC,cAAczB,GACtBA,EAeXD,wBACSf,UAAUe,+BAUnBiB,QAAQU,uBACGC,cAAKC,KAAK,CAAC,CACdC,WAAY,yBACZjB,KAAM,CACFkB,SAAUJ,eACVK,KAAM3B,KAAKnB,cAEf,GACHgC,MAAKe,WACK,CAACC,KAAMD,SAASC,KAAMC,GAAIC,kBAASC,2BAA2BJ,SAASK,gBAStFC,gBAAgBN,UACE5B,KAAKC,QAAQD,KAAKE,OAAOpB,eAAgB8C,UAC7CzB,wBAQLvB,UAAUuD,UAAY,IAS/BC,cAAc1C,WACIM,KAAKC,QAAQD,KAAKE,OAAOhB,MAAOQ,WACpCS,wCAIGT,UAAUA,WAU3BW,sBAAsBgC,cACZ3B,eAAiB,IAAIC,iBAAQ,kCAC7BgB
,KAAO3B,KAAKsC,cACZ7B,SAAW,IAAI8B,gBAAgB,IAAK,IAAIC,SAASb,MAAOc,YAC9DhC,SAASiC,OAAOL,OAAOM,aAAa,QAASN,OAAOM,aAAa,UAEjEC,WAAWC,gCAAgClB,MAAM,QAG5CmB,sBAEAlC,QAAQH,SAASsC,YACrBlC,MAAKC,MAAQd,KAAKe,WAAWD,QAC7BD,KAAKH,eAAeM,SACpBxB,OAAME,WAAaM,KAAKoC,cAAc1C,aAQ3C4C,qBACWtC,KAAKpB,UAAUoE,cAAc,QAQxCC,yBACIL,WAAWC,gCAAgC7C,KAAKsC,eAAe,GAC/DY,kBAAkBC,oBAAoBnD,KAAKsC,eAM/ClC,2BAES6C,yBAESjD,KAAKC,QAAQD,KAAKE,OAAOnB,gBAC5BoB,wBAEFvB,UAAUuD,UAAY,IAYnCpB,qBAAWc,KAACA,KAADC,GAAOA,gBACPsB,mBAAUC,oBAAoBrD,KAAKpB,UAAWiD,KAAMC,IAQ/DwB,mBAEIV,WAAWC,gCAAgC7C,KAAKsC,qBAG1CiB,QAAU,IAAIvD,KAAKpB,UAAU4E,iBAAiB,yCAGhDD,QAAQE,SACRF,QAAQ,GAAGG,SACJ,GASfZ,sBACSlE,UAAU4E,iBAAiB,6BAC3BG,SAAQC,IAAMA,GAAGC,aAAa,YAAY,KAMnDC,qBACSlF,UAAU4E,iBAAiB,6BAC3BG,SAAQC,IAAMA,GAAGG,gBAAgB,+CAQ1B/D,KAAKsD,oCACRrD,QAAQD,KAAKE,OAAOlB,wBAAyB,MAAM,QAGvD8D,uBAGCnB,KAAO3B,KAAKpB,UAAUoE,cAAc,QACpCvC,SAAW,IAAI8B,gBAAgB,IAAK,IAAIC,SAASb,MAAOc,0BAGzDjB,KAAK,CAAC,CACPC,WAAY,yBACZjB,KAAM,CACFkB,SAAUjB,SAASsC,WACnBpB,KAAM3B,KAAKnB,cAEf,GACHgC,MAAMe,cACEA,SAASoC,UA
KP,OAEGC,KAAOC,KAAKC,MAAMvC,SAASqC,WAC5BH,qBACAb,8BACAf,gBAAgB+B,gBARhBlD,WAAW,CAACc,KAAMD,SAASC,KAAMC,GAAIC,kBAASC,2BAA2BJ,SAASK,mBAClF6B,qBACA7D,QAAQD,KAAKE,OAAOjB,wBAAyB,MAAM,UAQrD,QAEVO,OAAME,WAAaM,KAAKoC,cAAc1C"}