{"id":1505,"date":"2025-04-17T05:50:00","date_gmt":"2025-04-17T10:50:00","guid":{"rendered":"https:\/\/automatethemundane.com\/index.php\/2025\/04\/17\/cascading-choice-drop-downs-in-mda\/"},"modified":"2025-04-17T05:50:00","modified_gmt":"2025-04-17T10:50:00","slug":"cascading-choice-drop-downs-in-mda","status":"publish","type":"post","link":"https:\/\/automatethemundane.com\/index.php\/2025\/04\/17\/cascading-choice-drop-downs-in-mda\/","title":{"rendered":"Cascading Choice Drop downs in MDA"},"content":{"rendered":"\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">I was working on a project recently where a developer wanted to have a drop down filter another drop down within a Model Driven App (MDA) form. There are some great ways to do this for lookups, but it took me a bit to see how it would work for drop downs. In the end we ended up creating a simple JavaScript that would map a parent choice with a child choice. <\/p>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n<h1 class=\"wp-block-heading\">Table Setup<\/h1>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">I have built a new table called Magic Choices with two choice columns in it, Parent column and Child column. The Parent column has the following choices, A,B,C,D  The Child column has the following choices, Aa,Ab,Ac,Ba,Bb,Bc,Ca,Cb,Cc. The idea is that anytime the Parent column has A in it, the child choices Aa,Ab,Ac are shown. If B, Ba,Bb,Bc, are shown. If C, Ca,Cb,Cc are shown. If D, then all child choices are shown. <\/p>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-9d6595d7 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2025\/04\/image-18.png\" alt=\"\"\/><\/figure>\n\n<\/div>\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2025\/04\/image-19.png\" alt=\"\"\/><\/figure>\n\n<\/div>\n\n<\/div>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2025\/04\/image-20-1024x315.png\" alt=\"\"\/><\/figure>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n<h1 class=\"wp-block-heading\">The Code<\/h1>\n\n\n<ol class=\"wp-block-list\">\n<li>The first part of the script will cache all child choices using the onload of the form\n<pre class=\"wp-block-code\"><code>var originalChildOptions = [];\n\nfunction onLoadFilterSetup(executionContext) {\n    var formContext = executionContext.getFormContext();\n    var childCtrl = formContext.getControl(&quot;andy_childchoice&quot;);\n\n    if (originalChildOptions.length === 0) {\n        originalChildOptions = childCtrl.getOptions();\n    }\n\n    filterChildOptions(executionContext);\n}<\/code><\/pre>\n\n\n<p>&nbsp;<\/p>\n<\/li>\n\n\n\n<li>The next part will execute on the on change of the parent choice drop down. It will get the parent choice and the child choice, if it finds the choices then it will continue. \n<pre class=\"wp-block-code\"><code>function filterChildOptions(executionContext) {\nvar formContext = executionContext.getFormContext();\nvar parentAttr = formContext.getAttribute(&quot;andy_parentchoice&quot;);\nvar childAttr = formContext.getAttribute(&quot;andy_childchoice&quot;);\nvar childCtrl = formContext.getControl(&quot;andy_childchoice&quot;);\n\nif (!parentAttr || !childCtrl) return;\n<\/code><\/pre>\n\n\n<p>&nbsp;<\/p>\n<\/li>\n\n\n\n<li>Then we need to go out and clear the options of the child choice. There is no default way to do this, so we need to loop through all the choices. \n<pre class=\"wp-block-code\"><code>for (var i = childCtrl.getOptions().length - 1; i &gt;= 0; i--) {\n    childCtrl.removeOption(childCtrl.getOptions()[i].value);\n}<\/code><\/pre>\n\n\n<p>&nbsp;<\/p>\n<\/li>\n\n\n\n<li>With the choices cleared we need to add the options back in once checked. \n<pre class=\"wp-block-code\"><code>for (var i = 0; i &lt; originalChildOptions.length; i++) {\n    childCtrl.addOption(originalChildOptions[i]);\n}<\/code><\/pre>\n\n\n<p>&nbsp;<\/p>\n<\/li>\n\n\n\n<li>A matrix of all the parent choices to child choices is made\n<pre class=\"wp-block-code\"><code>var visibleOptions = {\n    435700000: [435700000, 435700001, 435700002],\n    435700001: [435700003, 435700004, 435700005],\n    435700002: [435700006, 435700007, 435700008],\n};<\/code><\/pre>\n\n\n<p>&nbsp;<\/p>\n<\/li>\n\n\n\n<li>With the matrix loaded we remove anything not in the current allowed list. This keeps the dropdown clean and relevant.\n<pre class=\"wp-block-code\"><code>if (allowed) {\n    for (var i = 0; i &lt; originalChildOptions.length; i++) {\n        var option = originalChildOptions[i];\n        if (!allowed.includes(option.value)) {\n            childCtrl.removeOption(option.value);\n        }\n    }\n<\/code><\/pre>\n\n\n<p>&nbsp;<\/p>\n<\/li>\n\n\n\n<li>The last part will allow for all choices to be shown if no parent is selected, or if an un mapped choice is selected. \n<pre class=\"wp-block-code\"><code>    if (!allowed.includes(childAttr.getValue())) {\n        childAttr.setValue(null);\n    }\n}<\/code><\/pre>\n\n\n<p>&nbsp;<\/p>\n<\/li>\n<\/ol>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Global variable to cache all child options\nvar originalChildOptions = [];\n\nfunction onLoadFilterSetup(executionContext) {\n    var formContext = executionContext.getFormContext();\n    var childCtrl = formContext.getControl(&quot;andy_childchoice&quot;);\n\n    \/\/ Cache original child options (only once)\n    if (originalChildOptions.length === 0) {\n        originalChildOptions = childCtrl.getOptions();\n    }\n\n    filterChildOptions(executionContext); \/\/ Run filtering initially too\n}\n\nfunction filterChildOptions(executionContext) {\n    var formContext = executionContext.getFormContext();\n    var parentAttr = formContext.getAttribute(&quot;andy_parentchoice&quot;);\n    var childAttr = formContext.getAttribute(&quot;andy_childchoice&quot;);\n    var childCtrl = formContext.getControl(&quot;andy_childchoice&quot;);\n\n    if (!parentAttr || !childCtrl) return;\n\n    var parentValue = parentAttr.getValue();\n\n    \/\/ First, restore all original options\n    childCtrl.clearOptions(); \/\/ This still doesn&#039;t work, so instead we:\n    for (var i = childCtrl.getOptions().length - 1; i &gt;= 0; i--) {\n        childCtrl.removeOption(childCtrl.getOptions()[i].value);\n    }\n\n    for (var i = 0; i &lt; originalChildOptions.length; i++) {\n        childCtrl.addOption(originalChildOptions[i]);\n    }\n\n    \/\/ Define which values should be visible based on parent\n    var visibleOptions = {\n        435700000: [435700000, 435700001, 435700002], \/\/ A \u2192 Aa, Ab, Ac\n        435700001: [435700003, 435700004, 435700005], \/\/ B \u2192 Ba, Bb, Bc\n        435700002: [435700006, 435700007, 435700008], \/\/ C \u2192 Ca, Cb, Cc\n    };\n\n    var allowed = visibleOptions[parentValue];\n\n    if (allowed) {\n        for (var i = 0; i &lt; originalChildOptions.length; i++) {\n            var option = originalChildOptions[i];\n            if (!allowed.includes(option.value)) {\n                childCtrl.removeOption(option.value);\n            }\n        }\n\n        \/\/ Optionally reset child value if current value isn&#039;t allowed\n        if (!allowed.includes(childAttr.getValue())) {\n            childAttr.setValue(null);\n        }\n    }\n}\n<\/code><\/pre>\n\n\n<p>&nbsp;<\/p>\n\n\n<h1 class=\"wp-block-heading\">Form and Business Rules<\/h1>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">With the code written we can add it to the form as a web resource. Then in the form set the On Load to onLoadFilterSetup.<\/p>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2025\/04\/image-21-1024x566.png\" alt=\"\"\/><\/figure>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">Within the parent choice set the On Change to filterChildOptions<\/p>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2025\/04\/image-22-1024x522.png\" alt=\"\"\/><\/figure>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">I have also added a business rule to the parent choice to hide the child choice if no selection is made<\/p>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2025\/04\/image-23-1024x399.png\" alt=\"\"\/><\/figure>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n<p class=\"has-background\" style=\"background-color: rgb(241, 241, 239)\">&#x1f4a1; This part could have also been done within the JavaScript, but I like to use business rules where I can<\/p>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n<h1 class=\"wp-block-heading\">App<\/h1>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">With it all put together, the Child choice is hidden if no parent is selected. <\/p>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2025\/04\/image-24-1024x327.png\" alt=\"\"\/><\/figure>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">When a choice is made it is displayed and the choice is filtered or not depending on the parent selection. <\/p>\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2025\/04\/image-25-1024x299.png\" alt=\"\"\/><\/figure>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"https:\/\/automatethemundane.com\/wp-content\/uploads\/2025\/04\/image-26-1024x374.png\" alt=\"\"\/><\/figure>\n\n\n<p class=\"has-text-color\" style=\"color: rgb(0, 0, 0)\">&nbsp;<\/p>\n\n\n<p class=\"has-background\" style=\"background-color: rgb(241, 241, 239)\">&#x1f4a1; If the Parent choice is changed and the child is not within the parents group the child selection will be cleared. <\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>I was working on a project recently where a developer wanted to have a drop down filter another drop down within a Model Driven App (MDA) form. There are some great ways to do this for lookups, but it took me a bit to see how it would work for drop downs. In the end [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1485,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[22,23,55],"tags":[],"class_list":["post-1505","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dataverse","category-forms","category-javascript","entry","has-media"],"jetpack_featured_media_url":"https:\/\/automatethemundane.com\/wp-content\/uploads\/2025\/04\/photo-1569011312883-031325587704-scaled.jpg","_links":{"self":[{"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/posts\/1505","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/comments?post=1505"}],"version-history":[{"count":0,"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/posts\/1505\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/media\/1485"}],"wp:attachment":[{"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/media?parent=1505"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/categories?post=1505"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/automatethemundane.com\/index.php\/wp-json\/wp\/v2\/tags?post=1505"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}