diff --git a/lib/form/amd/build/dynamicform.min.js b/lib/form/amd/build/dynamicform.min.js index 00836c7c9cf..3588f799e76 100644 --- a/lib/form/amd/build/dynamicform.min.js +++ b/lib/form/amd/build/dynamicform.min.js @@ -1,3 +1,3 @@ -define("core_form/dynamicform",["exports","core_form/changechecker","core_form/events","core/ajax","core/fragment","core/notification","core/pending","core/templates","core/str"],(function(_exports,FormChangeChecker,FormEvents,_ajax,_fragment,_notification,_pending,_templates,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}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}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,FormChangeChecker=_interopRequireWildcard(FormChangeChecker),FormEvents=_interopRequireWildcard(FormEvents),_ajax=_interopRequireDefault(_ajax),_fragment=_interopRequireDefault(_fragment),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),_templates=_interopRequireDefault(_templates);return _exports.default=class{constructor(container,formClass){var obj,key,value;value={FORM_SUBMITTED:"core_form_dynamicform_formsubmitted",FORM_CANCELLED:"core_form_dynamicform_formcancelled",CLIENT_VALIDATION_ERROR:"core_form_dynamicform_clientvalidationerror",SERVER_VALIDATION_ERROR:"core_form_dynamicform_validationerror",ERROR:"core_form_dynamicform_error",NOSUBMIT_BUTTON_PRESSED:"core_form_dynamicform_nosubmitbutton",SUBMIT_BUTTON_PRESSED:"core_form_dynamicform_submitbutton",CANCEL_BUTTON_PRESSED:"core_form_dynamicform_cancelbutton"},(key="events")in(obj=this)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,this.formClass=formClass,this.container=container,(0,_str.get_strings)([{key:"collapseall",component:"moodle"},{key:"expandall",component:"moodle"}]).catch(_notification.default.exception),this.container.addEventListener("click",(e=>{if(e.target.matches("form input[type=submit][data-cancel]")){e.preventDefault();this.trigger(this.events.CANCEL_BUTTON_PRESSED,e.target).defaultPrevented||this.processCancelButton()}else if(e.target.matches('form input[type=submit][data-no-submit="1"]')){e.preventDefault();this.trigger(this.events.NOSUBMIT_BUTTON_PRESSED,e.target).defaultPrevented||this.processNoSubmitButton(e.target)}})),this.container.addEventListener("submit",(e=>{if(e.target.matches("form")){e.preventDefault();this.trigger(this.events.SUBMIT_BUTTON_PRESSED).defaultPrevented||this.submitFormAjax()}}))}load(){let args=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;const formData=new URLSearchParams(Object.entries(args||{})),pendingPromise=new _pending.default("core_form/dynamicform:load");return this.getBody(formData.toString()).then((resp=>this.updateForm(resp))).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.container.dispatchEvent(e),e}addEventListener(){this.container.addEventListener(...arguments)}getBody(formDataString){return _ajax.default.call([{methodname:"core_form_dynamic_form",args:{formdata:formDataString,form:this.formClass}}])[0].then((response=>({html:response.html,js:_fragment.default.processCollectedJavascript(response.javascript)})))}onSubmitSuccess(response){this.trigger(this.events.FORM_SUBMITTED,response).defaultPrevented||(this.container.innerHTML="")}onSubmitError(exception){this.trigger(this.events.ERROR,exception).defaultPrevented||_notification.default.exception(exception)}processNoSubmitButton(button){const pendingPromise=new _pending.default("core_form/dynamicform:nosubmit"),form=this.getFormNode(),formData=new URLSearchParams([...new FormData(form).entries()]);formData.append(button.getAttribute("name"),button.getAttribute("value")),FormEvents.notifyFormSubmittedByJavascript(form,!0),this.disableButtons(),this.getBody(formData.toString()).then(this.updateForm).then(pendingPromise.resolve).catch((exception=>this.onSubmitError(exception)))}getFormNode(){return this.container.querySelector("form")}notifyResetFormChanges(){FormEvents.notifyFormSubmittedByJavascript(this.getFormNode(),!0),FormChangeChecker.resetFormDirtyState(this.getFormNode())}processCancelButton(){this.notifyResetFormChanges();this.trigger(this.events.FORM_CANCELLED).defaultPrevented||(this.container.innerHTML="")}updateForm(_ref){let{html:html,js:js}=_ref;return _templates.default.replaceNodeContents(this.container,html,js)}validateElements(){FormEvents.notifyFormSubmittedByJavascript(this.getFormNode());const invalid=[...this.container.querySelectorAll('[aria-invalid="true"], .error')];return!invalid.length||(invalid[0].focus(),!1)}disableButtons(){this.container.querySelectorAll('form input[type="submit"]').forEach((el=>el.setAttribute("disabled",!0)))}enableButtons(){this.container.querySelectorAll('form input[type="submit"]').forEach((el=>el.removeAttribute("disabled")))}async submitFormAjax(){if(!await this.validateElements())return void this.trigger(this.events.CLIENT_VALIDATION_ERROR,null,!1);this.disableButtons();const form=this.container.querySelector("form"),formData=new URLSearchParams([...new FormData(form).entries()]);_ajax.default.call([{methodname:"core_form_dynamic_form",args:{formdata:formData.toString(),form:this.formClass}}])[0].then((response=>{if(response.submitted){const data=JSON.parse(response.data);this.enableButtons(),this.notifyResetFormChanges(),this.onSubmitSuccess(data)}else this.updateForm({html:response.html,js:_fragment.default.processCollectedJavascript(response.javascript)}),this.enableButtons(),this.trigger(this.events.SERVER_VALIDATION_ERROR,null,!1);return null})).catch((exception=>this.onSubmitError(exception)))}},_exports.default})); +define("core_form/dynamicform",["exports","core_form/changechecker","core_form/events","core/ajax","core/fragment","core/notification","core/pending","core/templates","core/str"],(function(_exports,FormChangeChecker,FormEvents,_ajax,_fragment,_notification,_pending,_templates,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}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}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,FormChangeChecker=_interopRequireWildcard(FormChangeChecker),FormEvents=_interopRequireWildcard(FormEvents),_ajax=_interopRequireDefault(_ajax),_fragment=_interopRequireDefault(_fragment),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),_templates=_interopRequireDefault(_templates);return _exports.default=class{constructor(container,formClass){var obj,key,value;value={FORM_SUBMITTED:"core_form_dynamicform_formsubmitted",FORM_CANCELLED:"core_form_dynamicform_formcancelled",CLIENT_VALIDATION_ERROR:"core_form_dynamicform_clientvalidationerror",SERVER_VALIDATION_ERROR:"core_form_dynamicform_validationerror",ERROR:"core_form_dynamicform_error",NOSUBMIT_BUTTON_PRESSED:"core_form_dynamicform_nosubmitbutton",SUBMIT_BUTTON_PRESSED:"core_form_dynamicform_submitbutton",CANCEL_BUTTON_PRESSED:"core_form_dynamicform_cancelbutton"},(key="events")in(obj=this)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,this.formClass=formClass,this.container=container,(0,_str.get_strings)([{key:"collapseall",component:"moodle"},{key:"expandall",component:"moodle"}]).catch(_notification.default.exception),this.container.addEventListener("click",(e=>{if(e.target.matches("form input[type=submit][data-cancel]")){e.preventDefault();this.trigger(this.events.CANCEL_BUTTON_PRESSED,e.target).defaultPrevented||this.processCancelButton()}else if(e.target.matches('form input[type=submit][data-no-submit="1"]')){e.preventDefault();this.trigger(this.events.NOSUBMIT_BUTTON_PRESSED,e.target).defaultPrevented||this.processNoSubmitButton(e.target)}})),this.container.addEventListener("submit",(e=>{if(e.target.matches("form")){e.preventDefault();this.trigger(this.events.SUBMIT_BUTTON_PRESSED).defaultPrevented||this.submitFormAjax()}}))}load(){const serialize=function(obj){let prefix=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return[].concat(...Object.entries(obj).map((_ref=>{let[idx,v]=_ref;const k=prefix?prefix+"["+idx+"]":idx;return"object"==typeof v?serialize(v,k):"".concat(k,"=").concat(encodeURIComponent(v))}))).join("&")},formData=serialize((arguments.length>0&&void 0!==arguments[0]?arguments[0]:null)||{}),pendingPromise=new _pending.default("core_form/dynamicform:load");return this.getBody(formData).then((resp=>this.updateForm(resp))).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.container.dispatchEvent(e),e}addEventListener(){this.container.addEventListener(...arguments)}getBody(formDataString){return _ajax.default.call([{methodname:"core_form_dynamic_form",args:{formdata:formDataString,form:this.formClass}}])[0].then((response=>({html:response.html,js:_fragment.default.processCollectedJavascript(response.javascript)})))}onSubmitSuccess(response){this.trigger(this.events.FORM_SUBMITTED,response).defaultPrevented||(this.container.innerHTML="")}onSubmitError(exception){this.trigger(this.events.ERROR,exception).defaultPrevented||_notification.default.exception(exception)}processNoSubmitButton(button){const pendingPromise=new _pending.default("core_form/dynamicform:nosubmit"),form=this.getFormNode(),formData=new URLSearchParams([...new FormData(form).entries()]);formData.append(button.getAttribute("name"),button.getAttribute("value")),FormEvents.notifyFormSubmittedByJavascript(form,!0),this.disableButtons(),this.getBody(formData.toString()).then(this.updateForm).then(pendingPromise.resolve).catch((exception=>this.onSubmitError(exception)))}getFormNode(){return this.container.querySelector("form")}notifyResetFormChanges(){FormEvents.notifyFormSubmittedByJavascript(this.getFormNode(),!0),FormChangeChecker.resetFormDirtyState(this.getFormNode())}processCancelButton(){this.notifyResetFormChanges();this.trigger(this.events.FORM_CANCELLED).defaultPrevented||(this.container.innerHTML="")}updateForm(_ref2){let{html:html,js:js}=_ref2;return _templates.default.replaceNodeContents(this.container,html,js)}validateElements(){FormEvents.notifyFormSubmittedByJavascript(this.getFormNode());const invalid=[...this.container.querySelectorAll('[aria-invalid="true"], .error')];return!invalid.length||(invalid[0].focus(),!1)}disableButtons(){this.container.querySelectorAll('form input[type="submit"]').forEach((el=>el.setAttribute("disabled",!0)))}enableButtons(){this.container.querySelectorAll('form input[type="submit"]').forEach((el=>el.removeAttribute("disabled")))}async submitFormAjax(){if(!await this.validateElements())return void this.trigger(this.events.CLIENT_VALIDATION_ERROR,null,!1);this.disableButtons();const form=this.container.querySelector("form"),formData=new URLSearchParams([...new FormData(form).entries()]);_ajax.default.call([{methodname:"core_form_dynamic_form",args:{formdata:formData.toString(),form:this.formClass}}])[0].then((response=>{if(response.submitted){const data=JSON.parse(response.data);this.enableButtons(),this.notifyResetFormChanges(),this.onSubmitSuccess(data)}else this.updateForm({html:response.html,js:_fragment.default.processCollectedJavascript(response.javascript)}),this.enableButtons(),this.trigger(this.events.SERVER_VALIDATION_ERROR,null,!1);return null})).catch((exception=>this.onSubmitError(exception)))}},_exports.default})); //# sourceMappingURL=dynamicform.min.js.map \ No newline at end of file diff --git a/lib/form/amd/build/dynamicform.min.js.map b/lib/form/amd/build/dynamicform.min.js.map index 52053115a45..2462fa5a058 100644 --- a/lib/form/amd/build/dynamicform.min.js.map +++ b/lib/form/amd/build/dynamicform.min.js.map @@ -1 +1 @@ -{"version":3,"file":"dynamicform.min.js","sources":["../src/dynamicform.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\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"],"names":["constructor","container","formClass","FORM_SUBMITTED","FORM_CANCELLED","CLIENT_VALIDATION_ERROR","SERVER_VALIDATION_ERROR","ERROR","NOSUBMIT_BUTTON_PRESSED","SUBMIT_BUTTON_PRESSED","CANCEL_BUTTON_PRESSED","key","component","catch","Notification","exception","addEventListener","e","target","matches","preventDefault","this","trigger","events","defaultPrevented","processCancelButton","processNoSubmitButton","submitFormAjax","load","args","formData","URLSearchParams","Object","entries","pendingPromise","Pending","getBody","toString","then","resp","updateForm","resolve","eventName","CustomEvent","detail","cancelable","dispatchEvent","formDataString","Ajax","call","methodname","formdata","form","response","html","js","Fragment","processCollectedJavascript","javascript","onSubmitSuccess","innerHTML","onSubmitError","button","getFormNode","FormData","append","getAttribute","FormEvents","notifyFormSubmittedByJavascript","disableButtons","querySelector","notifyResetFormChanges","FormChangeChecker","resetFormDirtyState","Templates","replaceNodeContents","validateElements","invalid","querySelectorAll","length","focus","forEach","el","setAttribute","enableButtons","removeAttribute","submitted","data","JSON","parse"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"dynamicform.min.js","sources":["../src/dynamicform.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\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 serialize = function(obj, prefix = '') {\n return [].concat(...Object.entries(obj).map(([idx, v]) => {\n const k = prefix ? prefix + \"[\" + idx + \"]\" : idx;\n return (typeof v === \"object\") ? serialize(v, k) : `${k}=${encodeURIComponent(v)}`;\n })).join(\"&\");\n };\n const formData = serialize(args || {});\n const pendingPromise = new Pending('core_form/dynamicform:load');\n return this.getBody(formData)\n .then((resp) => this.updateForm(resp))\n .then(pendingPromise.resolve);\n }\n\n /**\n * Triggers a custom event\n *\n * @private\n * @param {String} eventName\n * @param {*} detail\n * @param {Boolean} cancelable\n * @return {CustomEvent}\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"],"names":["constructor","container","formClass","FORM_SUBMITTED","FORM_CANCELLED","CLIENT_VALIDATION_ERROR","SERVER_VALIDATION_ERROR","ERROR","NOSUBMIT_BUTTON_PRESSED","SUBMIT_BUTTON_PRESSED","CANCEL_BUTTON_PRESSED","key","component","catch","Notification","exception","addEventListener","e","target","matches","preventDefault","this","trigger","events","defaultPrevented","processCancelButton","processNoSubmitButton","submitFormAjax","load","serialize","obj","prefix","concat","Object","entries","map","_ref","idx","v","k","encodeURIComponent","join","formData","pendingPromise","Pending","getBody","then","resp","updateForm","resolve","eventName","CustomEvent","detail","cancelable","dispatchEvent","formDataString","Ajax","call","methodname","args","formdata","form","response","html","js","Fragment","processCollectedJavascript","javascript","onSubmitSuccess","innerHTML","onSubmitError","button","getFormNode","URLSearchParams","FormData","append","getAttribute","FormEvents","notifyFormSubmittedByJavascript","disableButtons","toString","querySelector","notifyResetFormChanges","FormChangeChecker","resetFormDirtyState","Templates","replaceNodeContents","validateElements","invalid","querySelectorAll","length","focus","forEach","el","setAttribute","enableButtons","removeAttribute","submitted","data","JSON","parse"],"mappings":"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"} \ 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 1e94ef37179..b802fa59f2d 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/modal_factory","core/notification","core/pending"],(function(_exports,_ajax,FormChangeChecker,FormEvents,_fragment,_modal_events,_modal_factory,_notification,_pending){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}}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),_modal_factory=_interopRequireDefault(_modal_factory),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending);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,type:_modal_factory.default.types.SAVE_CANCEL,large:!0,...this.config.modalConfig||{}},this.config.args=this.config.args||{},this.futureListeners=[]}show(){const pendingPromise=new _pending.default("core_form/modalform:init");return _modal_factory.default.create(this.config.modalConfig).then((modal=>{this.modal=modal;const formParams=new URLSearchParams(Object.entries(this.config.args||{})),bodyContent=this.getBody(formParams.toString());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();FormEvents.notifyFormSubmittedByJavascript(this.getFormNode(),!0),form&&FormChangeChecker.resetFormDirtyState(this.getFormNode())}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 formData=this.modal.getRoot().find("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);this.notifyResetFormChanges();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/modal_factory","core/notification","core/pending"],(function(_exports,_ajax,FormChangeChecker,FormEvents,_fragment,_modal_events,_modal_factory,_notification,_pending){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}}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),_modal_factory=_interopRequireDefault(_modal_factory),_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending);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,type:_modal_factory.default.types.SAVE_CANCEL,large:!0,...this.config.modalConfig||{}},this.config.args=this.config.args||{},this.futureListeners=[]}show(){const pendingPromise=new _pending.default("core_form/modalform:init"),serialize=function(obj){let prefix=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"";return[].concat(...Object.entries(obj).map((_ref=>{let[idx,v]=_ref;const k=prefix?prefix+"["+idx+"]":idx;return"object"==typeof v?serialize(v,k):"".concat(k,"=").concat(encodeURIComponent(v))}))).join("&")};return _modal_factory.default.create(this.config.modalConfig).then((modal=>{this.modal=modal;const formParams=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();FormEvents.notifyFormSubmittedByJavascript(this.getFormNode(),!0),form&&FormChangeChecker.resetFormDirtyState(this.getFormNode())}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 formData=this.modal.getRoot().find("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);this.notifyResetFormChanges();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})); //# 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 bf60e2ea200..bee8c458b58 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 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"],"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","type","ModalFactory","types","SAVE_CANCEL","large","this","args","futureListeners","show","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","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","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 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 const serialize = function(obj, prefix = '') {\n return [].concat(...Object.entries(obj).map(([idx, v]) => {\n const k = prefix ? prefix + \"[\" + idx + \"]\" : idx;\n return (typeof v === \"object\") ? serialize(v, k) : `${k}=${encodeURIComponent(v)}`;\n })).join(\"&\");\n };\n\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 = 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 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"],"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","type","ModalFactory","types","SAVE_CANCEL","large","this","args","futureListeners","show","pendingPromise","Pending","serialize","obj","prefix","concat","Object","entries","map","_ref","idx","v","k","encodeURIComponent","join","create","then","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","getAttribute","validateElements","invalid","length","first","disableButtons","getFooter","attr","enableButtons","removeAttr","submitted","data","JSON","parse","hide","promise","Promise","value","Error","removeClass"],"mappings":"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"} \ No newline at end of file diff --git a/lib/form/amd/src/dynamicform.js b/lib/form/amd/src/dynamicform.js index a32eb868142..24a7857328f 100644 --- a/lib/form/amd/src/dynamicform.js +++ b/lib/form/amd/src/dynamicform.js @@ -136,9 +136,15 @@ export default class DynamicForm { * @public */ load(args = null) { - const formData = new URLSearchParams(Object.entries(args || {})); + const serialize = function(obj, prefix = '') { + return [].concat(...Object.entries(obj).map(([idx, v]) => { + const k = prefix ? prefix + "[" + idx + "]" : idx; + return (typeof v === "object") ? serialize(v, k) : `${k}=${encodeURIComponent(v)}`; + })).join("&"); + }; + const formData = serialize(args || {}); const pendingPromise = new Pending('core_form/dynamicform:load'); - return this.getBody(formData.toString()) + return this.getBody(formData) .then((resp) => this.updateForm(resp)) .then(pendingPromise.resolve); } diff --git a/lib/form/amd/src/modalform.js b/lib/form/amd/src/modalform.js index fdc399854ef..63fcca890c4 100644 --- a/lib/form/amd/src/modalform.js +++ b/lib/form/amd/src/modalform.js @@ -115,6 +115,13 @@ export default class ModalForm { */ show() { const pendingPromise = new Pending('core_form/modalform:init'); + const serialize = function(obj, prefix = '') { + return [].concat(...Object.entries(obj).map(([idx, v]) => { + const k = prefix ? prefix + "[" + idx + "]" : idx; + return (typeof v === "object") ? serialize(v, k) : `${k}=${encodeURIComponent(v)}`; + })).join("&"); + }; + return ModalFactory.create(this.config.modalConfig) .then((modal) => { this.modal = modal; @@ -122,8 +129,8 @@ export default class ModalForm { // Retrieve the form and set the modal body. We can not set the body in the modalConfig, // we need to make sure that the modal already exists when we render the form. Some form elements // such as date_selector inspect the existing elements on the page to find the highest z-index. - const formParams = new URLSearchParams(Object.entries(this.config.args || {})); - const bodyContent = this.getBody(formParams.toString()); + const formParams = serialize(this.config.args || {}); + const bodyContent = this.getBody(formParams); this.modal.setBodyContent(bodyContent); bodyContent.catch(Notification.exception);