diff --git a/lib/form/amd/build/dynamicform.min.js b/lib/form/amd/build/dynamicform.min.js index 93c1892d3f4..be6193f6ebf 100644 --- a/lib/form/amd/build/dynamicform.min.js +++ b/lib/form/amd/build/dynamicform.min.js @@ -1,2 +1,2 @@ -function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("core_form/dynamicform",["exports","core_form/changechecker","core_form/events","core/ajax","core/fragment","core/notification","core/pending","core/templates","core/str"],function(a,b,c,d,e,f,g,h,i){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=l(b);c=l(c);d=j(d);e=j(e);f=j(f);g=j(g);h=j(h);function j(a){return a&&a.__esModule?a:{default:a}}function k(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;k=function(){return a};return a}function l(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=k();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function m(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function n(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){m(h,d,e,f,g,"next",a)}function g(a){m(h,d,e,f,g,"throw",a)}f(void 0)})}}function o(a){return s(a)||r(a)||q(a)||p()}function p(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function q(a,b){if(!a)return;if("string"==typeof a)return t(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return t(a,b)}function r(a){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(a))return Array.from(a)}function s(a){if(Array.isArray(a))return t(a)}function t(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);ca.length)b=a.length;for(var c=0,d=Array(b);c.\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 {get_strings as getStrings} from 'core/str';\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 = new URLSearchParams(Object.entries(args || {}));\n const pendingPromise = new Pending('core_form/dynamicform:load');\n return this.getBody(formData.toString())\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}\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 would 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 formData.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(this.updateForm)\n .then(pendingPromise.resolve)\n .catch(this.onSubmitError);\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 processCancelButton() {\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.notifyFormSubmittedByJavascript(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(this.onSubmitError);\n }\n}\n"],"file":"dynamicform.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/dynamicform.js"],"names":["DynamicForm","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","event","trigger","events","defaultPrevented","processCancelButton","processNoSubmitButton","submitFormAjax","args","formData","URLSearchParams","Object","entries","pendingPromise","Pending","getBody","toString","then","resp","updateForm","resolve","eventName","detail","cancelable","CustomEvent","dispatchEvent","formDataString","Ajax","call","methodname","formdata","form","response","html","js","Fragment","processCollectedJavascript","javascript","innerHTML","button","getFormNode","FormData","append","getAttribute","FormEvents","notifyFormSubmittedByJavascript","disableButtons","onSubmitError","querySelector","FormChangeChecker","resetFormDirtyState","notifyResetFormChanges","Templates","replaceNodeContents","invalid","querySelectorAll","length","focus","forEach","el","setAttribute","removeAttribute","validateElements","submitted","enableButtons","data","JSON","parse","onSubmitSuccess"],"mappings":"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","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 .\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 {get_strings as getStrings} from 'core/str';\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 = new URLSearchParams(Object.entries(args || {}));\n const pendingPromise = new Pending('core_form/dynamicform:load');\n return this.getBody(formData.toString())\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}\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 would 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 formData.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(this.updateForm)\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 processCancelButton() {\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.notifyFormSubmittedByJavascript(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"],"file":"dynamicform.min.js"} \ No newline at end of file diff --git a/lib/form/amd/build/modalform.min.js b/lib/form/amd/build/modalform.min.js index 1c5d2f31e54..0bf3c58004c 100644 --- a/lib/form/amd/build/modalform.min.js +++ b/lib/form/amd/build/modalform.min.js @@ -1,2 +1,2 @@ -function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("core_form/modalform",["exports","core/ajax","core_form/changechecker","core_form/events","core/fragment","core/modal_events","core/modal_factory","core/notification","core/pending"],function(a,b,c,d,e,f,g,h,i){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=l(b);c=k(c);d=k(d);e=l(e);f=l(f);g=l(g);h=l(h);i=l(i);function j(){if("function"!=typeof WeakMap)return null;var a=new WeakMap;j=function(){return a};return a}function k(a){if(a&&a.__esModule){return a}if(null===a||"object"!==_typeof(a)&&"function"!=typeof a){return{default:a}}var b=j();if(b&&b.has(a)){return b.get(a)}var c={},d=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var e in a){if(Object.prototype.hasOwnProperty.call(a,e)){var f=d?Object.getOwnPropertyDescriptor(a,e):null;if(f&&(f.get||f.set)){Object.defineProperty(c,e,f)}else{c[e]=a[e]}}}c.default=a;if(b){b.set(a,c)}return c}function l(a){return a&&a.__esModule?a:{default:a}}function m(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function n(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){m(h,d,e,f,g,"next",a)}function g(a){m(h,d,e,f,g,"throw",a)}f(void 0)})}}function o(a){return s(a)||r(a)||q(a)||p()}function p(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function q(a,b){if(!a)return;if("string"==typeof a)return t(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return t(a,b)}function r(a){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(a))return Array.from(a)}function s(a){if(Array.isArray(a))return t(a)}function t(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);ca.length)b=a.length;for(var c=0,d=Array(b);c.\n\n/**\n * Display a form in a modal dialogue\n *\n * Example:\n * import ModalForm from 'core_form/modalform';\n *\n * const modalForm = new ModalForm({\n * formClass: 'pluginname\\\\form\\\\formname',\n * modalConfig: {title: 'Here comes the title'},\n * args: {categoryid: 123},\n * returnFocus: e.target,\n * });\n * modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, (c) => window.console.log(c.detail));\n * modalForm.show();\n *\n * See also https://docs.moodle.org/dev/Modal_and_AJAX_forms\n *\n * @module core_form/modalform\n * @copyright 2018 Mitxel Moriana \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport * as FormChangeChecker from 'core_form/changechecker';\nimport * as FormEvents from 'core_form/events';\nimport Fragment from 'core/fragment';\nimport ModalEvents from 'core/modal_events';\nimport ModalFactory from 'core/modal_factory';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\n\nexport default class ModalForm {\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 (but it's hardly ever needed to cancel this event).\n FORM_SUBMITTED: 'core_form_modalform_formsubmitted',\n // Cancel button was pressed.\n // Cancellable (but it's hardly ever needed to cancel this event).\n FORM_CANCELLED: 'core_form_modalform_formcancelled',\n // User attempted to submit the form but there was client-side validation error.\n CLIENT_VALIDATION_ERROR: 'core_form_modalform_clientvalidationerror',\n // User attempted to submit the form but server returned validation error.\n SERVER_VALIDATION_ERROR: 'core_form_modalform_validationerror',\n // Error occurred while performing request to the server.\n // Cancellable (by default calls Notification.exception).\n ERROR: 'core_form_modalform_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_modalform_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_modalform_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_modalform_cancelbutton',\n // Modal was loaded and this.modal is available (but the form content may not be loaded yet).\n LOADED: 'core_form_modalform_loaded',\n };\n\n /**\n * Constructor\n *\n * Shows the required form inside a modal dialogue\n *\n * @param {Object} config parameters for the form and modal dialogue:\n * @paramy {String} config.formClass PHP class name that handles the form (should extend \\core_form\\modal )\n * @paramy {Object} config.modalConfig modal config - title, type, etc.\n * Default: {removeOnClose: true, type: ModalFactory.types.SAVE_CANCEL}\n * @paramy {Object} config.args Arguments for the initial form rendering (for example, id of the edited entity)\n * @paramy {String} config.saveButtonText the text to display on the Modal \"Save\" button (optional)\n * @paramy {String} config.saveButtonClasses additional CSS classes for the Modal \"Save\" button\n * @paramy {HTMLElement} config.returnFocus element to return focus to after the dialogue is closed\n */\n constructor(config) {\n this.modal = null;\n this.config = config;\n this.config.modalConfig = {\n removeOnClose: true,\n type: ModalFactory.types.SAVE_CANCEL,\n large: true,\n ...(this.config.modalConfig || {}),\n };\n this.config.args = this.config.args || {};\n this.futureListeners = [];\n }\n\n /**\n * Initialise the modal and shows it\n *\n * @return {Promise}\n */\n show() {\n const pendingPromise = new Pending('core_form/modalform:init');\n return ModalFactory.create(this.config.modalConfig)\n .then((modal) => {\n this.modal = modal;\n\n // Retrieve the form and set the modal body. We can not set the body in the modalConfig,\n // we need to make sure that the modal already exists when we render the form. Some form elements\n // such as date_selector inspect the existing elements on the page to find the highest z-index.\n const formParams = new URLSearchParams(Object.entries(this.config.args || {}));\n const bodyContent = this.getBody(formParams.toString());\n this.modal.setBodyContent(bodyContent);\n bodyContent.catch(Notification.exception);\n\n // After successfull submit, when we press \"Cancel\" or close the dialogue by clicking on X in the top right corner.\n this.modal.getRoot().on(ModalEvents.hidden, () => {\n this.notifyResetFormChanges();\n this.modal.destroy();\n // Focus on the element that actually launched the modal.\n if (this.config.returnFocus) {\n this.config.returnFocus.focus();\n }\n });\n\n // Add the class to the modal dialogue.\n this.modal.getModal().addClass('modal-form-dialogue');\n\n // We catch the press on submit buttons in the forms.\n this.modal.getRoot().on('click', 'form input[type=submit][data-no-submit]',\n (e) => {\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 // We catch the form submit event and use it to submit the form with ajax.\n this.modal.getRoot().on('submit', 'form', (e) => {\n e.preventDefault();\n const event = this.trigger(this.events.SUBMIT_BUTTON_PRESSED);\n if (!event.defaultPrevented) {\n this.submitFormAjax();\n }\n });\n\n // Change the text for the save button.\n if (typeof this.config.saveButtonText !== 'undefined' &&\n typeof this.modal.setSaveButtonText !== 'undefined') {\n this.modal.setSaveButtonText(this.config.saveButtonText);\n }\n // Set classes for the save button.\n if (typeof this.config.saveButtonClasses !== 'undefined') {\n this.setSaveButtonClasses(this.config.saveButtonClasses);\n }\n // When Save button is pressed - submit the form.\n this.modal.getRoot().on(ModalEvents.save, (e) => {\n e.preventDefault();\n this.modal.getRoot().find('form').submit();\n });\n\n // When Cancel button is pressed - allow to intercept.\n this.modal.getRoot().on(ModalEvents.cancel, (e) => {\n const event = this.trigger(this.events.CANCEL_BUTTON_PRESSED);\n if (event.defaultPrevented) {\n e.preventDefault();\n }\n });\n this.futureListeners.forEach(args => this.modal.getRoot()[0].addEventListener(...args));\n this.futureListeners = [];\n this.trigger(this.events.LOADED, null, false);\n return this.modal.show();\n })\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}\n */\n trigger(eventName, detail = null, cancelable = true) {\n const e = new CustomEvent(eventName, {detail, cancelable});\n this.modal.getRoot()[0].dispatchEvent(e);\n return e;\n }\n\n /**\n * Add listener for an event\n *\n * @param {array} args\n * @example:\n * const modalForm = new ModalForm(...);\n * dynamicForm.addEventListener(modalForm.events.FORM_SUBMITTED, e => {\n * window.console.log(e.detail);\n * });\n */\n addEventListener(...args) {\n if (!this.modal) {\n this.futureListeners.push(args);\n } else {\n this.modal.getRoot()[0].addEventListener(...args);\n }\n }\n\n /**\n * Get form contents (to be used in ModalForm.setBodyContent())\n *\n * @param {String} formDataString form data in format of a query string\n * @method getBody\n * @private\n * @return {Promise}\n */\n getBody(formDataString) {\n const params = {\n formdata: formDataString,\n form: this.config.formClass\n };\n const pendingPromise = new Pending('core_form/modalform:form_body');\n return Ajax.call([{\n methodname: 'core_form_dynamic_form',\n args: params\n }])[0]\n .then(response => {\n pendingPromise.resolve();\n return {html: response.html, js: Fragment.processCollectedJavascript(response.javascript)};\n });\n }\n\n /**\n * On exception during form processing. Caller may override\n *\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 * Notifies listeners that form dirty state should be reset.\n *\n * @fires event:formSubmittedByJavascript\n */\n notifyResetFormChanges() {\n const form = this.getFormNode();\n FormEvents.notifyFormSubmittedByJavascript(this.getFormNode(), true);\n\n if (!form) {\n return;\n }\n\n FormChangeChecker.resetFormDirtyState(this.getFormNode());\n }\n\n /**\n * Get the form node from the Dialogue.\n *\n * @returns {HTMLFormElement}\n */\n getFormNode() {\n return this.modal.getRoot().find('form')[0];\n }\n\n /**\n * Click on a \"submit\" button that is marked in the form as registerNoSubmitButton()\n *\n * @param {Element} button button that was pressed\n * @fires event:formSubmittedByJavascript\n */\n processNoSubmitButton(button) {\n const form = this.getFormNode();\n if (!form) {\n return;\n }\n\n FormEvents.notifyFormSubmittedByJavascript(form, true);\n\n // Add the button name to the form data and submit it.\n let formData = this.modal.getRoot().find('form').serialize();\n formData = formData + '&' + encodeURIComponent(button.getAttribute('name')) + '=' +\n encodeURIComponent(button.getAttribute('value'));\n\n const bodyContent = this.getBody(formData);\n this.modal.setBodyContent(bodyContent);\n bodyContent.catch(Notification.exception);\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 FormEvents.notifyFormSubmittedByJavascript(this.getFormNode());\n\n // Now the change events have run, see if there are any \"invalid\" form fields.\n /** @var {jQuery} list of elements with errors */\n const invalid = this.modal.getRoot().find('[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.first().focus();\n return false;\n }\n\n return true;\n }\n\n /**\n * Disable buttons during form submission\n */\n disableButtons() {\n this.modal.getFooter().find('[data-action]').attr('disabled', true);\n }\n\n /**\n * Enable buttons after form submission (on validation error)\n */\n enableButtons() {\n this.modal.getFooter().find('[data-action]').removeAttr('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 (!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 formData = this.modal.getRoot().find('form').serialize();\n\n // Now we can continue...\n Ajax.call([{\n methodname: 'core_form_dynamic_form',\n args: {\n formdata: formData,\n form: this.config.formClass\n }\n }])[0]\n .then((response) => {\n if (!response.submitted) {\n // Form was not submitted because validation failed.\n const promise = new Promise(\n resolve => resolve({html: response.html, js: Fragment.processCollectedJavascript(response.javascript)}));\n this.modal.setBodyContent(promise);\n this.enableButtons();\n this.trigger(this.events.SERVER_VALIDATION_ERROR);\n } else {\n // Form was submitted properly. Hide the modal and execute callback.\n const data = JSON.parse(response.data);\n this.notifyResetFormChanges();\n const event = this.trigger(this.events.FORM_SUBMITTED, data);\n if (!event.defaultPrevented) {\n this.modal.hide();\n }\n }\n return null;\n })\n .catch(this.onSubmitError);\n }\n\n /**\n * Set the classes for the 'save' button.\n *\n * @method setSaveButtonClasses\n * @param {(String)} value The 'save' button classes.\n */\n setSaveButtonClasses(value) {\n const button = this.modal.getFooter().find(\"[data-action='save']\");\n if (!button) {\n throw new Error(\"Unable to find the 'save' button\");\n }\n button.removeClass().addClass(value);\n }\n}\n"],"file":"modalform.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/modalform.js"],"names":["ModalForm","config","FORM_SUBMITTED","FORM_CANCELLED","CLIENT_VALIDATION_ERROR","SERVER_VALIDATION_ERROR","ERROR","NOSUBMIT_BUTTON_PRESSED","SUBMIT_BUTTON_PRESSED","CANCEL_BUTTON_PRESSED","LOADED","modal","modalConfig","removeOnClose","type","ModalFactory","types","SAVE_CANCEL","large","args","futureListeners","pendingPromise","Pending","create","then","formParams","URLSearchParams","Object","entries","bodyContent","getBody","toString","setBodyContent","catch","Notification","exception","getRoot","on","ModalEvents","hidden","notifyResetFormChanges","destroy","returnFocus","focus","getModal","addClass","e","preventDefault","event","trigger","events","target","defaultPrevented","processNoSubmitButton","submitFormAjax","saveButtonText","setSaveButtonText","saveButtonClasses","setSaveButtonClasses","save","find","submit","cancel","forEach","addEventListener","show","resolve","eventName","detail","cancelable","CustomEvent","dispatchEvent","push","formDataString","params","formdata","form","formClass","Ajax","call","methodname","response","html","js","Fragment","processCollectedJavascript","javascript","getFormNode","FormEvents","notifyFormSubmittedByJavascript","FormChangeChecker","resetFormDirtyState","button","formData","serialize","encodeURIComponent","getAttribute","invalid","length","first","getFooter","attr","removeAttr","validateElements","disableButtons","submitted","promise","Promise","enableButtons","data","JSON","parse","hide","onSubmitError","value","Error","removeClass"],"mappings":"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","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 .\n\n/**\n * Display a form in a modal dialogue\n *\n * Example:\n * import ModalForm from 'core_form/modalform';\n *\n * const modalForm = new ModalForm({\n * formClass: 'pluginname\\\\form\\\\formname',\n * modalConfig: {title: 'Here comes the title'},\n * args: {categoryid: 123},\n * returnFocus: e.target,\n * });\n * modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, (c) => window.console.log(c.detail));\n * modalForm.show();\n *\n * See also https://docs.moodle.org/dev/Modal_and_AJAX_forms\n *\n * @module core_form/modalform\n * @copyright 2018 Mitxel Moriana \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Ajax from 'core/ajax';\nimport * as FormChangeChecker from 'core_form/changechecker';\nimport * as FormEvents from 'core_form/events';\nimport Fragment from 'core/fragment';\nimport ModalEvents from 'core/modal_events';\nimport ModalFactory from 'core/modal_factory';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\n\nexport default class ModalForm {\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 (but it's hardly ever needed to cancel this event).\n FORM_SUBMITTED: 'core_form_modalform_formsubmitted',\n // Cancel button was pressed.\n // Cancellable (but it's hardly ever needed to cancel this event).\n FORM_CANCELLED: 'core_form_modalform_formcancelled',\n // User attempted to submit the form but there was client-side validation error.\n CLIENT_VALIDATION_ERROR: 'core_form_modalform_clientvalidationerror',\n // User attempted to submit the form but server returned validation error.\n SERVER_VALIDATION_ERROR: 'core_form_modalform_validationerror',\n // Error occurred while performing request to the server.\n // Cancellable (by default calls Notification.exception).\n ERROR: 'core_form_modalform_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_modalform_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_modalform_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_modalform_cancelbutton',\n // Modal was loaded and this.modal is available (but the form content may not be loaded yet).\n LOADED: 'core_form_modalform_loaded',\n };\n\n /**\n * Constructor\n *\n * Shows the required form inside a modal dialogue\n *\n * @param {Object} config parameters for the form and modal dialogue:\n * @paramy {String} config.formClass PHP class name that handles the form (should extend \\core_form\\modal )\n * @paramy {Object} config.modalConfig modal config - title, type, etc.\n * Default: {removeOnClose: true, type: ModalFactory.types.SAVE_CANCEL}\n * @paramy {Object} config.args Arguments for the initial form rendering (for example, id of the edited entity)\n * @paramy {String} config.saveButtonText the text to display on the Modal \"Save\" button (optional)\n * @paramy {String} config.saveButtonClasses additional CSS classes for the Modal \"Save\" button\n * @paramy {HTMLElement} config.returnFocus element to return focus to after the dialogue is closed\n */\n constructor(config) {\n this.modal = null;\n this.config = config;\n this.config.modalConfig = {\n removeOnClose: true,\n type: ModalFactory.types.SAVE_CANCEL,\n large: true,\n ...(this.config.modalConfig || {}),\n };\n this.config.args = this.config.args || {};\n this.futureListeners = [];\n }\n\n /**\n * Initialise the modal and shows it\n *\n * @return {Promise}\n */\n show() {\n const pendingPromise = new Pending('core_form/modalform:init');\n return ModalFactory.create(this.config.modalConfig)\n .then((modal) => {\n this.modal = modal;\n\n // Retrieve the form and set the modal body. We can not set the body in the modalConfig,\n // we need to make sure that the modal already exists when we render the form. Some form elements\n // such as date_selector inspect the existing elements on the page to find the highest z-index.\n const formParams = new URLSearchParams(Object.entries(this.config.args || {}));\n const bodyContent = this.getBody(formParams.toString());\n this.modal.setBodyContent(bodyContent);\n bodyContent.catch(Notification.exception);\n\n // After successfull submit, when we press \"Cancel\" or close the dialogue by clicking on X in the top right corner.\n this.modal.getRoot().on(ModalEvents.hidden, () => {\n this.notifyResetFormChanges();\n this.modal.destroy();\n // Focus on the element that actually launched the modal.\n if (this.config.returnFocus) {\n this.config.returnFocus.focus();\n }\n });\n\n // Add the class to the modal dialogue.\n this.modal.getModal().addClass('modal-form-dialogue');\n\n // We catch the press on submit buttons in the forms.\n this.modal.getRoot().on('click', 'form input[type=submit][data-no-submit]',\n (e) => {\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 // We catch the form submit event and use it to submit the form with ajax.\n this.modal.getRoot().on('submit', 'form', (e) => {\n e.preventDefault();\n const event = this.trigger(this.events.SUBMIT_BUTTON_PRESSED);\n if (!event.defaultPrevented) {\n this.submitFormAjax();\n }\n });\n\n // Change the text for the save button.\n if (typeof this.config.saveButtonText !== 'undefined' &&\n typeof this.modal.setSaveButtonText !== 'undefined') {\n this.modal.setSaveButtonText(this.config.saveButtonText);\n }\n // Set classes for the save button.\n if (typeof this.config.saveButtonClasses !== 'undefined') {\n this.setSaveButtonClasses(this.config.saveButtonClasses);\n }\n // When Save button is pressed - submit the form.\n this.modal.getRoot().on(ModalEvents.save, (e) => {\n e.preventDefault();\n this.modal.getRoot().find('form').submit();\n });\n\n // When Cancel button is pressed - allow to intercept.\n this.modal.getRoot().on(ModalEvents.cancel, (e) => {\n const event = this.trigger(this.events.CANCEL_BUTTON_PRESSED);\n if (event.defaultPrevented) {\n e.preventDefault();\n }\n });\n this.futureListeners.forEach(args => this.modal.getRoot()[0].addEventListener(...args));\n this.futureListeners = [];\n this.trigger(this.events.LOADED, null, false);\n return this.modal.show();\n })\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}\n */\n trigger(eventName, detail = null, cancelable = true) {\n const e = new CustomEvent(eventName, {detail, cancelable});\n this.modal.getRoot()[0].dispatchEvent(e);\n return e;\n }\n\n /**\n * Add listener for an event\n *\n * @param {array} args\n * @example:\n * const modalForm = new ModalForm(...);\n * dynamicForm.addEventListener(modalForm.events.FORM_SUBMITTED, e => {\n * window.console.log(e.detail);\n * });\n */\n addEventListener(...args) {\n if (!this.modal) {\n this.futureListeners.push(args);\n } else {\n this.modal.getRoot()[0].addEventListener(...args);\n }\n }\n\n /**\n * Get form contents (to be used in ModalForm.setBodyContent())\n *\n * @param {String} formDataString form data in format of a query string\n * @method getBody\n * @private\n * @return {Promise}\n */\n getBody(formDataString) {\n const params = {\n formdata: formDataString,\n form: this.config.formClass\n };\n const pendingPromise = new Pending('core_form/modalform:form_body');\n return Ajax.call([{\n methodname: 'core_form_dynamic_form',\n args: params\n }])[0]\n .then(response => {\n pendingPromise.resolve();\n return {html: response.html, js: Fragment.processCollectedJavascript(response.javascript)};\n });\n }\n\n /**\n * On exception during form processing. Caller may override\n *\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 * Notifies listeners that form dirty state should be reset.\n *\n * @fires event:formSubmittedByJavascript\n */\n notifyResetFormChanges() {\n const form = this.getFormNode();\n FormEvents.notifyFormSubmittedByJavascript(this.getFormNode(), true);\n\n if (!form) {\n return;\n }\n\n FormChangeChecker.resetFormDirtyState(this.getFormNode());\n }\n\n /**\n * Get the form node from the Dialogue.\n *\n * @returns {HTMLFormElement}\n */\n getFormNode() {\n return this.modal.getRoot().find('form')[0];\n }\n\n /**\n * Click on a \"submit\" button that is marked in the form as registerNoSubmitButton()\n *\n * @param {Element} button button that was pressed\n * @fires event:formSubmittedByJavascript\n */\n processNoSubmitButton(button) {\n const form = this.getFormNode();\n if (!form) {\n return;\n }\n\n FormEvents.notifyFormSubmittedByJavascript(form, true);\n\n // Add the button name to the form data and submit it.\n let formData = this.modal.getRoot().find('form').serialize();\n formData = formData + '&' + encodeURIComponent(button.getAttribute('name')) + '=' +\n encodeURIComponent(button.getAttribute('value'));\n\n const bodyContent = this.getBody(formData);\n this.modal.setBodyContent(bodyContent);\n bodyContent.catch(Notification.exception);\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 FormEvents.notifyFormSubmittedByJavascript(this.getFormNode());\n\n // Now the change events have run, see if there are any \"invalid\" form fields.\n /** @var {jQuery} list of elements with errors */\n const invalid = this.modal.getRoot().find('[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.first().focus();\n return false;\n }\n\n return true;\n }\n\n /**\n * Disable buttons during form submission\n */\n disableButtons() {\n this.modal.getFooter().find('[data-action]').attr('disabled', true);\n }\n\n /**\n * Enable buttons after form submission (on validation error)\n */\n enableButtons() {\n this.modal.getFooter().find('[data-action]').removeAttr('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 (!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 formData = this.modal.getRoot().find('form').serialize();\n\n // Now we can continue...\n Ajax.call([{\n methodname: 'core_form_dynamic_form',\n args: {\n formdata: formData,\n form: this.config.formClass\n }\n }])[0]\n .then((response) => {\n if (!response.submitted) {\n // Form was not submitted because validation failed.\n const promise = new Promise(\n resolve => resolve({html: response.html, js: Fragment.processCollectedJavascript(response.javascript)}));\n this.modal.setBodyContent(promise);\n this.enableButtons();\n this.trigger(this.events.SERVER_VALIDATION_ERROR);\n } else {\n // Form was submitted properly. Hide the modal and execute callback.\n const data = JSON.parse(response.data);\n this.notifyResetFormChanges();\n const event = this.trigger(this.events.FORM_SUBMITTED, data);\n if (!event.defaultPrevented) {\n this.modal.hide();\n }\n }\n return null;\n })\n .catch(exception => this.onSubmitError(exception));\n }\n\n /**\n * Set the classes for the 'save' button.\n *\n * @method setSaveButtonClasses\n * @param {(String)} value The 'save' button classes.\n */\n setSaveButtonClasses(value) {\n const button = this.modal.getFooter().find(\"[data-action='save']\");\n if (!button) {\n throw new Error(\"Unable to find the 'save' button\");\n }\n button.removeClass().addClass(value);\n }\n}\n"],"file":"modalform.min.js"} \ No newline at end of file diff --git a/lib/form/amd/src/dynamicform.js b/lib/form/amd/src/dynamicform.js index 982f39b0026..a32eb868142 100644 --- a/lib/form/amd/src/dynamicform.js +++ b/lib/form/amd/src/dynamicform.js @@ -248,7 +248,7 @@ export default class DynamicForm { this.getBody(formData.toString()) .then(this.updateForm) .then(pendingPromise.resolve) - .catch(this.onSubmitError); + .catch(exception => this.onSubmitError(exception)); } /** @@ -371,6 +371,6 @@ export default class DynamicForm { } return null; }) - .catch(this.onSubmitError); + .catch(exception => this.onSubmitError(exception)); } } diff --git a/lib/form/amd/src/modalform.js b/lib/form/amd/src/modalform.js index a9e63c6dd21..fdc399854ef 100644 --- a/lib/form/amd/src/modalform.js +++ b/lib/form/amd/src/modalform.js @@ -385,7 +385,7 @@ export default class ModalForm { } return null; }) - .catch(this.onSubmitError); + .catch(exception => this.onSubmitError(exception)); } /**