Skip to content

Commit 971a197

Browse files
Merge pull request #6 from webexpress-framework/develop
0.0.10-alpha
2 parents dbbeeb8 + cb48fe7 commit 971a197

540 files changed

Lines changed: 39768 additions & 11790 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/js/cascading.md

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
![WebExpress](https://raw.githubusercontent.com/webexpress-framework/.github/main/docs/assets/img/banner.png)
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

Comments
 (0)