diff --git a/lib/form/amd/build/form.min.js b/lib/form/amd/build/form.min.js index 82a21746e8547..508b38c1a9625 100644 --- a/lib/form/amd/build/form.min.js +++ b/lib/form/amd/build/form.min.js @@ -1,3 +1,3 @@ -define("core_form/form",["exports","./changechecker","./submit","./form/rules","./form/dom"],(function(_exports,FormChangeChecker,Submit,_rules,MutateDom){var 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}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,FormChangeChecker=_interopRequireWildcard(FormChangeChecker),Submit=_interopRequireWildcard(Submit),_rules=(obj=_rules)&&obj.__esModule?obj:{default:obj},MutateDom=_interopRequireWildcard(MutateDom);class Form{constructor(formID,dependencies){_defineProperty(this,"form",void 0),_defineProperty(this,"dependencies",void 0),this.form=document.querySelector("#".concat(formID)),this.dependencies=this.getDependencyMapper(dependencies),this.rules=new _rules.default(this),this.applyInitialState(),this.registerEventListeners(),FormChangeChecker.watchForm(this.form)}applyInitialState(){[...this.form.elements].forEach((element=>{this.dependencies.has(element.name)&&this.domDispatch(this.displayMapPrune(this.dispatchDependencyRules(element)))}))}registerEventListeners(){this.form.addEventListener("change",(e=>{"submit"===e.target.type&&(FormChangeChecker.resetFormDirtyState(this.form),Submit.init(e.target.id)),"reset"===e.target.type&&(FormChangeChecker.resetFormDirtyState(this.form),this.form.reset()),this.dependencies.has(e.target.name)&&(FormChangeChecker.markFormChangedFromNode(e.target),this.domDispatch(this.displayMapPrune(this.dispatchDependencyRules(e.target))))}))}dispatchDependencyRules(target){const displayMap=this.mapTemplate();return this.dependencies.get(target.name).forEach(((dependants,ruleName)=>{(this.rules[ruleName]?this.rules[ruleName](target):this.rules.neq(target)).forEach(((nodeNames,displayOption)=>{displayMap.get(displayOption).push(...nodeNames)}))})),displayMap}displayMapPrune(displayMap){const removeunlockifhidden=displayMap.get("unlock").filter((x=>!displayMap.get("hide").toString().includes(x.toString())));displayMap.set("unlock",removeunlockifhidden);for(const[key,value]of displayMap)0===value.length&&displayMap.delete(key);return displayMap}getDependantsOfType(element,type){var _this$dependencies$ge;return"undefined"!==this.dependencies.get(element)&&null!==(_this$dependencies$ge=this.dependencies.get(element).get(type))&&void 0!==_this$dependencies$ge?_this$dependencies$ge:[]}domDispatch(elNamesMap){elNamesMap.forEach(((elements,domUpdateOpt)=>{if(!MutateDom[domUpdateOpt])return;this.elementNamesToDomNodes(elements).forEach((node=>{null!==node&&MutateDom[domUpdateOpt](node)}))}))}elementNamesToDomNodes(elementNames){return elementNames.map((element=>element.map((element2=>this.form.elements.namedItem(element2)))))}getDependencyMapper(dependencies){const elementMap=new Map(Object.entries(dependencies));return elementMap.forEach(((elementrules,key)=>{const ruleMap=new Map(Object.entries(elementrules));ruleMap.forEach(((ruleComparisons,key)=>{const hideDefine=new Map(Object.entries(ruleComparisons));hideDefine.forEach(((action,compVal)=>{Array.isArray(action)&&(action={...action}),hideDefine.set(compVal,action)})),ruleMap.set(key,hideDefine)})),elementMap.set(key,ruleMap)})),elementMap}mapTemplate(){return new Map([["hide",[]],["show",[]],["lock",[]],["unlock",[]]])}static init(formID,dependencies){return new Form(formID,dependencies)}}return _exports.default=Form,_exports.default})); +define("core_form/form",["exports","./changechecker","./submit","./form/rules","./form/dom","core/pending"],(function(_exports,FormChangeChecker,Submit,_rules,MutateDom,_pending){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}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,FormChangeChecker=_interopRequireWildcard(FormChangeChecker),Submit=_interopRequireWildcard(Submit),_rules=_interopRequireDefault(_rules),MutateDom=_interopRequireWildcard(MutateDom),_pending=_interopRequireDefault(_pending);class Form{constructor(formID,dependencies){_defineProperty(this,"form",void 0),_defineProperty(this,"dependencies",void 0),_defineProperty(this,"editors",new Map),this.form=document.querySelector("#".concat(formID)),this.dependencies=this.getDependencyMapper(dependencies),this.isEditor(),this.rules=new _rules.default(this),this.applyInitialState(),this.registerEventListeners(),FormChangeChecker.watchForm(this.form)}applyInitialState(){[...this.form.elements].forEach((element=>{this.dependencies.has(element.name)&&this.domDispatch(this.displayMapPrune(this.dispatchDependencyRules(element)))}))}registerEventListeners(){this.form.addEventListener("change",(async e=>{if("submit"===e.target.type&&(FormChangeChecker.resetFormDirtyState(this.form),Submit.init(e.target.id)),"reset"===e.target.type&&(FormChangeChecker.resetFormDirtyState(this.form),this.form.reset()),this.dependencies.has(e.target.name)){FormChangeChecker.markFormChangedFromNode(e.target);const pendingPromise=new _pending.default("core/form:update");await this.domDispatch(this.displayMapPrune(this.dispatchDependencyRules(e.target))),pendingPromise.resolve()}}))}dispatchDependencyRules(target){const displayMap=this.mapTemplate();return this.dependencies.get(target.name).forEach(((dependants,ruleName)=>{(this.rules[ruleName]?this.rules[ruleName](target):this.rules.neq(target)).forEach(((nodeNames,displayOption)=>{displayMap.get(displayOption).push(...nodeNames)}))})),displayMap}displayMapPrune(displayMap){const removeunlockifhidden=displayMap.get("unlock").filter((x=>!displayMap.get("hide").toString().includes(x.toString())));displayMap.set("unlock",removeunlockifhidden);for(const[key,value]of displayMap)0===value.length&&displayMap.delete(key);return displayMap}getDependantsOfType(element,type){var _this$dependencies$ge;return"undefined"!==this.dependencies.get(element)&&null!==(_this$dependencies$ge=this.dependencies.get(element).get(type))&&void 0!==_this$dependencies$ge?_this$dependencies$ge:[]}domDispatch(elNamesMap){window.console.log("elNamesMap",elNamesMap),elNamesMap.forEach(((elements,domUpdateOpt)=>{if(!MutateDom[domUpdateOpt])return;this.elementNamesToDomNodes(elements).forEach((node=>{null!==node&&MutateDom[domUpdateOpt](node)}))}))}elementNamesToDomNodes(elementNames){return elementNames.map((element=>element.map((element2=>this.isEditor(element2)?this.form.elements.namedItem("".concat(element2,"[text]")):this.form.elements.namedItem(element2)))))}isEditor(){let elementName=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"";if(0===this.editors.size){const fEditors=this.form.querySelectorAll('[data-fieldtype="editor"] textarea');Array.from(fEditors).forEach((node=>{this.editors.set(node.name,!0)}))}return this.editors.get("".concat(elementName,"[text]"))||!1}getDependencyMapper(dependencies){const elementMap=new Map(Object.entries(dependencies));return elementMap.forEach(((elementrules,key)=>{const ruleMap=new Map(Object.entries(elementrules));ruleMap.forEach(((ruleComparisons,key)=>{const hideDefine=new Map(Object.entries(ruleComparisons));hideDefine.forEach(((action,compVal)=>{Array.isArray(action)&&(action={...action}),hideDefine.set(compVal,action)})),ruleMap.set(key,hideDefine)})),elementMap.set(key,ruleMap)})),elementMap}mapTemplate(){return new Map([["hide",[]],["show",[]],["lock",[]],["unlock",[]]])}static init(formID,dependencies){return new Form(formID,dependencies)}}return _exports.default=Form,_exports.default})); //# sourceMappingURL=form.min.js.map \ No newline at end of file diff --git a/lib/form/amd/build/form.min.js.map b/lib/form/amd/build/form.min.js.map index a0226b31f88f0..ecf72d4fada7a 100644 --- a/lib/form/amd/build/form.min.js.map +++ b/lib/form/amd/build/form.min.js.map @@ -1 +1 @@ -{"version":3,"file":"form.min.js","sources":["../src/form.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 * This file contains JS functionality required by mforms and is included automatically\n * when required.\n *\n * @see /lib/formslib.php#L2548 Candidate for removal, depends on grouped rules.\n * @see /lib/amd/src/showhidesettings.js Candidate for removal.\n *\n * @module core_form/form\n * @copyright 2024 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\n// Pure form functionality.\n// import {serialize} from './util';\nimport * as FormChangeChecker from './changechecker';\nimport * as Submit from './submit';\nimport Rules from './form/rules';\nimport * as MutateDom from './form/dom';\n\n// Maybe not needed but noted just in case.\n// import * as CollapseSections from './collapsesections'; // This is included via ../collapsesections.mustache\n// import * as EncryptedPassword from './encryptedpassword'; // This is included via ../setting_encryptedpassword.mustache\n// import * as FileTypes from './filetypes'; // This is included via /lib/form/filetypes.php#L152\n// import * as PasswordUnmask from './passwordunmask'; // This is included via ../element-passwordunmask.mustache\n// import * as ShowAdvanced from './showadvanced'; // This is included via /lib/formslib.php#L3349\n\n// Custom element types, Likely not needed.\n// import * as ChoiceDropdown from 'core_form/choicedropdown'; // This is included via ../choicedropdown.mustache\n// import * as ConfigText_maxlength from 'core_form/configtext_maxlength'; // This is included via lib/adminlib.php#L2635\n// import * as DefaultCustom from 'core_form/defaultcustom'; // This is included via lib/form/defaultcustom.php#L253\n\nexport default class Form {\n /**\n * @var {HTMLFormElement} form Our very own form to work on.\n */\n form;\n\n /**\n * @var {Map} dependencies Our map of form dependencies.\n */\n dependencies;\n\n /**\n * Create a new form instance.\n *\n * @param {String} formID The ID of the form to be managed.\n * @param {Object} dependencies The passed object of form dependencies.\n */\n constructor(formID, dependencies) {\n // Set class properties.\n this.form = document.querySelector(`#${formID}`);\n this.dependencies = this.getDependencyMapper(dependencies);\n this.rules = new Rules(this);\n\n // Apply the initial state of the form.\n this.applyInitialState();\n\n // Handle mutations within the form.\n this.registerEventListeners();\n FormChangeChecker.watchForm(this.form);\n }\n\n /**\n * Given the page has loaded, apply the initial state of the form.\n */\n applyInitialState() {\n [...this.form.elements].forEach((element) => {\n if (this.dependencies.has(element.name)) {\n this.domDispatch(this.displayMapPrune(this.dispatchDependencyRules(element)));\n }\n });\n }\n\n /**\n * Add event listeners to the form.\n */\n registerEventListeners() {\n this.form.addEventListener('change', (e) => {\n if (e.target.type === 'submit') {\n FormChangeChecker.resetFormDirtyState(this.form);\n Submit.init(e.target.id);\n }\n if (e.target.type === 'reset') {\n FormChangeChecker.resetFormDirtyState(this.form);\n this.form.reset();\n }\n // Something changes based on this element.\n if (this.dependencies.has(e.target.name)) {\n FormChangeChecker.markFormChangedFromNode(e.target);\n this.domDispatch(this.displayMapPrune(this.dispatchDependencyRules(e.target)));\n }\n });\n }\n\n /**\n * Dispatch the dependency rules to the appropriate rule handler.\n *\n * @param {HTMLFormElement} target The name associated to the element that has changed.\n * @returns {Map} Actions to be taken along with elements that should be affected.\n */\n dispatchDependencyRules(target) {\n const displayMap = this.mapTemplate();\n this.dependencies.get(target.name).forEach((dependants, ruleName) => {\n // If the rule exists, use it, otherwise fallback to 'neq' which seems to be the \"default\" rule originally.\n const elNamesMap = this.rules[ruleName] ? this.rules[ruleName](target) : this.rules.neq(target);\n // Merge the current rule map with the final display map.\n elNamesMap.forEach((nodeNames, displayOption) => {\n displayMap.get(displayOption).push(...nodeNames);\n });\n });\n return displayMap;\n }\n\n /**\n * By default, the full display map contains empty entries and potential duplicated DOM node names.\n * Here we will get rid of any empty entries and review the locked array. If a node name exists in both\n * the locked and hidden array, it should be removed from the locked array as nodes are locked / disabled when hidden.\n *\n * @param {Map} displayMap Map of elements and their associated rules to prune.\n * @returns {Map|Map<>} The pruned map or map even a fully pruned map if noting has to change.\n */\n displayMapPrune(displayMap) {\n // Filter any unlocked items that pegged to be hidden as they must be locked if they are hidden.\n const removeunlockifhidden = displayMap.get('unlock').filter(x => {\n return !displayMap.get('hide').toString().includes(x.toString());\n });\n displayMap.set('unlock', removeunlockifhidden);\n\n // Remove any empty entries.\n for (const [key, value] of displayMap) {\n if (value.length === 0) {\n displayMap.delete(key);\n }\n }\n return displayMap;\n }\n\n /**\n * For a given element, get the names of DOM nodes that can change based on the given rule type name.\n *\n * @param {String} element The name of the element to get the dependants for.\n * @param {String} type The rule type to get the dependants for.\n * @returns {Array|[]}\n */\n getDependantsOfType(element, type) {\n return this.dependencies.get(element) !== 'undefined' ? this.dependencies.get(element).get(type) ?? [] : [];\n }\n\n /**\n * Dispatch the DOM manipulation to the appropriate function.\n *\n * @param {Map} elNamesMap What needs to change.\n */\n domDispatch(elNamesMap) {\n elNamesMap.forEach((elements, domUpdateOpt) => {\n if (!MutateDom[domUpdateOpt]) {\n return;\n }\n const nodes = this.elementNamesToDomNodes(elements);\n nodes.forEach((node) => {\n if (node === null) {\n return;\n }\n MutateDom[domUpdateOpt](node);\n });\n });\n }\n\n /**\n * Convert the element names into DOM nodes based on the element names.\n *\n * @param {Array} elementNames The name of dependent elements to get associated DOM nodes.\n * @returns {Array}\n */\n elementNamesToDomNodes(elementNames) {\n return elementNames.map((element) => {\n // TODO: Rename and document why we need to deep look if there are multiple nodes effected by the rule.\n return element.map((element2) => {\n return this.form.elements.namedItem(element2);\n });\n });\n }\n\n /**\n * Convert the dependencies object into a map of elements and their associated rules.\n *\n * @example\n * Note: This is a simplified example of the returned map showing the rules for the grade type element in assign.\n *\n * \"grade[modgrade_type]\" => Map {\n * \"eq\" => Map {\n * \"none\" => Object {\n * 1 => Array [\n * \"advancedgradingmethod_submissions\",\n * \"gradecat\",\n * \"gradepass\",\n * \"completionusegrade\",\n * \"completionusegrade\",\n * ]\n * }\n * },\n * \"neq\" => Map {\n * \"point\" => Object {\n * 1 => Array [\n * \"grade[modgrade_point]\",\n * \"grade[modgrade_rescalegrades]\"\n * ]\n * },\n * \"scale\" => Object {\n * 1 => Array [\n * \"grade[modgrade_scale]\"\n * ]\n * }\n * }\n * }\n *\n * Note: If the value of grade[modgrade_type] === \"none\" then the array of elements defined should be hidden.\n * Note: If the value of grade[modgrade_type] !== \"point\" then the array of elements defined within the following:\n * \"eq\" => \"none\" && \"neq\" => \"scale\" should be hidden.\n *\n * Note: The object within the \"rule\" map can contain either 0 or 1 this helps determine if the element should be:\n * hidden or locked if the rule is met.\n * @See /lib/formslib.php DEP_DISABLE & DEP_HIDE.\n *\n * @param {Object} dependencies The supplied object of form dependencies to migrate into a map.\n * @returns {Map} A map of elements and their associated rules.\n */\n getDependencyMapper(dependencies) {\n /**\n * Convert the object into a first level map. i.e. elementName => ruleType.\n *\n * @type {Map} The map of rules associated to the given element.\n * @example \"grade[modgrade_type]\" => Map<\"eq\", \"neq\">\n */\n const elementMap = new Map(Object.entries(dependencies));\n elementMap.forEach((elementrules, key) => {\n /**\n * Convert the element rules object into a map.\n *\n * @type {Map} The map of rules associated to the given element.\n * @example \"eq\" => Map<\"none\" => Object>\n * @example \"neq\" => Map<\"point\" => Object, \"scale\" => Object>\n */\n const ruleMap = new Map(Object.entries(elementrules));\n ruleMap.forEach((ruleComparisons, key) => {\n /**\n * Convert any disabledIf rules into objects, so we can manage them the same as hideIf items.\n *\n * @type {Map} The map of comparison values t.\n * @example \"none\" => \"none\" => Object\n * @example \"neq\" => \"point\" => Object\n */\n const hideDefine = new Map(Object.entries(ruleComparisons));\n hideDefine.forEach((action, compVal) => {\n if (Array.isArray(action)) {\n action = {...action};\n }\n hideDefine.set(compVal, action);\n });\n ruleMap.set(key, hideDefine);\n });\n elementMap.set(key, ruleMap);\n });\n return elementMap;\n }\n\n /**\n * A standard map that we'll be using to figure out what has to change and how.\n *\n * @returns {Map}\n */\n mapTemplate() {\n return new Map([\n ['hide', []],\n ['show', []],\n ['lock', []],\n ['unlock', []],\n ]);\n }\n\n /**\n * Initialize the form and its dependencies.\n *\n * @param {String} formID The ID of the form to be managed.\n * @param {Object} dependencies The passed object of form dependencies.\n * @returns {Form} An instance associated to a specific form on a given page.\n */\n static init(formID, dependencies) {\n return new Form(formID, dependencies);\n }\n}\n"],"names":["Form","constructor","formID","dependencies","form","document","querySelector","this","getDependencyMapper","rules","Rules","applyInitialState","registerEventListeners","FormChangeChecker","watchForm","elements","forEach","element","has","name","domDispatch","displayMapPrune","dispatchDependencyRules","addEventListener","e","target","type","resetFormDirtyState","Submit","init","id","reset","markFormChangedFromNode","displayMap","mapTemplate","get","dependants","ruleName","neq","nodeNames","displayOption","push","removeunlockifhidden","filter","x","toString","includes","set","key","value","length","delete","getDependantsOfType","elNamesMap","domUpdateOpt","MutateDom","elementNamesToDomNodes","node","elementNames","map","element2","namedItem","elementMap","Map","Object","entries","elementrules","ruleMap","ruleComparisons","hideDefine","action","compVal","Array","isArray"],"mappings":"2hDAgDqBA,KAiBjBC,YAAYC,OAAQC,mGAEXC,KAAOC,SAASC,yBAAkBJ,cAClCC,aAAeI,KAAKC,oBAAoBL,mBACxCM,MAAQ,IAAIC,eAAMH,WAGlBI,yBAGAC,yBACLC,kBAAkBC,UAAUP,KAAKH,MAMrCO,wBACQJ,KAAKH,KAAKW,UAAUC,SAASC,UACzBV,KAAKJ,aAAae,IAAID,QAAQE,YACzBC,YAAYb,KAAKc,gBAAgBd,KAAKe,wBAAwBL,cAQ/EL,8BACSR,KAAKmB,iBAAiB,UAAWC,IACZ,WAAlBA,EAAEC,OAAOC,OACTb,kBAAkBc,oBAAoBpB,KAAKH,MAC3CwB,OAAOC,KAAKL,EAAEC,OAAOK,KAEH,UAAlBN,EAAEC,OAAOC,OACTb,kBAAkBc,oBAAoBpB,KAAKH,WACtCA,KAAK2B,SAGVxB,KAAKJ,aAAae,IAAIM,EAAEC,OAAON,QAC/BN,kBAAkBmB,wBAAwBR,EAAEC,aACvCL,YAAYb,KAAKc,gBAAgBd,KAAKe,wBAAwBE,EAAEC,cAWjFH,wBAAwBG,cACdQ,WAAa1B,KAAK2B,0BACnB/B,aAAagC,IAAIV,OAAON,MAAMH,SAAQ,CAACoB,WAAYC,aAEjC9B,KAAKE,MAAM4B,UAAY9B,KAAKE,MAAM4B,UAAUZ,QAAUlB,KAAKE,MAAM6B,IAAIb,SAE7ET,SAAQ,CAACuB,UAAWC,iBAC3BP,WAAWE,IAAIK,eAAeC,QAAQF,iBAGvCN,WAWXZ,gBAAgBY,kBAENS,qBAAuBT,WAAWE,IAAI,UAAUQ,QAAOC,IACjDX,WAAWE,IAAI,QAAQU,WAAWC,SAASF,EAAEC,cAEzDZ,WAAWc,IAAI,SAAUL,0BAGpB,MAAOM,IAAKC,SAAUhB,WACF,IAAjBgB,MAAMC,QACNjB,WAAWkB,OAAOH,YAGnBf,WAUXmB,oBAAoBnC,QAASS,sCACiB,cAAnCnB,KAAKJ,aAAagC,IAAIlB,wCAA2BV,KAAKJ,aAAagC,IAAIlB,SAASkB,IAAIT,6DAAc,GAQ7GN,YAAYiC,YACRA,WAAWrC,SAAQ,CAACD,SAAUuC,oBACrBC,UAAUD,qBAGD/C,KAAKiD,uBAAuBzC,UACpCC,SAASyC,OACE,OAATA,MAGJF,UAAUD,cAAcG,YAWpCD,uBAAuBE,qBACZA,aAAaC,KAAK1C,SAEdA,QAAQ0C,KAAKC,UACTrD,KAAKH,KAAKW,SAAS8C,UAAUD,cAiDhDpD,oBAAoBL,oBAOV2D,WAAa,IAAIC,IAAIC,OAAOC,QAAQ9D,sBAC1C2D,WAAW9C,SAAQ,CAACkD,aAAclB,aAQxBmB,QAAU,IAAIJ,IAAIC,OAAOC,QAAQC,eACvCC,QAAQnD,SAAQ,CAACoD,gBAAiBpB,aAQxBqB,WAAa,IAAIN,IAAIC,OAAOC,QAAQG,kBAC1CC,WAAWrD,SAAQ,CAACsD,OAAQC,WACpBC,MAAMC,QAAQH,UACdA,OAAS,IAAIA,SAEjBD,WAAWtB,IAAIwB,QAASD,WAE5BH,QAAQpB,IAAIC,IAAKqB,eAErBP,WAAWf,IAAIC,IAAKmB,YAEjBL,WAQX5B,qBACW,IAAI6B,IAAI,CACX,CAAC,OAAQ,IACT,CAAC,OAAQ,IACT,CAAC,OAAQ,IACT,CAAC,SAAU,kBAWP7D,OAAQC,qBACT,IAAIH,KAAKE,OAAQC"} \ No newline at end of file +{"version":3,"file":"form.min.js","sources":["../src/form.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 * This file contains JS functionality required by mforms and is included automatically\n * when required.\n *\n * @see /lib/formslib.php#L2548 Candidate for removal, depends on grouped rules.\n * @see /lib/amd/src/showhidesettings.js Candidate for removal.\n *\n * @module core_form/form\n * @copyright 2024 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\n// Pure form functionality.\n// import {serialize} from './util';\nimport * as FormChangeChecker from './changechecker';\nimport * as Submit from './submit';\nimport Rules from './form/rules';\nimport * as MutateDom from './form/dom';\nimport Pending from 'core/pending';\n\n// Maybe not needed but noted just in case.\n// import * as CollapseSections from './collapsesections'; // This is included via ../collapsesections.mustache\n// import * as EncryptedPassword from './encryptedpassword'; // This is included via ../setting_encryptedpassword.mustache\n// import * as FileTypes from './filetypes'; // This is included via /lib/form/filetypes.php#L152\n// import * as PasswordUnmask from './passwordunmask'; // This is included via ../element-passwordunmask.mustache\n// import * as ShowAdvanced from './showadvanced'; // This is included via /lib/formslib.php#L3349\n\n// Custom element types, Likely not needed.\n// import * as ChoiceDropdown from 'core_form/choicedropdown'; // This is included via ../choicedropdown.mustache\n// import * as ConfigText_maxlength from 'core_form/configtext_maxlength'; // This is included via lib/adminlib.php#L2635\n// import * as DefaultCustom from 'core_form/defaultcustom'; // This is included via lib/form/defaultcustom.php#L253\n\nexport default class Form {\n /**\n * @var {HTMLFormElement} form Our very own form to work on.\n */\n form;\n\n /**\n * @var {Map} dependencies Our map of form dependencies.\n */\n dependencies;\n\n editors = new Map();\n /**\n * Create a new form instance.\n *\n * @param {String} formID The ID of the form to be managed.\n * @param {Object} dependencies The passed object of form dependencies.\n */\n constructor(formID, dependencies) {\n // Set class properties.\n this.form = document.querySelector(`#${formID}`);\n this.dependencies = this.getDependencyMapper(dependencies);\n this.isEditor();\n this.rules = new Rules(this);\n\n // Apply the initial state of the form.\n this.applyInitialState();\n\n // Handle mutations within the form.\n this.registerEventListeners();\n FormChangeChecker.watchForm(this.form);\n }\n\n /**\n * Given the page has loaded, apply the initial state of the form.\n */\n applyInitialState() {\n [...this.form.elements].forEach((element) => {\n if (this.dependencies.has(element.name)) {\n this.domDispatch(this.displayMapPrune(this.dispatchDependencyRules(element)));\n }\n });\n }\n\n /**\n * Add event listeners to the form.\n */\n registerEventListeners() {\n this.form.addEventListener('change', async(e) => {\n if (e.target.type === 'submit') {\n FormChangeChecker.resetFormDirtyState(this.form);\n Submit.init(e.target.id);\n }\n if (e.target.type === 'reset') {\n FormChangeChecker.resetFormDirtyState(this.form);\n this.form.reset();\n }\n // Something changes based on this element.\n if (this.dependencies.has(e.target.name)) {\n FormChangeChecker.markFormChangedFromNode(e.target);\n // Add a pending?\n const pendingPromise = new Pending('core/form:update');\n await this.domDispatch(this.displayMapPrune(this.dispatchDependencyRules(e.target)));\n pendingPromise.resolve();\n }\n });\n }\n\n /**\n * Dispatch the dependency rules to the appropriate rule handler.\n *\n * @param {HTMLFormElement} target The name associated to the element that has changed.\n * @returns {Map} Actions to be taken along with elements that should be affected.\n */\n dispatchDependencyRules(target) {\n const displayMap = this.mapTemplate();\n this.dependencies.get(target.name).forEach((dependants, ruleName) => {\n // If the rule exists, use it, otherwise fallback to 'neq' which seems to be the \"default\" rule originally.\n const elNamesMap = this.rules[ruleName] ? this.rules[ruleName](target) : this.rules.neq(target);\n // Merge the current rule map with the final display map.\n elNamesMap.forEach((nodeNames, displayOption) => {\n displayMap.get(displayOption).push(...nodeNames);\n });\n });\n return displayMap;\n }\n\n /**\n * By default, the full display map contains empty entries and potential duplicated DOM node names.\n * Here we will get rid of any empty entries and review the locked array. If a node name exists in both\n * the locked and hidden array, it should be removed from the locked array as nodes are locked / disabled when hidden.\n *\n * @param {Map} displayMap Map of elements and their associated rules to prune.\n * @returns {Map|Map<>} The pruned map or map even a fully pruned map if noting has to change.\n */\n displayMapPrune(displayMap) {\n // Filter any unlocked items that pegged to be hidden as they must be locked if they are hidden.\n const removeunlockifhidden = displayMap.get('unlock').filter(x => {\n return !displayMap.get('hide').toString().includes(x.toString());\n });\n displayMap.set('unlock', removeunlockifhidden);\n\n // Remove any empty entries.\n for (const [key, value] of displayMap) {\n if (value.length === 0) {\n displayMap.delete(key);\n }\n }\n return displayMap;\n }\n\n /**\n * For a given element, get the names of DOM nodes that can change based on the given rule type name.\n *\n * @param {String} element The name of the element to get the dependants for.\n * @param {String} type The rule type to get the dependants for.\n * @returns {Array|[]}\n */\n getDependantsOfType(element, type) {\n return this.dependencies.get(element) !== 'undefined' ? this.dependencies.get(element).get(type) ?? [] : [];\n }\n\n /**\n * Dispatch the DOM manipulation to the appropriate function.\n *\n * @param {Map} elNamesMap What needs to change.\n */\n domDispatch(elNamesMap) {\n window.console.log('elNamesMap', elNamesMap);\n elNamesMap.forEach((elements, domUpdateOpt) => {\n if (!MutateDom[domUpdateOpt]) {\n return;\n }\n const nodes = this.elementNamesToDomNodes(elements);\n nodes.forEach((node) => {\n if (node === null) {\n return;\n }\n MutateDom[domUpdateOpt](node);\n });\n });\n }\n\n /**\n * Convert the element names into DOM nodes based on the element names.\n *\n * @param {Array} elementNames The name of dependent elements to get associated DOM nodes.\n * @returns {Array}\n */\n elementNamesToDomNodes(elementNames) {\n return elementNames.map((element) => {\n // TODO: Rename and document why we need to deep look if there are multiple nodes effected by the rule.\n return element.map((element2) => {\n if (this.isEditor(element2)) {\n return this.form.elements.namedItem(`${element2}[text]`);\n }\n return this.form.elements.namedItem(element2);\n });\n });\n }\n\n isEditor(elementName = '') {\n if (this.editors.size === 0) {\n const fEditors = this.form.querySelectorAll('[data-fieldtype=\"editor\"] textarea');\n Array.from(fEditors).forEach((node) => {\n this.editors.set(node.name, true);\n });\n }\n\n return this.editors.get(`${elementName}[text]`) || false;\n }\n\n /**\n * Convert the dependencies object into a map of elements and their associated rules.\n *\n * @example\n * Note: This is a simplified example of the returned map showing the rules for the grade type element in assign.\n *\n * \"grade[modgrade_type]\" => Map {\n * \"eq\" => Map {\n * \"none\" => Object {\n * 1 => Array [\n * \"advancedgradingmethod_submissions\",\n * \"gradecat\",\n * \"gradepass\",\n * \"completionusegrade\",\n * \"completionusegrade\",\n * ]\n * }\n * },\n * \"neq\" => Map {\n * \"point\" => Object {\n * 1 => Array [\n * \"grade[modgrade_point]\",\n * \"grade[modgrade_rescalegrades]\"\n * ]\n * },\n * \"scale\" => Object {\n * 1 => Array [\n * \"grade[modgrade_scale]\"\n * ]\n * }\n * }\n * }\n *\n * Note: If the value of grade[modgrade_type] === \"none\" then the array of elements defined should be hidden.\n * Note: If the value of grade[modgrade_type] !== \"point\" then the array of elements defined within the following:\n * \"eq\" => \"none\" && \"neq\" => \"scale\" should be hidden.\n *\n * Note: The object within the \"rule\" map can contain either 0 or 1 this helps determine if the element should be:\n * hidden or locked if the rule is met.\n * @See /lib/formslib.php DEP_DISABLE & DEP_HIDE.\n *\n * @param {Object} dependencies The supplied object of form dependencies to migrate into a map.\n * @returns {Map} A map of elements and their associated rules.\n */\n getDependencyMapper(dependencies) {\n /**\n * Convert the object into a first level map. i.e. elementName => ruleType.\n *\n * @type {Map} The map of rules associated to the given element.\n * @example \"grade[modgrade_type]\" => Map<\"eq\", \"neq\">\n */\n const elementMap = new Map(Object.entries(dependencies));\n elementMap.forEach((elementrules, key) => {\n /**\n * Convert the element rules object into a map.\n *\n * @type {Map} The map of rules associated to the given element.\n * @example \"eq\" => Map<\"none\" => Object>\n * @example \"neq\" => Map<\"point\" => Object, \"scale\" => Object>\n */\n const ruleMap = new Map(Object.entries(elementrules));\n ruleMap.forEach((ruleComparisons, key) => {\n /**\n * Convert any disabledIf rules into objects, so we can manage them the same as hideIf items.\n *\n * @type {Map} The map of comparison values t.\n * @example \"none\" => \"none\" => Object\n * @example \"neq\" => \"point\" => Object\n */\n const hideDefine = new Map(Object.entries(ruleComparisons));\n hideDefine.forEach((action, compVal) => {\n if (Array.isArray(action)) {\n action = {...action};\n }\n hideDefine.set(compVal, action);\n });\n ruleMap.set(key, hideDefine);\n });\n elementMap.set(key, ruleMap);\n });\n return elementMap;\n }\n\n /**\n * A standard map that we'll be using to figure out what has to change and how.\n *\n * @returns {Map}\n */\n mapTemplate() {\n return new Map([\n ['hide', []],\n ['show', []],\n ['lock', []],\n ['unlock', []],\n ]);\n }\n\n /**\n * Initialize the form and its dependencies.\n *\n * @param {String} formID The ID of the form to be managed.\n * @param {Object} dependencies The passed object of form dependencies.\n * @returns {Form} An instance associated to a specific form on a given page.\n */\n static init(formID, dependencies) {\n return new Form(formID, dependencies);\n }\n}\n"],"names":["Form","constructor","formID","dependencies","Map","form","document","querySelector","this","getDependencyMapper","isEditor","rules","Rules","applyInitialState","registerEventListeners","FormChangeChecker","watchForm","elements","forEach","element","has","name","domDispatch","displayMapPrune","dispatchDependencyRules","addEventListener","async","e","target","type","resetFormDirtyState","Submit","init","id","reset","markFormChangedFromNode","pendingPromise","Pending","resolve","displayMap","mapTemplate","get","dependants","ruleName","neq","nodeNames","displayOption","push","removeunlockifhidden","filter","x","toString","includes","set","key","value","length","delete","getDependantsOfType","elNamesMap","window","console","log","domUpdateOpt","MutateDom","elementNamesToDomNodes","node","elementNames","map","element2","namedItem","elementName","editors","size","fEditors","querySelectorAll","Array","from","elementMap","Object","entries","elementrules","ruleMap","ruleComparisons","hideDefine","action","compVal","isArray"],"mappings":"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"} \ No newline at end of file diff --git a/lib/form/amd/build/form/dom.min.js b/lib/form/amd/build/form/dom.min.js index 7c22a1db52025..51c5eabbbb667 100644 --- a/lib/form/amd/build/form/dom.min.js +++ b/lib/form/amd/build/form/dom.min.js @@ -1,3 +1,3 @@ -define("core_form/form/dom",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.unlock=_exports.show=_exports.lock=_exports.hide=void 0;_exports.lock=elements=>{elements.forEach((element=>{null!==element&&element.setAttribute("disabled","disabled")}))};_exports.unlock=elements=>{elements.forEach((element=>{null!==element&&element.removeAttribute("disabled")}))};_exports.hide=elements=>{elements.forEach((element=>{if(null!==element){const parent=element.closest("[data-groupname]")?element.closest("[data-groupname]"):element.closest(".fitem");if(parent){parent.setAttribute("hidden","hidden"),parent.classList.add("d-none");const label=document.querySelector('label[for="'+element.id+'"]');label&&(label.setAttribute("hidden","hidden"),label.classList.add("d-none"))}}}))};_exports.show=elements=>{elements.forEach((element=>{if(null!==element){const parent=element.closest("[data-groupname]")?element.closest("[data-groupname]"):element.closest(".fitem");if(parent){parent.removeAttribute("hidden"),parent.classList.remove("d-none");const label=document.querySelector('label[for="'+element.id+'"]');label&&(label.removeAttribute("hidden"),label.classList.remove("d-none"))}}}))}})); +define("core_form/form/dom",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.unlock=_exports.show=_exports.lock=_exports.hide=void 0;_exports.lock=elements=>{elements.forEach((element=>{null!==element&&(element.setAttribute("disabled","disabled"),element.setAttribute("readonly","readonly"),element.dispatchEvent(new Event("form:editorUpdated")))}))};_exports.unlock=elements=>{elements.forEach((element=>{null!==element&&(element.removeAttribute("disabled"),element.removeAttribute("readonly"),element.dispatchEvent(new Event("form:editorUpdated")))}))};_exports.hide=elements=>{elements.forEach((element=>{if(null!==element&&element instanceof HTMLElement){element.setAttribute("disabled","disabled");const parent=element.closest("[data-groupname]")?element.closest("[data-groupname]"):element.closest(".fitem");if(parent){parent.setAttribute("hidden","hidden"),parent.classList.add("d-none");const label=document.querySelector('label[for="'+element.id+'"]');label&&(label.setAttribute("hidden","hidden"),label.classList.add("d-none"))}}}))};_exports.show=elements=>{elements.forEach((element=>{if(null!==element&&element instanceof HTMLElement){element.removeAttribute("disabled");const parent=element.closest("[data-groupname]")?element.closest("[data-groupname]"):element.closest(".fitem");if(parent){parent.removeAttribute("hidden"),parent.classList.remove("d-none");const label=document.querySelector('label[for="'+element.id+'"]');label&&(label.removeAttribute("hidden"),label.classList.remove("d-none"))}}}))}})); //# sourceMappingURL=dom.min.js.map \ No newline at end of file diff --git a/lib/form/amd/build/form/dom.min.js.map b/lib/form/amd/build/form/dom.min.js.map index a1e3793d292c1..065cec78b0212 100644 --- a/lib/form/amd/build/form/dom.min.js.map +++ b/lib/form/amd/build/form/dom.min.js.map @@ -1 +1 @@ -{"version":3,"file":"dom.min.js","sources":["../../src/form/dom.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 * This file contains some helper functions to change the visual state of the form elements.\n *\n * @module core_form/form/dom\n * @copyright 2024 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\n/**\n * Disable an element.\n *\n * @param {Array} elements The element to be disabled.\n */\nexport const lock = (elements) => {\n elements.forEach(element => {\n if (element !== null) {\n element.setAttribute('disabled', 'disabled');\n }\n });\n};\n\n/**\n * Enable an element.\n *\n * @param {Array} elements The element to be enabled.\n */\nexport const unlock = (elements) => {\n elements.forEach(element => {\n if (element !== null) {\n element.removeAttribute('disabled');\n }\n });\n};\n\n/**\n * Hide an element.\n *\n * @param {Array} elements The elements to be hidden.\n */\nexport const hide = (elements) => {\n elements.forEach(element => {\n if (element !== null) {\n // Check if the element is part of a group otherwise, just grab the nearest wrapping element.\n const parent = element.closest('[data-groupname]') ? element.closest('[data-groupname]') : element.closest('.fitem');\n if (parent) {\n parent.setAttribute('hidden', 'hidden');\n parent.classList.add('d-none');\n\n // Hide the label as well.\n const label = document.querySelector('label[for=\"' + element.id + '\"]');\n if (label) {\n label.setAttribute('hidden', 'hidden');\n label.classList.add('d-none');\n }\n }\n }\n });\n};\n\n/**\n * Show an element.\n *\n * @param {Array} elements The elements to be shown.\n */\nexport const show = (elements) => {\n elements.forEach(element => {\n if (element !== null) {\n // Check if the element is part of a group otherwise, just grab the nearest wrapping element.\n const parent = element.closest('[data-groupname]') ? element.closest('[data-groupname]') : element.closest('.fitem');\n if (parent) {\n parent.removeAttribute('hidden');\n parent.classList.remove('d-none');\n\n // Show the label as well.\n const label = document.querySelector('label[for=\"' + element.id + '\"]');\n if (label) {\n label.removeAttribute('hidden');\n label.classList.remove('d-none');\n }\n }\n }\n });\n};\n"],"names":["elements","forEach","element","setAttribute","removeAttribute","parent","closest","classList","add","label","document","querySelector","id","remove"],"mappings":"mMA8BqBA,WACjBA,SAASC,SAAQC,UACG,OAAZA,SACAA,QAAQC,aAAa,WAAY,gCAUtBH,WACnBA,SAASC,SAAQC,UACG,OAAZA,SACAA,QAAQE,gBAAgB,8BAUfJ,WACjBA,SAASC,SAAQC,aACG,OAAZA,QAAkB,OAEZG,OAASH,QAAQI,QAAQ,oBAAsBJ,QAAQI,QAAQ,oBAAsBJ,QAAQI,QAAQ,aACvGD,OAAQ,CACRA,OAAOF,aAAa,SAAU,UAC9BE,OAAOE,UAAUC,IAAI,gBAGfC,MAAQC,SAASC,cAAc,cAAgBT,QAAQU,GAAK,MAC9DH,QACAA,MAAMN,aAAa,SAAU,UAC7BM,MAAMF,UAAUC,IAAI,+BAYnBR,WACjBA,SAASC,SAAQC,aACG,OAAZA,QAAkB,OAEZG,OAASH,QAAQI,QAAQ,oBAAsBJ,QAAQI,QAAQ,oBAAsBJ,QAAQI,QAAQ,aACvGD,OAAQ,CACRA,OAAOD,gBAAgB,UACvBC,OAAOE,UAAUM,OAAO,gBAGlBJ,MAAQC,SAASC,cAAc,cAAgBT,QAAQU,GAAK,MAC9DH,QACAA,MAAML,gBAAgB,UACtBK,MAAMF,UAAUM,OAAO"} \ No newline at end of file +{"version":3,"file":"dom.min.js","sources":["../../src/form/dom.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 * This file contains some helper functions to change the visual state of the form elements.\n *\n * @module core_form/form/dom\n * @copyright 2024 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\n/**\n * Disable an element.\n *\n * @param {Array} elements The element to be disabled.\n */\nexport const lock = (elements) => {\n elements.forEach(element => {\n if (element !== null) {\n element.setAttribute('disabled', 'disabled');\n // TODO: Editor check.\n element.setAttribute('readonly', 'readonly');\n element.dispatchEvent(new Event('form:editorUpdated'));\n }\n });\n};\n\n/**\n * Enable an element.\n *\n * @param {Array} elements The element to be enabled.\n */\nexport const unlock = (elements) => {\n elements.forEach(element => {\n if (element !== null) {\n element.removeAttribute('disabled');\n // TODO: Editor check.\n element.removeAttribute('readonly');\n element.dispatchEvent(new Event('form:editorUpdated'));\n }\n });\n};\n\n/**\n * Hide an element.\n *\n * @param {Array} elements The elements to be hidden.\n */\nexport const hide = (elements) => {\n elements.forEach(element => {\n // TODO: RadioNodeList is not an instance of HTMLElement, need to find a way to handle this.\n if (element !== null && element instanceof HTMLElement) {\n element.setAttribute('disabled', 'disabled');\n // Check if the element is part of a group otherwise, just grab the nearest wrapping element.\n const parent = element.closest('[data-groupname]') ? element.closest('[data-groupname]') : element.closest('.fitem');\n if (parent) {\n parent.setAttribute('hidden', 'hidden');\n parent.classList.add('d-none');\n\n // Hide the label as well.\n const label = document.querySelector('label[for=\"' + element.id + '\"]');\n if (label) {\n label.setAttribute('hidden', 'hidden');\n label.classList.add('d-none');\n }\n }\n }/* else {\n window.console.log('Hide: Element is not an instance of HTMLElement', element);\n }*/\n });\n};\n\n/**\n * Show an element.\n *\n * @param {Array} elements The elements to be shown.\n */\nexport const show = (elements) => {\n elements.forEach(element => {\n // TODO: RadioNodeList is not an instance of HTMLElement, need to find a way to handle this.\n if (element !== null && element instanceof HTMLElement) {\n element.removeAttribute('disabled');\n // Check if the element is part of a group otherwise, just grab the nearest wrapping element.\n const parent = element.closest('[data-groupname]') ? element.closest('[data-groupname]') : element.closest('.fitem');\n if (parent) {\n parent.removeAttribute('hidden');\n parent.classList.remove('d-none');\n\n // Show the label as well.\n const label = document.querySelector('label[for=\"' + element.id + '\"]');\n if (label) {\n label.removeAttribute('hidden');\n label.classList.remove('d-none');\n }\n }\n }/* else {\n window.console.log('Show: Element is not an instance of HTMLElement', element);\n }*/\n });\n};\n"],"names":["elements","forEach","element","setAttribute","dispatchEvent","Event","removeAttribute","HTMLElement","parent","closest","classList","add","label","document","querySelector","id","remove"],"mappings":"mMA8BqBA,WACjBA,SAASC,SAAQC,UACG,OAAZA,UACAA,QAAQC,aAAa,WAAY,YAEjCD,QAAQC,aAAa,WAAY,YACjCD,QAAQE,cAAc,IAAIC,MAAM,4CAUrBL,WACnBA,SAASC,SAAQC,UACG,OAAZA,UACAA,QAAQI,gBAAgB,YAExBJ,QAAQI,gBAAgB,YACxBJ,QAAQE,cAAc,IAAIC,MAAM,0CAUvBL,WACjBA,SAASC,SAAQC,aAEG,OAAZA,SAAoBA,mBAAmBK,YAAa,CACpDL,QAAQC,aAAa,WAAY,kBAE3BK,OAASN,QAAQO,QAAQ,oBAAsBP,QAAQO,QAAQ,oBAAsBP,QAAQO,QAAQ,aACvGD,OAAQ,CACRA,OAAOL,aAAa,SAAU,UAC9BK,OAAOE,UAAUC,IAAI,gBAGfC,MAAQC,SAASC,cAAc,cAAgBZ,QAAQa,GAAK,MAC9DH,QACAA,MAAMT,aAAa,SAAU,UAC7BS,MAAMF,UAAUC,IAAI,+BAcnBX,WACjBA,SAASC,SAAQC,aAEG,OAAZA,SAAoBA,mBAAmBK,YAAa,CACpDL,QAAQI,gBAAgB,kBAElBE,OAASN,QAAQO,QAAQ,oBAAsBP,QAAQO,QAAQ,oBAAsBP,QAAQO,QAAQ,aACvGD,OAAQ,CACRA,OAAOF,gBAAgB,UACvBE,OAAOE,UAAUM,OAAO,gBAGlBJ,MAAQC,SAASC,cAAc,cAAgBZ,QAAQa,GAAK,MAC9DH,QACAA,MAAMN,gBAAgB,UACtBM,MAAMF,UAAUM,OAAO"} \ No newline at end of file diff --git a/lib/form/amd/build/form/rules.min.js b/lib/form/amd/build/form/rules.min.js index 209b928930205..5031d878c40de 100644 --- a/lib/form/amd/build/form/rules.min.js +++ b/lib/form/amd/build/form/rules.min.js @@ -1,3 +1,3 @@ -define("core_form/form/rules",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;_exports.default=class{notchecked(target){const displayMap=this.form.mapTemplate();let lock=!1;return this.getDependantsOfType(target.name,"notchecked").forEach(((dependant,key)=>{lock=Boolean(key)!==target.checked,determineDisplayMap(dependant,displayMap,lock)})),this.form.displayMapPrune(displayMap)}checked(target){const displayMap=this.form.mapTemplate();let lock=!1;return this.getDependantsOfType(target.name,"checked").forEach(((dependant,key)=>{lock=Boolean(key)===target.checked,determineDisplayMap(dependant,displayMap,lock)})),this.form.displayMapPrune(displayMap)}eq(target){const displayMap=this.form.mapTemplate(),ctarget=this.getRadioFieldVal(target);let lock=!1;return this.getDependantsOfType(target.name,"eq").forEach(((dependant,key)=>{if(("radio"!==target.type||String(key)===String(ctarget.value))&&"hidden"!==target.type)return"checkbox"!==target.type||target.checksed?void(target.classList.contains("filepickerhidden")?(lock=!M.form_filepicker.instances[target.id].fileadded,determineDisplayMap(dependant,displayMap,lock)):"select"===target.tagName.toLowerCase()&&target.multiple||(lock=target.value===key,determineDisplayMap(dependant,displayMap,lock))):(lock=target.checked===Boolean(key),void determineDisplayMap(dependant,displayMap,lock))})),this.form.displayMapPrune(displayMap)}neq(target){var _this$getDependantsOf,_this$getDependantsOf2,_this$getDependantsOf3,_this$getDependantsOf4,_this$getDependantsOf5,_this$getDependantsOf6;const displayMap=this.form.mapTemplate(),ctarget=this.getRadioFieldVal(target);let lock=!1;const maps=[...null!==(_this$getDependantsOf=null===(_this$getDependantsOf2=this.getDependantsOfType(target.name,"neq"))||void 0===_this$getDependantsOf2?void 0:_this$getDependantsOf2.entries())&&void 0!==_this$getDependantsOf?_this$getDependantsOf:[],...null!==(_this$getDependantsOf3=null===(_this$getDependantsOf4=this.getDependantsOfType(target.name,"ne"))||void 0===_this$getDependantsOf4?void 0:_this$getDependantsOf4.entries())&&void 0!==_this$getDependantsOf3?_this$getDependantsOf3:[],...null!==(_this$getDependantsOf5=null===(_this$getDependantsOf6=this.getDependantsOfType(target.name,"noteq"))||void 0===_this$getDependantsOf6?void 0:_this$getDependantsOf6.entries())&&void 0!==_this$getDependantsOf5?_this$getDependantsOf5:[]];return new Map(maps).forEach(((dependant,key)=>{if(("radio"!==target.type||String(key)===String(ctarget.value))&&"hidden"!==target.type)return"checkbox"!==target.type||target.checked?void(target.classList.contains("filepickerhidden")?(lock=!!M.form_filepicker.instances[target.id].fileadded,determineDisplayMap(dependant,displayMap,lock)):"select"===target.tagName.toLowerCase()&&target.multiple||(lock=target.value!==key,determineDisplayMap(dependant,displayMap,lock))):(lock=target.checked===Boolean(key),void determineDisplayMap(dependant,displayMap,lock))})),this.form.displayMapPrune(displayMap)}in(target){const displayMap=this.form.mapTemplate();let lock=!1;return this.getDependantsOfType(target.name,"in").forEach(((dependant,key)=>{lock=key.split("|").includes(target.value),determineDisplayMap(dependant,displayMap,lock)})),this.form.displayMapPrune(displayMap)}getDependantsOfType(element,type){return this.form.getDependantsOfType(element,type)}getRadioFieldVal(target){return"radio"===target.type?this.form.form.elements.namedItem(target.name):target}constructor(form){var obj,key,value;value=void 0,(key="form")in(obj=this)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,this.form=form}};const dependencyBehaviour_disable=0,dependencyBehaviour_hide=1,determineDisplayMap=(dependant,displayMap,lock)=>{const hide=!!dependant.hasOwnProperty(dependencyBehaviour_hide)&&lock;dependant.hasOwnProperty(dependencyBehaviour_disable)?(displayMap.get("show").push(dependant[dependencyBehaviour_disable]),lock?displayMap.get("lock").push(dependant[dependencyBehaviour_disable]):displayMap.get("unlock").push(dependant[dependencyBehaviour_disable])):dependant.hasOwnProperty(dependencyBehaviour_hide)&&(hide?displayMap.get("hide").push(dependant[dependencyBehaviour_hide]):displayMap.get("show").push(dependant[dependencyBehaviour_hide]))};return _exports.default})); +define("core_form/form/rules",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;_exports.default=class{notchecked(target){const displayMap=this.form.mapTemplate();let lock=!1;return this.getDependantsOfType(target.name,"notchecked").forEach(((dependant,key)=>{lock=Boolean(key)!==target.checked,determineDisplayMap(dependant,displayMap,lock)})),this.form.displayMapPrune(displayMap)}checked(target){const displayMap=this.form.mapTemplate();let lock=!1;return this.getDependantsOfType(target.name,"checked").forEach(((dependant,key)=>{lock=Boolean(key)===target.checked,determineDisplayMap(dependant,displayMap,lock)})),this.form.displayMapPrune(displayMap)}eq(target){const displayMap=this.form.mapTemplate(),rTarget=this.getRadioFieldVal(target);let lock=!1;return this.getDependantsOfType(target.name,"eq").forEach(((dependant,key)=>"radio"===target.type?(lock=String(key)===String(rTarget.value),void determineDisplayMap(dependant,displayMap,lock)):"hidden"!==target.type?"checkbox"!==target.type||target.checksed?void(target.classList.contains("filepickerhidden")?(lock=!M.form_filepicker.instances[target.id].fileadded,determineDisplayMap(dependant,displayMap,lock)):"select"===target.tagName.toLowerCase()&&target.multiple||(lock=target.value===key,determineDisplayMap(dependant,displayMap,lock))):(lock=target.checked===Boolean(key),void determineDisplayMap(dependant,displayMap,lock)):void 0)),this.form.displayMapPrune(displayMap)}neq(target){var _this$getDependantsOf,_this$getDependantsOf2,_this$getDependantsOf3,_this$getDependantsOf4,_this$getDependantsOf5,_this$getDependantsOf6;const displayMap=this.form.mapTemplate(),rTarget=this.getRadioFieldVal(target);let lock=!1;const maps=[...null!==(_this$getDependantsOf=null===(_this$getDependantsOf2=this.getDependantsOfType(target.name,"neq"))||void 0===_this$getDependantsOf2?void 0:_this$getDependantsOf2.entries())&&void 0!==_this$getDependantsOf?_this$getDependantsOf:[],...null!==(_this$getDependantsOf3=null===(_this$getDependantsOf4=this.getDependantsOfType(target.name,"ne"))||void 0===_this$getDependantsOf4?void 0:_this$getDependantsOf4.entries())&&void 0!==_this$getDependantsOf3?_this$getDependantsOf3:[],...null!==(_this$getDependantsOf5=null===(_this$getDependantsOf6=this.getDependantsOfType(target.name,"noteq"))||void 0===_this$getDependantsOf6?void 0:_this$getDependantsOf6.entries())&&void 0!==_this$getDependantsOf5?_this$getDependantsOf5:[]];return new Map(maps).forEach(((dependant,key)=>"radio"===target.type?(lock=String(key)!==String(rTarget.value),void determineDisplayMap(dependant,displayMap,lock)):"hidden"!==target.type?"checkbox"!==target.type||target.checked?void(target.classList.contains("filepickerhidden")?(lock=!!M.form_filepicker.instances[target.id].fileadded,determineDisplayMap(dependant,displayMap,lock)):"select"===target.tagName.toLowerCase()&&target.multiple||(lock=target.value!==key,determineDisplayMap(dependant,displayMap,lock))):(lock=target.checked===Boolean(key),void determineDisplayMap(dependant,displayMap,lock)):void 0)),this.form.displayMapPrune(displayMap)}in(target){const displayMap=this.form.mapTemplate();let lock=!1;return this.getDependantsOfType(target.name,"in").forEach(((dependant,key)=>{lock=key.split("|").includes(target.value),determineDisplayMap(dependant,displayMap,lock)})),this.form.displayMapPrune(displayMap)}getDependantsOfType(element,type){return this.form.getDependantsOfType(element,type)}getRadioFieldVal(target){return"radio"===target.type?this.form.form.elements.namedItem(target.name):target}constructor(form){var obj,key,value;value=void 0,(key="form")in(obj=this)?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,this.form=form}};const dependencyBehaviour_disable=0,dependencyBehaviour_hide=1,determineDisplayMap=(dependant,displayMap,lock)=>{const hide=!!dependant.hasOwnProperty(dependencyBehaviour_hide)&&lock;dependant.hasOwnProperty(dependencyBehaviour_disable)?lock?displayMap.get("lock").push(dependant[dependencyBehaviour_disable]):displayMap.get("unlock").push(dependant[dependencyBehaviour_disable]):dependant.hasOwnProperty(dependencyBehaviour_hide)&&(hide||displayMap.get("hide").toString().includes(dependant[dependencyBehaviour_hide].toString())?displayMap.get("hide").push(dependant[dependencyBehaviour_hide]):displayMap.get("show").push(dependant[dependencyBehaviour_hide]))};return _exports.default})); //# sourceMappingURL=rules.min.js.map \ No newline at end of file diff --git a/lib/form/amd/build/form/rules.min.js.map b/lib/form/amd/build/form/rules.min.js.map index 613bc3aae21fc..1b23f8e24f88d 100644 --- a/lib/form/amd/build/form/rules.min.js.map +++ b/lib/form/amd/build/form/rules.min.js.map @@ -1 +1 @@ -{"version":3,"file":"rules.min.js","sources":["../../src/form/rules.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 * This file contains a set of rules that elements can be compared against to determine if they should be shown, hidden, etc...\n *\n * @See /lib/pear/HTML/QuickForm/Rule/Compare.php\n * @See https://pear.php.net/manual/en/package.html.html-quickform2.rules.list.php for a list of available rules.\n *\n * @module core_form/form/rules\n * @copyright 2024 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\nexport default class Rules {\n /**\n * @var {Form} form The instance of the form class that has a DOM node & references matched.\n */\n form;\n\n // Shorthand helpers rather than requiring devs to export the value and use an eq or neq check.\n\n /**\n * Compare the value of the changed DOM node to the requested rule.\n *\n * @param {HTMLFormElement} target The changed DOM node to be compared against the requested rule.\n * @returns {Map} Actions to be taken along with elements that should be affected.\n */\n notchecked(target) {\n const displayMap = this.form.mapTemplate();\n let lock = false;\n\n this.getDependantsOfType(target.name, 'notchecked').forEach((dependant, key) => {\n lock = Boolean(key) !== target.checked;\n determineDisplayMap(dependant, displayMap, lock);\n });\n\n return this.form.displayMapPrune(displayMap);\n }\n\n /**\n * Compare the value of the changed DOM node to the requested rule.\n *\n * @param {HTMLFormElement} target The changed DOM node to be compared against the requested rule.\n * @returns {Map} Actions to be taken along with elements that should be affected.\n */\n checked(target) {\n const displayMap = this.form.mapTemplate();\n let lock = false;\n\n this.getDependantsOfType(target.name, 'checked').forEach((dependant, key) => {\n lock = Boolean(key) === target.checked;\n determineDisplayMap(dependant, displayMap, lock);\n });\n\n return this.form.displayMapPrune(displayMap);\n }\n\n // Handlers for default rules defined in the links in the JSDoc for this class.\n\n /**\n * Compare the value of the changed DOM node to the requested rule.\n *\n * @param {HTMLFormElement} target The changed DOM node to be compared against the requested rule.\n * @returns {Map} Actions to be taken along with elements that should be affected.\n */\n eq(target) {\n const displayMap = this.form.mapTemplate();\n const ctarget = this.getRadioFieldVal(target);\n let lock = false;\n\n this.getDependantsOfType(target.name, 'eq').forEach((dependant, key) => {\n if (target.type === 'radio' && String(key) !== String(ctarget.value)) {\n return;\n } else if (target.type === 'hidden') {\n // This is the hidden input that is part of an advcheckbox.\n //hiddenVal = target.value === key;\n return;\n } else if (target.type === 'checkbox' && !target.checksed) {\n lock = target.checked === Boolean(key);\n determineDisplayMap(dependant, displayMap, lock);\n return;\n }\n if (target.classList.contains('filepickerhidden')) {\n lock = !M.form_filepicker.instances[target.id].fileadded;\n determineDisplayMap(dependant, displayMap, lock);\n } else if (target.tagName.toLowerCase() === 'select' && target.multiple) {\n // Multiple selects can have one or more value assigned. A pipe (|) is used as a value separator\n // when multiple values have to be selected at the same time.\n } else {\n lock = target.value === key;\n determineDisplayMap(dependant, displayMap, lock);\n }\n });\n\n return this.form.displayMapPrune(displayMap);\n }\n\n /**\n * Compare the value of the changed DOM node to the requested rule.\n * @See Moodle has some interesting aliasing ne && noteq, this is also the old \"default\" rule.\n *\n * @param {HTMLFormElement} target The changed DOM node to be compared against the requested rule.\n * @returns {Map} Actions to be taken along with elements that should be affected.\n */\n neq(target) {\n const displayMap = this.form.mapTemplate();\n const ctarget = this.getRadioFieldVal(target);\n let lock = false;\n\n // Get all the aliases of neq and check them all at once.\n const maps = [\n ...this.getDependantsOfType(target.name, 'neq')?.entries() ?? [],\n ...this.getDependantsOfType(target.name, 'ne')?.entries() ?? [],\n ...this.getDependantsOfType(target.name, 'noteq')?.entries() ?? [],\n ];\n const condensedMaps = new Map(maps);\n condensedMaps.forEach((dependant, key) => {\n if (target.type === 'radio' && String(key) !== String(ctarget.value)) {\n return;\n } else if (target.type === 'hidden') {\n // This is the hidden input that is part of an advcheckbox.\n //hiddenVal = target.value === key;\n return;\n } else if (target.type === 'checkbox' && !target.checked) {\n lock = target.checked === Boolean(key);\n determineDisplayMap(dependant, displayMap, lock);\n return;\n }\n if (target.classList.contains('filepickerhidden')) {\n lock = !!M.form_filepicker.instances[target.id].fileadded;\n determineDisplayMap(dependant, displayMap, lock);\n } else if (target.tagName.toLowerCase() === 'select' && target.multiple) {\n // Multiple selects can have one or more value assigned. A pipe (|) is used as a value separator\n // when multiple values have to be selected at the same time.\n } else {\n lock = target.value !== key;\n determineDisplayMap(dependant, displayMap, lock);\n }\n });\n\n return this.form.displayMapPrune(displayMap);\n }\n\n // Moodle addons to the rules list.\n\n /**\n * Compare the value of the changed DOM node to the requested rule.\n *\n * @param {HTMLFormElement} target The changed DOM node to be compared against the requested rule.\n * @returns {Map} Actions to be taken along with elements that should be affected.\n */\n in(target) {\n const displayMap = this.form.mapTemplate();\n let lock = false;\n\n this.getDependantsOfType(target.name, 'in').forEach((dependant, key) => {\n lock = key.split('|').includes(target.value);\n determineDisplayMap(dependant, displayMap, lock);\n });\n\n return this.form.displayMapPrune(displayMap);\n }\n\n /**\n * Call off to the Form() to grab the elements that listen to the passed rule for the given element.\n *\n * @param {String} element The name of the element to get the dependants for.\n * @param {String} type The rule type to get the dependants for.\n * @returns {Array|[]}\n */\n getDependantsOfType(element, type) {\n return this.form.getDependantsOfType(element, type);\n }\n\n /**\n * Radio fields are a bit different, they need to be handled differently.\n *\n * @param {HTMLFormElement} target The changed DOM node to find a potential radio field for.\n * @returns {RadioNodeList|HTMLFormElement}\n */\n getRadioFieldVal(target) {\n return target.type === 'radio' ? this.form.form.elements.namedItem(target.name) : target;\n }\n\n /**\n * Constructor for the Rules class.\n * @param {Form} form The form object that the rules are being applied to.\n */\n constructor(form) {\n this.form = form;\n }\n}\n\n/**\n * A small object that defines the behaviour of the dependency rules for readability.\n *\n * @type {{hide: number, disable: number}}\n */\nconst dependencyBehaviour = {\n disable: 0,\n hide: 1,\n};\n\n/**\n * Considering the dependant object and if we need to lock it, assign the elements to the correct displayMap key.\n *\n * @param {Object} dependant The dependant object that contains the rules for hiding and locking.\n * @param {Map} displayMap The aggregation of elements that should be shown, hidden, locked, or unlocked.\n * @param {Boolean} lock According to the rules, should the element be locked or unlocked.\n */\nconst determineDisplayMap = (dependant, displayMap, lock) => {\n const hide = dependant.hasOwnProperty(dependencyBehaviour.hide) ? lock : false;\n if (dependant.hasOwnProperty(dependencyBehaviour.disable)) {\n displayMap.get('show').push(dependant[dependencyBehaviour.disable]);\n if (lock) {\n displayMap.get('lock').push(dependant[dependencyBehaviour.disable]);\n } else {\n displayMap.get('unlock').push(dependant[dependencyBehaviour.disable]);\n }\n } else if (dependant.hasOwnProperty(dependencyBehaviour.hide)) {\n if (hide) {\n displayMap.get('hide').push(dependant[dependencyBehaviour.hide]);\n } else {\n displayMap.get('show').push(dependant[dependencyBehaviour.hide]);\n }\n }\n};\n"],"names":["notchecked","target","displayMap","this","form","mapTemplate","lock","getDependantsOfType","name","forEach","dependant","key","Boolean","checked","determineDisplayMap","displayMapPrune","eq","ctarget","getRadioFieldVal","type","String","value","checksed","classList","contains","M","form_filepicker","instances","id","fileadded","tagName","toLowerCase","multiple","neq","maps","_this$getDependantsOf2","entries","_this$getDependantsOf4","_this$getDependantsOf6","Map","in","split","includes","element","elements","namedItem","constructor","dependencyBehaviour","hide","hasOwnProperty","get","push"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"rules.min.js","sources":["../../src/form/rules.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 * This file contains a set of rules that elements can be compared against to determine if they should be shown, hidden, etc...\n *\n * @See /lib/pear/HTML/QuickForm/Rule/Compare.php\n * @See https://pear.php.net/manual/en/package.html.html-quickform2.rules.list.php for a list of available rules.\n *\n * @module core_form/form/rules\n * @copyright 2024 Mathew May \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\nexport default class Rules {\n /**\n * @var {Form} form The instance of the form class that has a DOM node & references matched.\n */\n form;\n\n // Shorthand helpers rather than requiring devs to export the value and use an eq or neq check.\n\n /**\n * Compare the value of the changed DOM node to the requested rule.\n *\n * @param {HTMLFormElement} target The changed DOM node to be compared against the requested rule.\n * @returns {Map} Actions to be taken along with elements that should be affected.\n */\n notchecked(target) {\n const displayMap = this.form.mapTemplate();\n let lock = false;\n\n this.getDependantsOfType(target.name, 'notchecked').forEach((dependant, key) => {\n lock = Boolean(key) !== target.checked;\n determineDisplayMap(dependant, displayMap, lock);\n });\n\n return this.form.displayMapPrune(displayMap);\n }\n\n /**\n * Compare the value of the changed DOM node to the requested rule.\n *\n * @param {HTMLFormElement} target The changed DOM node to be compared against the requested rule.\n * @returns {Map} Actions to be taken along with elements that should be affected.\n */\n checked(target) {\n const displayMap = this.form.mapTemplate();\n let lock = false;\n\n this.getDependantsOfType(target.name, 'checked').forEach((dependant, key) => {\n lock = Boolean(key) === target.checked;\n determineDisplayMap(dependant, displayMap, lock);\n });\n\n return this.form.displayMapPrune(displayMap);\n }\n\n // Handlers for default rules defined in the links in the JSDoc for this class.\n\n /**\n * Compare the value of the changed DOM node to the requested rule.\n *\n * @param {HTMLFormElement} target The changed DOM node to be compared against the requested rule.\n * @returns {Map} Actions to be taken along with elements that should be affected.\n */\n eq(target) {\n const displayMap = this.form.mapTemplate();\n const rTarget = this.getRadioFieldVal(target);\n let lock = false;\n\n this.getDependantsOfType(target.name, 'eq').forEach((dependant, key) => {\n if (target.type === 'radio') {\n lock = String(key) === String(rTarget.value);\n determineDisplayMap(dependant, displayMap, lock);\n return;\n } else if (target.type === 'hidden') {\n // This is the hidden input that is part of an advcheckbox.\n //hiddenVal = target.value === key;\n return;\n } else if (target.type === 'checkbox' && !target.checksed) {\n lock = target.checked === Boolean(key);\n determineDisplayMap(dependant, displayMap, lock);\n return;\n }\n if (target.classList.contains('filepickerhidden')) {\n lock = !M.form_filepicker.instances[target.id].fileadded;\n determineDisplayMap(dependant, displayMap, lock);\n } else if (target.tagName.toLowerCase() === 'select' && target.multiple) {\n // Multiple selects can have one or more value assigned. A pipe (|) is used as a value separator\n // when multiple values have to be selected at the same time.\n } else {\n lock = target.value === key;\n determineDisplayMap(dependant, displayMap, lock);\n }\n });\n\n return this.form.displayMapPrune(displayMap);\n }\n\n /**\n * Compare the value of the changed DOM node to the requested rule.\n * @See Moodle has some interesting aliasing ne && noteq, this is also the old \"default\" rule.\n *\n * @param {HTMLFormElement} target The changed DOM node to be compared against the requested rule.\n * @returns {Map} Actions to be taken along with elements that should be affected.\n */\n neq(target) {\n const displayMap = this.form.mapTemplate();\n const rTarget = this.getRadioFieldVal(target);\n let lock = false;\n\n // Get all the aliases of neq and check them all at once.\n const maps = [\n ...this.getDependantsOfType(target.name, 'neq')?.entries() ?? [],\n ...this.getDependantsOfType(target.name, 'ne')?.entries() ?? [],\n ...this.getDependantsOfType(target.name, 'noteq')?.entries() ?? [],\n ];\n const condensedMaps = new Map(maps);\n condensedMaps.forEach((dependant, key) => {\n if (target.type === 'radio') {\n lock = String(key) !== String(rTarget.value);\n determineDisplayMap(dependant, displayMap, lock);\n return;\n } else if (target.type === 'hidden') {\n // This is the hidden input that is part of an advcheckbox.\n //hiddenVal = target.value === key;\n return;\n } else if (target.type === 'checkbox' && !target.checked) {\n lock = target.checked === Boolean(key);\n determineDisplayMap(dependant, displayMap, lock);\n return;\n }\n if (target.classList.contains('filepickerhidden')) {\n lock = !!M.form_filepicker.instances[target.id].fileadded;\n determineDisplayMap(dependant, displayMap, lock);\n } else if (target.tagName.toLowerCase() === 'select' && target.multiple) {\n // Multiple selects can have one or more value assigned. A pipe (|) is used as a value separator\n // when multiple values have to be selected at the same time.\n } else {\n lock = target.value !== key;\n determineDisplayMap(dependant, displayMap, lock);\n }\n });\n\n return this.form.displayMapPrune(displayMap);\n }\n\n // Moodle addons to the rules list.\n\n /**\n * Compare the value of the changed DOM node to the requested rule.\n *\n * @param {HTMLFormElement} target The changed DOM node to be compared against the requested rule.\n * @returns {Map} Actions to be taken along with elements that should be affected.\n */\n in(target) {\n const displayMap = this.form.mapTemplate();\n let lock = false;\n\n this.getDependantsOfType(target.name, 'in').forEach((dependant, key) => {\n lock = key.split('|').includes(target.value);\n determineDisplayMap(dependant, displayMap, lock);\n });\n\n return this.form.displayMapPrune(displayMap);\n }\n\n /**\n * Call off to the Form() to grab the elements that listen to the passed rule for the given element.\n *\n * @param {String} element The name of the element to get the dependants for.\n * @param {String} type The rule type to get the dependants for.\n * @returns {Array|[]}\n */\n getDependantsOfType(element, type) {\n return this.form.getDependantsOfType(element, type);\n }\n\n /**\n * Radio fields are a bit different, they need to be handled differently.\n *\n * @param {HTMLFormElement} target The changed DOM node to find a potential radio field for.\n * @returns {RadioNodeList|HTMLFormElement}\n */\n getRadioFieldVal(target) {\n return target.type === 'radio' ? this.form.form.elements.namedItem(target.name) : target;\n }\n\n /**\n * Constructor for the Rules class.\n * @param {Form} form The form object that the rules are being applied to.\n */\n constructor(form) {\n this.form = form;\n }\n}\n\n/**\n * A small object that defines the behaviour of the dependency rules for readability.\n *\n * @type {{hide: number, disable: number}}\n */\nconst dependencyBehaviour = {\n disable: 0,\n hide: 1,\n};\n\n/**\n * Considering the dependant object and if we need to lock it, assign the elements to the correct displayMap key.\n *\n * @param {Object} dependant The dependant object that contains the rules for hiding and locking.\n * @param {Map} displayMap The aggregation of elements that should be shown, hidden, locked, or unlocked.\n * @param {Boolean} lock According to the rules, should the element be locked or unlocked.\n */\nconst determineDisplayMap = (dependant, displayMap, lock) => {\n const hide = dependant.hasOwnProperty(dependencyBehaviour.hide) ? lock : false;\n if (dependant.hasOwnProperty(dependencyBehaviour.disable)) {\n //displayMap.get('show').push(dependant[dependencyBehaviour.disable]);\n if (lock) {\n displayMap.get('lock').push(dependant[dependencyBehaviour.disable]);\n } else {\n displayMap.get('unlock').push(dependant[dependencyBehaviour.disable]);\n }\n } else if (dependant.hasOwnProperty(dependencyBehaviour.hide)) {\n // Prevent showing an element if it has already been defined hidden.\n if (!hide && !displayMap.get('hide').toString().includes(dependant[dependencyBehaviour.hide].toString())) {\n displayMap.get('show').push(dependant[dependencyBehaviour.hide]);\n } else {\n displayMap.get('hide').push(dependant[dependencyBehaviour.hide]);\n }\n }\n};\n"],"names":["notchecked","target","displayMap","this","form","mapTemplate","lock","getDependantsOfType","name","forEach","dependant","key","Boolean","checked","determineDisplayMap","displayMapPrune","eq","rTarget","getRadioFieldVal","type","String","value","checksed","classList","contains","M","form_filepicker","instances","id","fileadded","tagName","toLowerCase","multiple","neq","maps","_this$getDependantsOf2","entries","_this$getDependantsOf4","_this$getDependantsOf6","Map","in","split","includes","element","elements","namedItem","constructor","dependencyBehaviour","hide","hasOwnProperty","get","push","toString"],"mappings":"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"} \ No newline at end of file diff --git a/lib/form/amd/src/form.js b/lib/form/amd/src/form.js index 5d6d7f04344ce..559a840e8a68f 100644 --- a/lib/form/amd/src/form.js +++ b/lib/form/amd/src/form.js @@ -33,6 +33,7 @@ import * as FormChangeChecker from './changechecker'; import * as Submit from './submit'; import Rules from './form/rules'; import * as MutateDom from './form/dom'; +import Pending from 'core/pending'; // Maybe not needed but noted just in case. // import * as CollapseSections from './collapsesections'; // This is included via ../collapsesections.mustache @@ -57,6 +58,7 @@ export default class Form { */ dependencies; + editors = new Map(); /** * Create a new form instance. * @@ -67,6 +69,7 @@ export default class Form { // Set class properties. this.form = document.querySelector(`#${formID}`); this.dependencies = this.getDependencyMapper(dependencies); + this.isEditor(); this.rules = new Rules(this); // Apply the initial state of the form. @@ -92,7 +95,7 @@ export default class Form { * Add event listeners to the form. */ registerEventListeners() { - this.form.addEventListener('change', (e) => { + this.form.addEventListener('change', async(e) => { if (e.target.type === 'submit') { FormChangeChecker.resetFormDirtyState(this.form); Submit.init(e.target.id); @@ -104,7 +107,10 @@ export default class Form { // Something changes based on this element. if (this.dependencies.has(e.target.name)) { FormChangeChecker.markFormChangedFromNode(e.target); - this.domDispatch(this.displayMapPrune(this.dispatchDependencyRules(e.target))); + // Add a pending? + const pendingPromise = new Pending('core/form:update'); + await this.domDispatch(this.displayMapPrune(this.dispatchDependencyRules(e.target))); + pendingPromise.resolve(); } }); } @@ -169,6 +175,7 @@ export default class Form { * @param {Map} elNamesMap What needs to change. */ domDispatch(elNamesMap) { + window.console.log('elNamesMap', elNamesMap); elNamesMap.forEach((elements, domUpdateOpt) => { if (!MutateDom[domUpdateOpt]) { return; @@ -193,11 +200,25 @@ export default class Form { return elementNames.map((element) => { // TODO: Rename and document why we need to deep look if there are multiple nodes effected by the rule. return element.map((element2) => { + if (this.isEditor(element2)) { + return this.form.elements.namedItem(`${element2}[text]`); + } return this.form.elements.namedItem(element2); }); }); } + isEditor(elementName = '') { + if (this.editors.size === 0) { + const fEditors = this.form.querySelectorAll('[data-fieldtype="editor"] textarea'); + Array.from(fEditors).forEach((node) => { + this.editors.set(node.name, true); + }); + } + + return this.editors.get(`${elementName}[text]`) || false; + } + /** * Convert the dependencies object into a map of elements and their associated rules. * diff --git a/lib/form/amd/src/form/dom.js b/lib/form/amd/src/form/dom.js index 5606a635d8124..7b5e4608901af 100644 --- a/lib/form/amd/src/form/dom.js +++ b/lib/form/amd/src/form/dom.js @@ -32,6 +32,9 @@ export const lock = (elements) => { elements.forEach(element => { if (element !== null) { element.setAttribute('disabled', 'disabled'); + // TODO: Editor check. + element.setAttribute('readonly', 'readonly'); + element.dispatchEvent(new Event('form:editorUpdated')); } }); }; @@ -45,6 +48,9 @@ export const unlock = (elements) => { elements.forEach(element => { if (element !== null) { element.removeAttribute('disabled'); + // TODO: Editor check. + element.removeAttribute('readonly'); + element.dispatchEvent(new Event('form:editorUpdated')); } }); }; @@ -56,7 +62,9 @@ export const unlock = (elements) => { */ export const hide = (elements) => { elements.forEach(element => { - if (element !== null) { + // TODO: RadioNodeList is not an instance of HTMLElement, need to find a way to handle this. + if (element !== null && element instanceof HTMLElement) { + element.setAttribute('disabled', 'disabled'); // Check if the element is part of a group otherwise, just grab the nearest wrapping element. const parent = element.closest('[data-groupname]') ? element.closest('[data-groupname]') : element.closest('.fitem'); if (parent) { @@ -70,7 +78,9 @@ export const hide = (elements) => { label.classList.add('d-none'); } } - } + }/* else { + window.console.log('Hide: Element is not an instance of HTMLElement', element); + }*/ }); }; @@ -81,7 +91,9 @@ export const hide = (elements) => { */ export const show = (elements) => { elements.forEach(element => { - if (element !== null) { + // TODO: RadioNodeList is not an instance of HTMLElement, need to find a way to handle this. + if (element !== null && element instanceof HTMLElement) { + element.removeAttribute('disabled'); // Check if the element is part of a group otherwise, just grab the nearest wrapping element. const parent = element.closest('[data-groupname]') ? element.closest('[data-groupname]') : element.closest('.fitem'); if (parent) { @@ -95,6 +107,8 @@ export const show = (elements) => { label.classList.remove('d-none'); } } - } + }/* else { + window.console.log('Show: Element is not an instance of HTMLElement', element); + }*/ }); }; diff --git a/lib/form/amd/src/form/rules.js b/lib/form/amd/src/form/rules.js index 1317725007959..045dd89751aa1 100644 --- a/lib/form/amd/src/form/rules.js +++ b/lib/form/amd/src/form/rules.js @@ -80,11 +80,13 @@ export default class Rules { */ eq(target) { const displayMap = this.form.mapTemplate(); - const ctarget = this.getRadioFieldVal(target); + const rTarget = this.getRadioFieldVal(target); let lock = false; this.getDependantsOfType(target.name, 'eq').forEach((dependant, key) => { - if (target.type === 'radio' && String(key) !== String(ctarget.value)) { + if (target.type === 'radio') { + lock = String(key) === String(rTarget.value); + determineDisplayMap(dependant, displayMap, lock); return; } else if (target.type === 'hidden') { // This is the hidden input that is part of an advcheckbox. @@ -119,7 +121,7 @@ export default class Rules { */ neq(target) { const displayMap = this.form.mapTemplate(); - const ctarget = this.getRadioFieldVal(target); + const rTarget = this.getRadioFieldVal(target); let lock = false; // Get all the aliases of neq and check them all at once. @@ -130,7 +132,9 @@ export default class Rules { ]; const condensedMaps = new Map(maps); condensedMaps.forEach((dependant, key) => { - if (target.type === 'radio' && String(key) !== String(ctarget.value)) { + if (target.type === 'radio') { + lock = String(key) !== String(rTarget.value); + determineDisplayMap(dependant, displayMap, lock); return; } else if (target.type === 'hidden') { // This is the hidden input that is part of an advcheckbox. @@ -226,17 +230,18 @@ const dependencyBehaviour = { const determineDisplayMap = (dependant, displayMap, lock) => { const hide = dependant.hasOwnProperty(dependencyBehaviour.hide) ? lock : false; if (dependant.hasOwnProperty(dependencyBehaviour.disable)) { - displayMap.get('show').push(dependant[dependencyBehaviour.disable]); + //displayMap.get('show').push(dependant[dependencyBehaviour.disable]); if (lock) { displayMap.get('lock').push(dependant[dependencyBehaviour.disable]); } else { displayMap.get('unlock').push(dependant[dependencyBehaviour.disable]); } } else if (dependant.hasOwnProperty(dependencyBehaviour.hide)) { - if (hide) { - displayMap.get('hide').push(dependant[dependencyBehaviour.hide]); - } else { + // Prevent showing an element if it has already been defined hidden. + if (!hide && !displayMap.get('hide').toString().includes(dependant[dependencyBehaviour.hide].toString())) { displayMap.get('show').push(dependant[dependencyBehaviour.hide]); + } else { + displayMap.get('hide').push(dependant[dependencyBehaviour.hide]); } } }; diff --git a/lib/form/tests/behat/fixtures/form_rules.php b/lib/form/tests/behat/fixtures/form_rules.php index f672fec85df2b..0558eeef1cf52 100644 --- a/lib/form/tests/behat/fixtures/form_rules.php +++ b/lib/form/tests/behat/fixtures/form_rules.php @@ -79,7 +79,7 @@ public function definition() { $mform->addElement('button', 'checked_hidden_btn', 'Checked hidden'); $mform->hideIf('checked_hidden_btn', 'hidden_ckb', 'checked'); - $mform->addElement('text', 'checked_hidden_txt', 'Text input'); + $mform->addElement('text', 'checked_hidden_txt', 'Label test'); $mform->setType('checked_hidden_txt', PARAM_TEXT); $mform->hideIf('checked_hidden_txt', 'hidden_ckb', 'checked'); @@ -164,7 +164,9 @@ public function definition() { // Editor rule test. $mform->addElement('header', 'editorheader', 'Editor: ~'); - //$mform->setExpanded('editorheader'); + $mform->setExpanded('editorheader'); + $mform->addElement('checkbox', 'edt_enb', get_string('enable')); + $mform->addElement('checkbox', 'edt_dis', get_string('hide')); $editoroptions = [ 'subdirs' => 0, 'maxbytes' => 0, @@ -176,6 +178,8 @@ public function definition() { ]; $mform->addElement('editor', 'edt', 'Editor for testing', $editoroptions); $mform->setDefault('edt', ['text' => 'Hello world!', 'format' => FORMAT_HTML]); + $mform->disabledIf("edt", 'edt_enb'); + $mform->hideIf("edt", 'edt_dis'); // Filepicker rule test. $mform->addElement('header', 'filepickerheader', 'Filepicker: ~'); diff --git a/lib/form/tests/behat/form_rules.feature b/lib/form/tests/behat/form_rules.feature index 7da1d3d2ca181..5b55c66d12492 100644 --- a/lib/form/tests/behat/form_rules.feature +++ b/lib/form/tests/behat/form_rules.feature @@ -7,6 +7,7 @@ Feature: Test the form rules Background: Given I log in as "admin" And I am on fixture page "/lib/form/tests/behat/fixtures/form_rules.php" + And I wait until the page is ready @javascript Scenario Outline: Checkbox checked & notchecked rules