From 4bb42cc3629fc3c0bc1a4c273b393f23f196581d Mon Sep 17 00:00:00 2001 From: sam marshall Date: Mon, 15 Jan 2024 17:10:32 +0000 Subject: [PATCH] MDL-80608 core_form: Fire modal form event for errors in definition The ERROR event is defined as being fired if an exception occurs while contacting the server. This change ensures it is fired for exceptions in the form definition AJAX request, not just form submission. Additionally, if such an error occurs in submission, the form buttons were left in disabled state. This change makes them enabled again. --- lib/form/amd/build/modalform.min.js | 2 +- lib/form/amd/build/modalform.min.js.map | 2 +- lib/form/amd/src/modalform.js | 10 +++++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/form/amd/build/modalform.min.js b/lib/form/amd/build/modalform.min.js index ed1377728c235..875f7e92ae4c7 100644 --- a/lib/form/amd/build/modalform.min.js +++ b/lib/form/amd/build/modalform.min.js @@ -1,3 +1,3 @@ -define("core_form/modalform",["exports","core/ajax","core_form/changechecker","core_form/events","core/fragment","core/modal_events","core/notification","core/pending","./util"],(function(_exports,_ajax,FormChangeChecker,FormEvents,_fragment,_modal_events,_notification,_pending,_util){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_ajax=_interopRequireDefault(_ajax),FormChangeChecker=_interopRequireWildcard(FormChangeChecker),FormEvents=_interopRequireWildcard(FormEvents),_fragment=_interopRequireDefault(_fragment),_modal_events=_interopRequireDefault(_modal_events),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending);var _systemImportTransformerGlobalIdentifier="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}return _exports.default=class{constructor(config){var obj,key,value;value={FORM_SUBMITTED:"core_form_modalform_formsubmitted",FORM_CANCELLED:"core_form_modalform_formcancelled",CLIENT_VALIDATION_ERROR:"core_form_modalform_clientvalidationerror",SERVER_VALIDATION_ERROR:"core_form_modalform_validationerror",ERROR:"core_form_modalform_error",NOSUBMIT_BUTTON_PRESSED:"core_form_modalform_nosubmitbutton",SUBMIT_BUTTON_PRESSED:"core_form_modalform_submitbutton",CANCEL_BUTTON_PRESSED:"core_form_modalform_cancelbutton",LOADED:"core_form_modalform_loaded"},(key="events")in(obj=this)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,this.modal=null,this.config=config,this.config.modalConfig={removeOnClose:!0,large:!0,...this.config.modalConfig||{}},this.config.args=this.config.args||{},this.futureListeners=[]}getModalModule(){if(!this.config.moduleName&&this.config.modalConfig.type&&"SAVE_CANCEL"!==this.config.modalConfig.type)return window.console.warn("Passing config.modalConfig.type to ModalForm has been deprecated since Moodle 4.3. Please pass config.modalName instead with the full module name."),("function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require(["core/modal_factory"],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require("core/modal_factory")):Promise.resolve(_systemImportTransformerGlobalIdentifier["core/modal_factory"])).then((ModalFactory=>ModalFactory.create(this.config.modalConfig)));{var _this$config$moduleNa;const moduleName=null!==(_this$config$moduleNa=this.config.moduleName)&&void 0!==_this$config$moduleNa?_this$config$moduleNa:"core/modal_save_cancel";return("function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require([moduleName],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require(moduleName)):Promise.resolve(_systemImportTransformerGlobalIdentifier[moduleName])).then((module=>module.create(this.config.modalConfig)))}}show(){const pendingPromise=new _pending.default("core_form/modalform:init");return this.getModalModule().then((modal=>{this.modal=modal;const formParams=(0,_util.serialize)(this.config.args||{}),bodyContent=this.getBody(formParams);return this.modal.setBodyContent(bodyContent),bodyContent.catch(_notification.default.exception),this.modal.getRoot().on(_modal_events.default.hidden,(()=>{this.notifyResetFormChanges(),this.modal.destroy(),this.config.returnFocus&&this.config.returnFocus.focus()})),this.modal.getModal().addClass("modal-form-dialogue"),this.modal.getRoot().on("click","form input[type=submit][data-no-submit]",(e=>{e.preventDefault();this.trigger(this.events.NOSUBMIT_BUTTON_PRESSED,e.target).defaultPrevented||this.processNoSubmitButton(e.target)})),this.modal.getRoot().on("submit","form",(e=>{e.preventDefault();this.trigger(this.events.SUBMIT_BUTTON_PRESSED).defaultPrevented||this.submitFormAjax()})),void 0!==this.config.saveButtonText&&void 0!==this.modal.setSaveButtonText&&this.modal.setSaveButtonText(this.config.saveButtonText),void 0!==this.config.saveButtonClasses&&this.setSaveButtonClasses(this.config.saveButtonClasses),this.modal.getRoot().on(_modal_events.default.save,(e=>{e.preventDefault(),this.modal.getRoot().find("form").submit()})),this.modal.getRoot().on(_modal_events.default.cancel,(e=>{this.trigger(this.events.CANCEL_BUTTON_PRESSED).defaultPrevented&&e.preventDefault()})),this.futureListeners.forEach((args=>this.modal.getRoot()[0].addEventListener(...args))),this.futureListeners=[],this.trigger(this.events.LOADED,null,!1),this.modal.show()})).then(pendingPromise.resolve)}trigger(eventName){const e=new CustomEvent(eventName,{detail:arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,cancelable:!(arguments.length>2&&void 0!==arguments[2])||arguments[2]});return this.modal.getRoot()[0].dispatchEvent(e),e}addEventListener(){for(var _len=arguments.length,args=new Array(_len),_key=0;_key<_len;_key++)args[_key]=arguments[_key];this.modal?this.modal.getRoot()[0].addEventListener(...args):this.futureListeners.push(args)}getBody(formDataString){const params={formdata:formDataString,form:this.config.formClass},pendingPromise=new _pending.default("core_form/modalform:form_body");return _ajax.default.call([{methodname:"core_form_dynamic_form",args:params}])[0].then((response=>(pendingPromise.resolve(),{html:response.html,js:_fragment.default.processCollectedJavascript(response.javascript)})))}onSubmitError(exception){this.trigger(this.events.ERROR,exception).defaultPrevented||_notification.default.exception(exception)}notifyResetFormChanges(){const form=this.getFormNode();form&&(FormEvents.notifyFormSubmittedByJavascript(form,!0),FormChangeChecker.resetFormDirtyState(form))}getFormNode(){return this.modal.getRoot().find("form")[0]}processNoSubmitButton(button){const form=this.getFormNode();if(!form)return;FormEvents.notifyFormSubmittedByJavascript(form,!0);let formData=this.modal.getRoot().find("form").serialize();formData=formData+"&"+encodeURIComponent(button.getAttribute("name"))+"="+encodeURIComponent(button.getAttribute("value"));const bodyContent=this.getBody(formData);this.modal.setBodyContent(bodyContent),bodyContent.catch(_notification.default.exception)}validateElements(){FormEvents.notifyFormSubmittedByJavascript(this.getFormNode());const invalid=this.modal.getRoot().find('[aria-invalid="true"], .error');return!invalid.length||(invalid.first().focus(),!1)}disableButtons(){this.modal.getFooter().find("[data-action]").attr("disabled",!0)}enableButtons(){this.modal.getFooter().find("[data-action]").removeAttr("disabled")}async submitFormAjax(){if(!this.validateElements())return void this.trigger(this.events.CLIENT_VALIDATION_ERROR,null,!1);this.disableButtons();const form=this.modal.getRoot().find("form"),formData=form.serialize();_ajax.default.call([{methodname:"core_form_dynamic_form",args:{formdata:formData,form:this.config.formClass}}])[0].then((response=>{if(response.submitted){const data=JSON.parse(response.data);FormChangeChecker.markFormSubmitted(form[0]);this.trigger(this.events.FORM_SUBMITTED,data).defaultPrevented||this.modal.hide()}else{const promise=new Promise((resolve=>resolve({html:response.html,js:_fragment.default.processCollectedJavascript(response.javascript)})));this.modal.setBodyContent(promise),this.enableButtons(),this.trigger(this.events.SERVER_VALIDATION_ERROR)}return null})).catch((exception=>this.onSubmitError(exception)))}setSaveButtonClasses(value){const button=this.modal.getFooter().find("[data-action='save']");if(!button)throw new Error("Unable to find the 'save' button");button.removeClass().addClass(value)}},_exports.default})); +define("core_form/modalform",["exports","core/ajax","core_form/changechecker","core_form/events","core/fragment","core/modal_events","core/notification","core/pending","./util"],(function(_exports,_ajax,FormChangeChecker,FormEvents,_fragment,_modal_events,_notification,_pending,_util){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_ajax=_interopRequireDefault(_ajax),FormChangeChecker=_interopRequireWildcard(FormChangeChecker),FormEvents=_interopRequireWildcard(FormEvents),_fragment=_interopRequireDefault(_fragment),_modal_events=_interopRequireDefault(_modal_events),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending);var _systemImportTransformerGlobalIdentifier="undefined"!=typeof window?window:"undefined"!=typeof self?self:"undefined"!=typeof global?global:{};function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}return _exports.default=class{constructor(config){var obj,key,value;value={FORM_SUBMITTED:"core_form_modalform_formsubmitted",FORM_CANCELLED:"core_form_modalform_formcancelled",CLIENT_VALIDATION_ERROR:"core_form_modalform_clientvalidationerror",SERVER_VALIDATION_ERROR:"core_form_modalform_validationerror",ERROR:"core_form_modalform_error",NOSUBMIT_BUTTON_PRESSED:"core_form_modalform_nosubmitbutton",SUBMIT_BUTTON_PRESSED:"core_form_modalform_submitbutton",CANCEL_BUTTON_PRESSED:"core_form_modalform_cancelbutton",LOADED:"core_form_modalform_loaded"},(key="events")in(obj=this)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,this.modal=null,this.config=config,this.config.modalConfig={removeOnClose:!0,large:!0,...this.config.modalConfig||{}},this.config.args=this.config.args||{},this.futureListeners=[]}getModalModule(){if(!this.config.moduleName&&this.config.modalConfig.type&&"SAVE_CANCEL"!==this.config.modalConfig.type)return window.console.warn("Passing config.modalConfig.type to ModalForm has been deprecated since Moodle 4.3. Please pass config.modalName instead with the full module name."),("function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require(["core/modal_factory"],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require("core/modal_factory")):Promise.resolve(_systemImportTransformerGlobalIdentifier["core/modal_factory"])).then((ModalFactory=>ModalFactory.create(this.config.modalConfig)));{var _this$config$moduleNa;const moduleName=null!==(_this$config$moduleNa=this.config.moduleName)&&void 0!==_this$config$moduleNa?_this$config$moduleNa:"core/modal_save_cancel";return("function"==typeof _systemImportTransformerGlobalIdentifier.define&&_systemImportTransformerGlobalIdentifier.define.amd?new Promise((function(resolve,reject){_systemImportTransformerGlobalIdentifier.require([moduleName],resolve,reject)})):"undefined"!=typeof module&&module.exports&&"undefined"!=typeof require||"undefined"!=typeof module&&module.component&&_systemImportTransformerGlobalIdentifier.require&&"component"===_systemImportTransformerGlobalIdentifier.require.loader?Promise.resolve(require(moduleName)):Promise.resolve(_systemImportTransformerGlobalIdentifier[moduleName])).then((module=>module.create(this.config.modalConfig)))}}show(){const pendingPromise=new _pending.default("core_form/modalform:init");return this.getModalModule().then((modal=>{this.modal=modal;const formParams=(0,_util.serialize)(this.config.args||{}),bodyContent=this.getBody(formParams);return this.modal.setBodyContent(bodyContent),bodyContent.catch(_notification.default.exception),this.modal.getRoot().on(_modal_events.default.hidden,(()=>{this.notifyResetFormChanges(),this.modal.destroy(),this.config.returnFocus&&this.config.returnFocus.focus()})),this.modal.getModal().addClass("modal-form-dialogue"),this.modal.getRoot().on("click","form input[type=submit][data-no-submit]",(e=>{e.preventDefault();this.trigger(this.events.NOSUBMIT_BUTTON_PRESSED,e.target).defaultPrevented||this.processNoSubmitButton(e.target)})),this.modal.getRoot().on("submit","form",(e=>{e.preventDefault();this.trigger(this.events.SUBMIT_BUTTON_PRESSED).defaultPrevented||this.submitFormAjax()})),void 0!==this.config.saveButtonText&&void 0!==this.modal.setSaveButtonText&&this.modal.setSaveButtonText(this.config.saveButtonText),void 0!==this.config.saveButtonClasses&&this.setSaveButtonClasses(this.config.saveButtonClasses),this.modal.getRoot().on(_modal_events.default.save,(e=>{e.preventDefault(),this.modal.getRoot().find("form").submit()})),this.modal.getRoot().on(_modal_events.default.cancel,(e=>{this.trigger(this.events.CANCEL_BUTTON_PRESSED).defaultPrevented&&e.preventDefault()})),this.futureListeners.forEach((args=>this.modal.getRoot()[0].addEventListener(...args))),this.futureListeners=[],this.trigger(this.events.LOADED,null,!1),this.modal.show()})).then(pendingPromise.resolve)}trigger(eventName){const e=new CustomEvent(eventName,{detail:arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,cancelable:!(arguments.length>2&&void 0!==arguments[2])||arguments[2]});return this.modal.getRoot()[0].dispatchEvent(e),e}addEventListener(){for(var _len=arguments.length,args=new Array(_len),_key=0;_key<_len;_key++)args[_key]=arguments[_key];this.modal?this.modal.getRoot()[0].addEventListener(...args):this.futureListeners.push(args)}getBody(formDataString){const params={formdata:formDataString,form:this.config.formClass},pendingPromise=new _pending.default("core_form/modalform:form_body");return _ajax.default.call([{methodname:"core_form_dynamic_form",args:params}])[0].then((response=>(pendingPromise.resolve(),{html:response.html,js:_fragment.default.processCollectedJavascript(response.javascript)}))).catch((exception=>this.onSubmitError(exception)))}onSubmitError(exception){this.trigger(this.events.ERROR,exception).defaultPrevented||_notification.default.exception(exception)}notifyResetFormChanges(){const form=this.getFormNode();form&&(FormEvents.notifyFormSubmittedByJavascript(form,!0),FormChangeChecker.resetFormDirtyState(form))}getFormNode(){return this.modal.getRoot().find("form")[0]}processNoSubmitButton(button){const form=this.getFormNode();if(!form)return;FormEvents.notifyFormSubmittedByJavascript(form,!0);let formData=this.modal.getRoot().find("form").serialize();formData=formData+"&"+encodeURIComponent(button.getAttribute("name"))+"="+encodeURIComponent(button.getAttribute("value"));const bodyContent=this.getBody(formData);this.modal.setBodyContent(bodyContent),bodyContent.catch(_notification.default.exception)}validateElements(){FormEvents.notifyFormSubmittedByJavascript(this.getFormNode());const invalid=this.modal.getRoot().find('[aria-invalid="true"], .error');return!invalid.length||(invalid.first().focus(),!1)}disableButtons(){this.modal.getFooter().find("[data-action]").attr("disabled",!0)}enableButtons(){this.modal.getFooter().find("[data-action]").removeAttr("disabled")}async submitFormAjax(){if(!this.validateElements())return void this.trigger(this.events.CLIENT_VALIDATION_ERROR,null,!1);this.disableButtons();const form=this.modal.getRoot().find("form"),formData=form.serialize();_ajax.default.call([{methodname:"core_form_dynamic_form",args:{formdata:formData,form:this.config.formClass}}])[0].then((response=>{if(response.submitted){const data=JSON.parse(response.data);FormChangeChecker.markFormSubmitted(form[0]);this.trigger(this.events.FORM_SUBMITTED,data).defaultPrevented||this.modal.hide()}else{const promise=new Promise((resolve=>resolve({html:response.html,js:_fragment.default.processCollectedJavascript(response.javascript)})));this.modal.setBodyContent(promise),this.enableButtons(),this.trigger(this.events.SERVER_VALIDATION_ERROR)}return null})).catch((exception=>{this.enableButtons(),this.onSubmitError(exception)}))}setSaveButtonClasses(value){const button=this.modal.getFooter().find("[data-action='save']");if(!button)throw new Error("Unable to find the 'save' button");button.removeClass().addClass(value)}},_exports.default})); //# sourceMappingURL=modalform.min.js.map \ No newline at end of file diff --git a/lib/form/amd/build/modalform.min.js.map b/lib/form/amd/build/modalform.min.js.map index fb2cc7aea86aa..a6d1e5bca2774 100644 --- a/lib/form/amd/build/modalform.min.js.map +++ b/lib/form/amd/build/modalform.min.js.map @@ -1 +1 @@ -{"version":3,"file":"modalform.min.js","sources":["../src/modalform.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 .\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 Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {serialize} from './util';\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 {String} config.moduleName module name to use if different to core/modal_save_cancel (optional)\n * @paramy {Object} config.modalConfig modal config - title, header, footer, etc.\n * Default: {removeOnClose: true, large: true}\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 large: true,\n ...(this.config.modalConfig || {}),\n };\n this.config.args = this.config.args || {};\n this.futureListeners = [];\n }\n\n /**\n * Loads the modal module and creates an instance\n *\n * @returns {Promise}\n */\n getModalModule() {\n if (!this.config.moduleName && this.config.modalConfig.type && this.config.modalConfig.type !== 'SAVE_CANCEL') {\n // Legacy loader for plugins that were not updated with Moodle 4.3 changes.\n window.console.warn(\n 'Passing config.modalConfig.type to ModalForm has been deprecated since Moodle 4.3. ' +\n 'Please pass config.modalName instead with the full module name.',\n );\n return import('core/modal_factory')\n .then((ModalFactory) => ModalFactory.create(this.config.modalConfig));\n } else {\n // New loader for Moodle 4.3 and above.\n const moduleName = this.config.moduleName ?? 'core/modal_save_cancel';\n return import(moduleName)\n .then((module) => module.create(this.config.modalConfig));\n }\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\n return this.getModalModule()\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 = serialize(this.config.args || {});\n const bodyContent = this.getBody(formParams);\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 if (!form) {\n return;\n }\n\n FormEvents.notifyFormSubmittedByJavascript(form, true);\n\n FormChangeChecker.resetFormDirtyState(form);\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 form = this.modal.getRoot().find('form');\n const formData = 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 FormChangeChecker.markFormSubmitted(form[0]);\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"],"names":["constructor","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","large","this","args","futureListeners","getModalModule","moduleName","type","window","console","warn","then","ModalFactory","create","module","show","pendingPromise","Pending","formParams","bodyContent","getBody","setBodyContent","catch","Notification","exception","getRoot","on","ModalEvents","hidden","notifyResetFormChanges","destroy","returnFocus","focus","getModal","addClass","e","preventDefault","trigger","events","target","defaultPrevented","processNoSubmitButton","submitFormAjax","saveButtonText","setSaveButtonText","saveButtonClasses","setSaveButtonClasses","save","find","submit","cancel","forEach","addEventListener","resolve","eventName","CustomEvent","detail","cancelable","dispatchEvent","push","formDataString","params","formdata","form","formClass","Ajax","call","methodname","response","html","js","Fragment","processCollectedJavascript","javascript","onSubmitError","getFormNode","FormEvents","notifyFormSubmittedByJavascript","FormChangeChecker","resetFormDirtyState","button","formData","serialize","encodeURIComponent","getAttribute","validateElements","invalid","length","first","disableButtons","getFooter","attr","enableButtons","removeAttr","submitted","data","JSON","parse","markFormSubmitted","hide","promise","Promise","value","Error","removeClass"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"modalform.min.js","sources":["../src/modalform.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 .\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 Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {serialize} from './util';\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 {String} config.moduleName module name to use if different to core/modal_save_cancel (optional)\n * @paramy {Object} config.modalConfig modal config - title, header, footer, etc.\n * Default: {removeOnClose: true, large: true}\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 large: true,\n ...(this.config.modalConfig || {}),\n };\n this.config.args = this.config.args || {};\n this.futureListeners = [];\n }\n\n /**\n * Loads the modal module and creates an instance\n *\n * @returns {Promise}\n */\n getModalModule() {\n if (!this.config.moduleName && this.config.modalConfig.type && this.config.modalConfig.type !== 'SAVE_CANCEL') {\n // Legacy loader for plugins that were not updated with Moodle 4.3 changes.\n window.console.warn(\n 'Passing config.modalConfig.type to ModalForm has been deprecated since Moodle 4.3. ' +\n 'Please pass config.modalName instead with the full module name.',\n );\n return import('core/modal_factory')\n .then((ModalFactory) => ModalFactory.create(this.config.modalConfig));\n } else {\n // New loader for Moodle 4.3 and above.\n const moduleName = this.config.moduleName ?? 'core/modal_save_cancel';\n return import(moduleName)\n .then((module) => module.create(this.config.modalConfig));\n }\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\n return this.getModalModule()\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 = serialize(this.config.args || {});\n const bodyContent = this.getBody(formParams);\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 .catch(exception => this.onSubmitError(exception));\n }\n\n /**\n * On exception during form processing or initial rendering. 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 if (!form) {\n return;\n }\n\n FormEvents.notifyFormSubmittedByJavascript(form, true);\n\n FormChangeChecker.resetFormDirtyState(form);\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 form = this.modal.getRoot().find('form');\n const formData = 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 FormChangeChecker.markFormSubmitted(form[0]);\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 => {\n this.enableButtons();\n this.onSubmitError(exception);\n });\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"],"names":["constructor","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","large","this","args","futureListeners","getModalModule","moduleName","type","window","console","warn","then","ModalFactory","create","module","show","pendingPromise","Pending","formParams","bodyContent","getBody","setBodyContent","catch","Notification","exception","getRoot","on","ModalEvents","hidden","notifyResetFormChanges","destroy","returnFocus","focus","getModal","addClass","e","preventDefault","trigger","events","target","defaultPrevented","processNoSubmitButton","submitFormAjax","saveButtonText","setSaveButtonText","saveButtonClasses","setSaveButtonClasses","save","find","submit","cancel","forEach","addEventListener","resolve","eventName","CustomEvent","detail","cancelable","dispatchEvent","push","formDataString","params","formdata","form","formClass","Ajax","call","methodname","response","html","js","Fragment","processCollectedJavascript","javascript","onSubmitError","getFormNode","FormEvents","notifyFormSubmittedByJavascript","FormChangeChecker","resetFormDirtyState","button","formData","serialize","encodeURIComponent","getAttribute","validateElements","invalid","length","first","disableButtons","getFooter","attr","enableButtons","removeAttr","submitted","data","JSON","parse","markFormSubmitted","hide","promise","Promise","value","Error","removeClass"],"mappings":"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"} \ No newline at end of file diff --git a/lib/form/amd/src/modalform.js b/lib/form/amd/src/modalform.js index 5f00b6d30c2dd..c99aca4cd90ac 100644 --- a/lib/form/amd/src/modalform.js +++ b/lib/form/amd/src/modalform.js @@ -266,11 +266,12 @@ export default class ModalForm { .then(response => { pendingPromise.resolve(); return {html: response.html, js: Fragment.processCollectedJavascript(response.javascript)}; - }); + }) + .catch(exception => this.onSubmitError(exception)); } /** - * On exception during form processing. Caller may override + * On exception during form processing or initial rendering. Caller may override. * * @param {Object} exception */ @@ -409,7 +410,10 @@ export default class ModalForm { } return null; }) - .catch(exception => this.onSubmitError(exception)); + .catch(exception => { + this.enableButtons(); + this.onSubmitError(exception); + }); } /**