|
| 1 | + |
| 2 | + |
| 3 | +# InputCascadingCtrl |
| 4 | + |
| 5 | +This document describes the `InputCascadingCtrl` component, which implements a multi-level cascading selection. The control reads a static options structure from the DOM hierarchy of `.wx-selection-item` elements, renders an `InputSelectionCtrl` per level, and ensures that when a selection is made at one level the immediate child options are loaded and displayed. The implementation is intended for declarative initialization in HTML and allows programmatic control via the associated controller instance. |
| 6 | + |
| 7 | +``` |
| 8 | + ┌──────────────┐ |
| 9 | + │ Level 0 ▼│ |
| 10 | + └──────────────┘ |
| 11 | + ┌──────────────┐ |
| 12 | + │ Level 1 ▼│ |
| 13 | + └──────────────┘ |
| 14 | + ┌──────────────┐ |
| 15 | + │ Level ... ▼│ |
| 16 | + └──────────────┘ |
| 17 | +``` |
| 18 | + |
| 19 | +## Declarative configuration |
| 20 | + |
| 21 | +The component is activated by a host element with the class `wx-webui-input-cascading`. Initial options are extracted from the host element’s direct `.wx-selection-item` children. Configuration elements: |
| 22 | + |
| 23 | +| Attribute | Description |
| 24 | +|-----------------------|--------------------------------------------------- |
| 25 | +| `name` | Name for the hidden input used for form submission. |
| 26 | +| `placeholder` | Placeholder text for each rendered InputSelection host. |
| 27 | +| `data-multiselection` | Removed during internal setup; the component operates with single selection per level. |
| 28 | + |
| 29 | +### Definition of an option node |
| 30 | + |
| 31 | +A `.wx-selection-item` represents an option node within the hierarchical selection tree. Nodes may be nested to arbitrary depth. Each nesting level corresponds to a selection stage that is rendered as its own `InputSelection` element. A node without child elements is considered a terminal node. |
| 32 | + |
| 33 | +| Attribute | Description |
| 34 | +|--------------------|------------------------------------------------------------------------------- |
| 35 | +| `id` | Unique identifier of the node (important for path formation and child lookup). |
| 36 | +| `data-label` | Visible label text (alternatively the element’s direct text node is used). |
| 37 | +| `data-label-color` | Optional color class for the label. |
| 38 | +| `data-icon` | Optional icon class. |
| 39 | +| `data-image` | Optional image URL. |
| 40 | +| `disabled` | Presence indicates the element is disabled. |
| 41 | + |
| 42 | +### Additional structural notes |
| 43 | + |
| 44 | +The order of nodes determines the order of displayed options. |
| 45 | + |
| 46 | +- Multiple child elements under a `.wx-selection-item` produce the next selection level. |
| 47 | +- Empty nodes (without label and without children) should be avoided because they may cause inconsistent UI states. |
| 48 | +- IDs must be unique across the entire tree, not only within a single level. |
| 49 | +- Internally the component constructs a selection path such as `continent/europe/germany/berlin` based on the `id` attributes. |
| 50 | + |
| 51 | +Example (declarative, HTML): |
| 52 | + |
| 53 | +```html |
| 54 | +<div class="wx-webui-input-cascading" name="location" placeholder="Please choose"> |
| 55 | + <div class="wx-selection-item" id="country_de" data-label="Germany"> |
| 56 | + <div class="wx-selection-item" id="state_by" data-label="Bavaria"> |
| 57 | + <div class="wx-selection-item" id="city_muc" data-label="Munich"></div> |
| 58 | + <div class="wx-selection-item" id="city_nbg" data-label="Nuremberg"></div> |
| 59 | + </div> |
| 60 | + </div> |
| 61 | + <div class="wx-selection-item" id="country_us" data-label="USA"> |
| 62 | + <div class="wx-selection-item" id="state_ca" data-label="California"> |
| 63 | + <div class="wx-selection-item" id="city_sf" data-label="San Francisco"></div> |
| 64 | + <div class="wx-selection-item" id="city_la" data-label="Los Angeles"></div> |
| 65 | + </div> |
| 66 | + </div> |
| 67 | + <div class="wx-selection-item" id="country_at" data-label="Austria"> |
| 68 | + <div class="wx-selection-item" id="state_v" data-label="Vorarlberg"></div> |
| 69 | + </div> |
| 70 | +</div> |
| 71 | +``` |
| 72 | + |
| 73 | +## Programmatic control |
| 74 | + |
| 75 | +The controller instance is retrievable via the host element. The `value` accessor returns and sets the current path. |
| 76 | + |
| 77 | +### Accessing an Automatically Created Instance |
| 78 | + |
| 79 | +When the component is initialized declaratively through a host element with the class `wx-webui-input-cascading`, the corresponding controller instance can be retrieved programmatically. The controller exposes the current selection path through the value accessor and also allows updating it. |
| 80 | + |
| 81 | +Example: obtain instance and set value. |
| 82 | + |
| 83 | +```javascript |
| 84 | +// retrieve controller instance from host element |
| 85 | +const host = document.querySelector(".wx-webui-input-cascading"); |
| 86 | +const ctrl = webexpress.webui.Controller.getInstanceByElement(host); |
| 87 | +if (ctrl) { |
| 88 | + // set a path by array of ids (single-selection-per-level enforced) |
| 89 | + ctrl.value = ["country_de", "state_by", "city_muc"]; |
| 90 | + // set a path by semicolon-separated string |
| 91 | + ctrl.value = "country_de;state_by;city_muc"; |
| 92 | + // clear selection |
| 93 | + ctrl.value = null; |
| 94 | +} |
| 95 | +``` |
| 96 | + |
| 97 | +### Manual Instantiation |
| 98 | + |
| 99 | +Manual instantiation is useful when the component needs to be created dynamically in JavaScript and attached to an existing or newly constructed host element structure. It is important to note that the constructor parses the DOM hierarchy of the .wx-selection-item children at the moment the instance is created, so the desired structure must already exist before the controller instance is initialized. |
| 100 | + |
| 101 | +Example: |
| 102 | + |
| 103 | +``` |
| 104 | +// create host element and populate with selection items |
| 105 | +const host = document.createElement("div"); |
| 106 | +host.classList.add("wx-webui-input-cascading"); |
| 107 | +host.setAttribute("name", "location"); |
| 108 | +host.setAttribute("placeholder", "Please choose"); |
| 109 | +
|
| 110 | +// insert hierarchical .wx-selection-item nodes before instantiation |
| 111 | +host.innerHTML = '' |
| 112 | + + '<div class="wx-selection-item" id="country_de" data-label="Germany">' |
| 113 | + + ' <div class="wx-selection-item" id="state_by" data-label="Bavaria">' |
| 114 | + + ' <div class="wx-selection-item" id="city_muc" data-label="Munich"></div>' |
| 115 | + + ' <div class="wx-selection-item" id="city_nbg" data-label="Nuremberg"></div>' |
| 116 | + + ' </div>' |
| 117 | + + '</div>' |
| 118 | + + '<div class="wx-selection-item" id="country_us" data-label="USA">' |
| 119 | + + ' <div class="wx-selection-item" id="state_ca" data-label="California">' |
| 120 | + + ' <div class="wx-selection-item" id="city_sf" data-label="San Francisco"></div>' |
| 121 | + + ' <div class="wx-selection-item" id="city_la" data-label="Los Angeles"></div>' |
| 122 | + + ' </div>' |
| 123 | + + '</div>' |
| 124 | + + '<div class="wx-selection-item" id="country_at" data-label="Austria">' |
| 125 | + + ' <div class="wx-selection-item" id="state_v" data-label="Vorarlberg"></div>' |
| 126 | + + '</div>'; |
| 127 | +
|
| 128 | +// attach host to the document so that any measurements or rendering work |
| 129 | +document.body.appendChild(host); |
| 130 | +
|
| 131 | +// instantiate the controller; constructor will parse the DOM and render level 0 |
| 132 | +const ctrl = new webexpress.webui.InputCascadingCtrl(host); |
| 133 | +
|
| 134 | +// programmatic operations after instantiation |
| 135 | +if (ctrl) { |
| 136 | + // set selection path by array of ids |
| 137 | + ctrl.value = ["country_de", "state_by", "city_muc"]; |
| 138 | +
|
| 139 | + // or set by semicolon-separated string |
| 140 | + ctrl.value = "country_de;state_by;city_muc"; |
| 141 | +
|
| 142 | + // clear selection |
| 143 | + ctrl.value = null; |
| 144 | +} |
| 145 | +
|
| 146 | +// listen for changes emitted by the control |
| 147 | +host.addEventListener(webexpress.webui.Event.CHANGE_VALUE_EVENT, function (e) { |
| 148 | + // handle path change |
| 149 | + console.log("current path:", e.detail.value); |
| 150 | +}); |
| 151 | +``` |
| 152 | + |
| 153 | +## Events |
| 154 | + |
| 155 | +The component dispatches standardized events to notify about changes. |
| 156 | + |
| 157 | +- `webexpress.webui.Event.CHANGE_VALUE_EVENT`: Fired when the overall selection (path) changes. The event detail contains the current path as an array of ids. |
| 158 | + |
| 159 | +Example of listening: |
| 160 | + |
| 161 | +```javascript |
| 162 | +// listen for path changes on the host element |
| 163 | +host.addEventListener(webexpress.webui.Event.CHANGE_VALUE_EVENT, function (e) { |
| 164 | + // detail.value contains array of selected ids |
| 165 | + console.log("current path:", e.detail.value); |
| 166 | +}); |
| 167 | +``` |
| 168 | + |
| 169 | +## Use cases and styling |
| 170 | + |
| 171 | +The component is suitable for selection chains such as country → state → city, category → subcategory → product, etc. Styling is applied via the usual CSS classes of the host element and the rendered InputSelection instances. Icons, images and label colors are taken from the data attributes of the `.wx-selection-item` nodes. |
| 172 | + |
| 173 | +## Common causes & troubleshooting |
| 174 | + |
| 175 | +If selecting an item at level 0 does not display level 1, common causes include: |
| 176 | + |
| 177 | +- Missing or non-unique `id` attributes on `.wx-selection-item` nodes. The component uses `id` to resolve child nodes. |
| 178 | +- Mismatch between the return formats of `InputSelectionCtrl` and the expected key values. Some implementations of `InputSelectionCtrl` return primitive values (e.g. strings) in the `value` array; others return objects like `{ value: "...", label: "..." }`. If only `id` fields are set without an additional `value` field in the option objects, later comparison logic may fail to correctly map the selection. |
| 179 | + |
| 180 | +Recommendation: When setting the option objects on `InputSelectionCtrl`, ensure each option includes a `value` field, and normalize both shapes (primitive and object-with-value) when evaluating the selection. A possible correction example in vanilla JS: |
| 181 | + |
| 182 | +```javascript |
| 183 | +// example: build options for InputSelectionCtrl |
| 184 | +const items = nodes.map(function (node) { |
| 185 | + return { |
| 186 | + // include value so the selection widget returns a comparable value |
| 187 | + value: node.id, |
| 188 | + id: node.id, |
| 189 | + label: node.label, |
| 190 | + labelColor: node.labelColor, |
| 191 | + icon: node.icon, |
| 192 | + image: node.image, |
| 193 | + content: node.content, |
| 194 | + disabled: node.disabled |
| 195 | + }; |
| 196 | +}); |
| 197 | + |
| 198 | +// example: normalize selection returned by selectionCtrl |
| 199 | +// selectionCtrl.value might be e.g. ["country_de"] or [{ value: "country_de", label: "Germany" }] |
| 200 | +function normalizeSelectedFirst(selectionValueArray) { |
| 201 | + // inline comment: normalize possible return shapes (primitive or object with value) |
| 202 | + if (selectionValueArray && selectionValueArray.length > 0) { |
| 203 | + const first = selectionValueArray[0]; |
| 204 | + if (first && typeof first === "object" && ("value" in first)) { |
| 205 | + return first.value; |
| 206 | + } |
| 207 | + return first; |
| 208 | + } |
| 209 | + return null; |
| 210 | +} |
| 211 | +``` |
0 commit comments