html-form-field 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +102 -0
- package/dist/html-form-field.cjs +353 -0
- package/dist/html-form-field.min.js +1 -0
- package/dist/html-form-field.mjs +351 -0
- package/dist/package.json +1 -0
- package/package.json +64 -0
- package/src/form-field.ts +207 -0
- package/src/form-item.ts +148 -0
- package/src/index.ts +5 -0
- package/types/html-form-field.d.ts +186 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
const triggerOnWrite$1 = (field) => {
|
|
2
|
+
const onWrite = field.options.onWrite;
|
|
3
|
+
if (onWrite) onWrite(field);
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
const checkableMap = {
|
|
7
|
+
option: true,
|
|
8
|
+
radio: true,
|
|
9
|
+
checkbox: true,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
const isElementOf = (v, tagName) => ((v ).tagName === tagName);
|
|
15
|
+
|
|
16
|
+
const formItemList = (field, nodeList) => {
|
|
17
|
+
const list = [];
|
|
18
|
+
|
|
19
|
+
for (const node of nodeList) {
|
|
20
|
+
if (isElementOf(node, "SELECT")) {
|
|
21
|
+
for (const option of node.options) {
|
|
22
|
+
list.push(new OptionItem(field, option));
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
list.push(new InputItem(field, node));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return list
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
class BaseFormItem {
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
constructor(field, node) {
|
|
38
|
+
this.field = field;
|
|
39
|
+
this.node = node;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get value() {
|
|
43
|
+
return this.node.value
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
set value(value) {
|
|
47
|
+
this.setValue(value);
|
|
48
|
+
triggerOnWrite$1(this.field);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setValue(value) {
|
|
52
|
+
this.node.value = value;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get checked() {
|
|
56
|
+
return this.getChecked()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
set checked(checked) {
|
|
62
|
+
const prev = this.checked;
|
|
63
|
+
if (prev !== checked) {
|
|
64
|
+
this.setChecked(checked);
|
|
65
|
+
triggerOnWrite$1(this.field);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
get disabled() {
|
|
72
|
+
return this.node.disabled
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
set disabled(disabled) {
|
|
76
|
+
this.node.disabled = disabled;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class InputItem extends BaseFormItem {
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
constructor(entry, node) {
|
|
86
|
+
super(entry, node);
|
|
87
|
+
this.checkable = checkableMap[node.type];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getChecked() {
|
|
91
|
+
return (this.node ).checked
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
setChecked(checked) {
|
|
95
|
+
(this.node ).checked = checked;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
get label() {
|
|
99
|
+
const node = this.node;
|
|
100
|
+
const label = node.closest("label");
|
|
101
|
+
const siblings = label && label.querySelectorAll(node.tagName);
|
|
102
|
+
|
|
103
|
+
if (siblings && siblings.length === 1) {
|
|
104
|
+
const text = pickText(label);
|
|
105
|
+
if (text) return text
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const labels = node.labels;
|
|
109
|
+
for (const label of labels) {
|
|
110
|
+
const text = pickText(label);
|
|
111
|
+
if (text) return text
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const text = pickText(node);
|
|
115
|
+
if (text) return text
|
|
116
|
+
|
|
117
|
+
function pickText(node) {
|
|
118
|
+
const text = node.innerText || node.textContent;
|
|
119
|
+
if (text) return text.trim()
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
class OptionItem extends BaseFormItem {
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
constructor(entry, node) {
|
|
128
|
+
super(entry, node);
|
|
129
|
+
this.checkable = true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
getChecked() {
|
|
133
|
+
return this.node.selected
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
setChecked(checked) {
|
|
137
|
+
// Note: Setting false may trigger auto-selection of the first option on select-one
|
|
138
|
+
this.node.selected = checked;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
get label() {
|
|
142
|
+
const node = this.node;
|
|
143
|
+
const text = node.label || node.textContent;
|
|
144
|
+
if (text) return text.trim()
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const DELIM = ","; // or use Unit Separator `\x1F` instead
|
|
149
|
+
|
|
150
|
+
const isString = (v) => ("string" === typeof v);
|
|
151
|
+
|
|
152
|
+
const noNullString = (v) => (v == null) ? "" : isString(v) ? v : String(v);
|
|
153
|
+
|
|
154
|
+
const splitString = (v, delim) => (v == null) ? [] : Array.isArray(v) ? v.map(noNullString) : noNullString(v).split(delim);
|
|
155
|
+
|
|
156
|
+
const isHTMLElement = (v) => ("function" === typeof (v ).matches);
|
|
157
|
+
|
|
158
|
+
const formField = (options) => {
|
|
159
|
+
return new FormBridgeImpl(options)
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const getNodeList = ({form, name}) => {
|
|
163
|
+
const safeName = JSON.stringify(name);
|
|
164
|
+
if (!name) {
|
|
165
|
+
throw new Error(`Invalid name=${safeName}`)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const selector = `input[name=${safeName}], textarea[name=${safeName}], button[name=${safeName}], select[name=${safeName}]`;
|
|
169
|
+
|
|
170
|
+
const nodeList = form.querySelectorAll(selector);
|
|
171
|
+
if (!nodeList.length) {
|
|
172
|
+
if (isHTMLElement(form) && form.matches(selector)) {
|
|
173
|
+
return [form]
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
throw new Error(`Not found: name=${safeName}`)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return nodeList
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const updateEventListener = (nodeList, handler) => {
|
|
183
|
+
for (const node of nodeList) {
|
|
184
|
+
node.removeEventListener("change", handler);
|
|
185
|
+
node.addEventListener("change", handler);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const triggerOnWrite = (field) => {
|
|
190
|
+
const onWrite = field.options.onWrite;
|
|
191
|
+
if (onWrite) onWrite(field);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const triggerOnChange = (field) => {
|
|
195
|
+
const onChange = field.options.onChange;
|
|
196
|
+
if (onChange) onChange(field);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const applyBindTo = (bindTo, name, field) => {
|
|
200
|
+
if (!bindTo) return // nothing to be bound
|
|
201
|
+
|
|
202
|
+
delete bindTo[name ];
|
|
203
|
+
|
|
204
|
+
const getValue = () => field.value;
|
|
205
|
+
|
|
206
|
+
const setValue = (value) => {
|
|
207
|
+
field.value = value;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
Object.defineProperty(bindTo, name, {
|
|
211
|
+
get: getValue,
|
|
212
|
+
set: setValue,
|
|
213
|
+
enumerable: true,
|
|
214
|
+
configurable: true,
|
|
215
|
+
});
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
class FormBridgeImpl {
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
constructor(options = {} ) {
|
|
226
|
+
this.options = options;
|
|
227
|
+
const name = this.name = options.name;
|
|
228
|
+
applyBindTo(options.bindTo, name, this);
|
|
229
|
+
|
|
230
|
+
const _onChange = this._onChange = () => {
|
|
231
|
+
triggerOnWrite(this);
|
|
232
|
+
triggerOnChange(this);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const nodeList = getNodeList(options);
|
|
236
|
+
updateEventListener(nodeList, _onChange);
|
|
237
|
+
const items = this._items = formItemList(this, nodeList);
|
|
238
|
+
|
|
239
|
+
const defaults = options.defaults;
|
|
240
|
+
if (defaults) {
|
|
241
|
+
if (items[0].checkable) {
|
|
242
|
+
for (const v of defaults) {
|
|
243
|
+
if (this.setValue(v)) break
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
this.setValue(defaults);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
get value() {
|
|
252
|
+
const values = this.current().map(v => v.value);
|
|
253
|
+
if (values.length > 1) {
|
|
254
|
+
const delim = this.options.delim || DELIM;
|
|
255
|
+
return values.join(delim)
|
|
256
|
+
} else {
|
|
257
|
+
return values[0]
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
set value(value) {
|
|
262
|
+
this.setValue(value);
|
|
263
|
+
triggerOnWrite(this);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
setValue(value) {
|
|
267
|
+
const items = this.items();
|
|
268
|
+
|
|
269
|
+
if (items.length === 1 && !items[0].checkable && isString(value)) {
|
|
270
|
+
value = [value];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const delim = this.options.delim || DELIM;
|
|
274
|
+
const values = splitString(value, delim);
|
|
275
|
+
let i = 0;
|
|
276
|
+
let assigned = 0;
|
|
277
|
+
|
|
278
|
+
for (const item of items) {
|
|
279
|
+
if (item.checkable) {
|
|
280
|
+
const checked = values.includes(item.value);
|
|
281
|
+
if (checked) assigned++;
|
|
282
|
+
if (item.checked !== checked) {
|
|
283
|
+
item.setChecked(checked);
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
const value = values[i++];
|
|
287
|
+
const isNull = (value == null);
|
|
288
|
+
item.setValue(isNull ? "" : value);
|
|
289
|
+
if (!isNull) assigned++;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return !!assigned
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
current() {
|
|
297
|
+
return this.items().filter(v => (!v.disabled && (!v.checkable || v.checked)))
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
reload() {
|
|
301
|
+
const nodeList = getNodeList(this.options);
|
|
302
|
+
const _onChange = this._onChange;
|
|
303
|
+
updateEventListener(nodeList, _onChange);
|
|
304
|
+
this._items = formItemList(this, nodeList);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
items() {
|
|
308
|
+
return this._items
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
toggle(value, checked) {
|
|
312
|
+
let result;
|
|
313
|
+
const delim = this.options.delim || DELIM;
|
|
314
|
+
const values = splitString(value, delim);
|
|
315
|
+
|
|
316
|
+
for (const item of this.items()) {
|
|
317
|
+
if (values.includes(item.value)) {
|
|
318
|
+
if (checked != null) {
|
|
319
|
+
result = checked;
|
|
320
|
+
} else {
|
|
321
|
+
result = !item.checked;
|
|
322
|
+
}
|
|
323
|
+
item.checked = result;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return result
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
has(value) {
|
|
331
|
+
return !!this.current().find(v => v.value === value)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Shortcut to access an item by index (operates on items()).
|
|
336
|
+
* Equivalent to items().at(index). Returns undefined if out of range.
|
|
337
|
+
*/
|
|
338
|
+
itemAt(index) {
|
|
339
|
+
return this.items().at(index)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Shortcut to access an item by value (operates on items()).
|
|
344
|
+
* Returns the first FormItem whose value equals the given value, or undefined if not found.
|
|
345
|
+
*/
|
|
346
|
+
itemOf(value) {
|
|
347
|
+
return this.items().find(v => v.value === value)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export { formField };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "html-form-field",
|
|
3
|
+
"description": "Unified interface for HTML form fields with synchronized binding to object properties",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"author": "Kawanet",
|
|
6
|
+
"c8": {
|
|
7
|
+
"reporter": [
|
|
8
|
+
"text",
|
|
9
|
+
"lcov"
|
|
10
|
+
],
|
|
11
|
+
"include": [
|
|
12
|
+
"src/**/*.ts"
|
|
13
|
+
],
|
|
14
|
+
"reportsDir": "coverage"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@rollup/plugin-alias": "^5.1.1",
|
|
18
|
+
"@rollup/plugin-multi-entry": "^7.0.0",
|
|
19
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
20
|
+
"@rollup/plugin-sucrase": "^5.0.2",
|
|
21
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
22
|
+
"@types/jsdom": "^27.0.0",
|
|
23
|
+
"@types/node": "^24.7.2",
|
|
24
|
+
"c8": "^10.1.3",
|
|
25
|
+
"chai": "^6.2.0",
|
|
26
|
+
"html-ele": "^0.0.1",
|
|
27
|
+
"jsdom": "^27.0.0",
|
|
28
|
+
"mocha": "^11.7.4",
|
|
29
|
+
"rollup": "^4.52.4",
|
|
30
|
+
"terser": "^5.44.0",
|
|
31
|
+
"typescript": "^5.9.3"
|
|
32
|
+
},
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"require": "./dist/html-form-field.cjs",
|
|
36
|
+
"import": {
|
|
37
|
+
"types": "./types/html-form-field.d.ts",
|
|
38
|
+
"default": "./dist/html-form-field.mjs"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"LICENSE",
|
|
44
|
+
"README.md",
|
|
45
|
+
"dist/html-form-field.cjs",
|
|
46
|
+
"dist/html-form-field.min.js",
|
|
47
|
+
"dist/html-form-field.mjs",
|
|
48
|
+
"dist/package.json",
|
|
49
|
+
"src/*.ts",
|
|
50
|
+
"types/*.d.ts"
|
|
51
|
+
],
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"main": "./src/index.ts",
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "make -C builder",
|
|
56
|
+
"fixpack": "fixpack",
|
|
57
|
+
"prepare": "make -C builder clean all test",
|
|
58
|
+
"test": "node --test",
|
|
59
|
+
"test:coverage": "c8 node --test"
|
|
60
|
+
},
|
|
61
|
+
"sideEffects": false,
|
|
62
|
+
"type": "module",
|
|
63
|
+
"types": "types/html-form-field.d.ts"
|
|
64
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import type {formField as NS, FormField, FormFieldOptions} from "../types/html-form-field.d.ts"
|
|
2
|
+
import {formItemList} from "./form-item.ts"
|
|
3
|
+
|
|
4
|
+
type FieldEventHandler = (this: NS.FieldElement, ev: Event) => void
|
|
5
|
+
|
|
6
|
+
const DELIM = "," // or use Unit Separator `\x1F` instead
|
|
7
|
+
|
|
8
|
+
const isString = (v: any): v is string => ("string" === typeof v)
|
|
9
|
+
|
|
10
|
+
const noNullString = (v: any): string => (v == null) ? "" : isString(v) ? v : String(v)
|
|
11
|
+
|
|
12
|
+
const splitString = (v: string | string[], delim: string) => (v == null) ? [] : Array.isArray(v) ? v.map(noNullString) : noNullString(v).split(delim)
|
|
13
|
+
|
|
14
|
+
const isHTMLElement = (v: ParentNode): v is HTMLElement => ("function" === typeof (v as HTMLElement).matches)
|
|
15
|
+
|
|
16
|
+
export const formField: typeof NS = (options) => {
|
|
17
|
+
return new FormBridgeImpl(options)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const getNodeList = <T>({form, name}: {form: ParentNode, name: NS.StringKeys<T>}): Iterable<NS.FieldElement> => {
|
|
21
|
+
const safeName = JSON.stringify(name)
|
|
22
|
+
if (!name) {
|
|
23
|
+
throw new Error(`Invalid name=${safeName}`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const selector = `input[name=${safeName}], textarea[name=${safeName}], button[name=${safeName}], select[name=${safeName}]`
|
|
27
|
+
|
|
28
|
+
const nodeList = form.querySelectorAll<NS.FieldElement>(selector)
|
|
29
|
+
if (!nodeList.length) {
|
|
30
|
+
if (isHTMLElement(form) && form.matches(selector)) {
|
|
31
|
+
return [form] as NS.FieldElement[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
throw new Error(`Not found: name=${safeName}`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return nodeList
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const updateEventListener = (nodeList: Iterable<NS.FieldElement>, handler: FieldEventHandler) => {
|
|
41
|
+
for (const node of nodeList) {
|
|
42
|
+
node.removeEventListener("change", handler)
|
|
43
|
+
node.addEventListener("change", handler)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const triggerOnWrite = <T>(field: FormField<T>) => {
|
|
48
|
+
const onWrite = field.options.onWrite
|
|
49
|
+
if (onWrite) onWrite(field)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const triggerOnChange = <T>(field: FormField<T>) => {
|
|
53
|
+
const onChange = field.options.onChange
|
|
54
|
+
if (onChange) onChange(field)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const applyBindTo = <T>(bindTo: T, name: NS.StringKeys<T>, field: FormField<T>) => {
|
|
58
|
+
if (!bindTo) return // nothing to be bound
|
|
59
|
+
|
|
60
|
+
delete bindTo[name as keyof T]
|
|
61
|
+
|
|
62
|
+
const getValue = () => field.value
|
|
63
|
+
|
|
64
|
+
const setValue = (value: string) => {
|
|
65
|
+
field.value = value
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
Object.defineProperty(bindTo, name, {
|
|
69
|
+
get: getValue,
|
|
70
|
+
set: setValue,
|
|
71
|
+
enumerable: true,
|
|
72
|
+
configurable: true,
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class FormBridgeImpl<T = any> implements FormField<T> {
|
|
77
|
+
readonly options: FormFieldOptions<T>
|
|
78
|
+
readonly name: FormField<T>["name"]
|
|
79
|
+
|
|
80
|
+
private _items: NS.FormItem[]
|
|
81
|
+
private readonly _onChange: FieldEventHandler
|
|
82
|
+
|
|
83
|
+
constructor(options: FormFieldOptions<T> = {} as FormFieldOptions<T>) {
|
|
84
|
+
this.options = options
|
|
85
|
+
const name = this.name = options.name
|
|
86
|
+
applyBindTo(options.bindTo, name, this)
|
|
87
|
+
|
|
88
|
+
const _onChange = this._onChange = () => {
|
|
89
|
+
triggerOnWrite(this)
|
|
90
|
+
triggerOnChange(this)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const nodeList = getNodeList(options)
|
|
94
|
+
updateEventListener(nodeList, _onChange)
|
|
95
|
+
const items = this._items = formItemList(this, nodeList)
|
|
96
|
+
|
|
97
|
+
const defaults = options.defaults
|
|
98
|
+
if (defaults) {
|
|
99
|
+
if (items[0].checkable) {
|
|
100
|
+
for (const v of defaults) {
|
|
101
|
+
if (this.setValue(v)) break
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
this.setValue(defaults)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get value() {
|
|
110
|
+
const values = this.current().map(v => v.value)
|
|
111
|
+
if (values.length > 1) {
|
|
112
|
+
const delim = this.options.delim || DELIM
|
|
113
|
+
return values.join(delim)
|
|
114
|
+
} else {
|
|
115
|
+
return values[0]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
set value(value: string) {
|
|
120
|
+
this.setValue(value)
|
|
121
|
+
triggerOnWrite(this)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
protected setValue(value: string | string[]): boolean {
|
|
125
|
+
const items = this.items()
|
|
126
|
+
|
|
127
|
+
if (items.length === 1 && !items[0].checkable && isString(value)) {
|
|
128
|
+
value = [value]
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const delim = this.options.delim || DELIM
|
|
132
|
+
const values = splitString(value, delim)
|
|
133
|
+
let i = 0
|
|
134
|
+
let assigned = 0
|
|
135
|
+
|
|
136
|
+
for (const item of items) {
|
|
137
|
+
if (item.checkable) {
|
|
138
|
+
const checked = values.includes(item.value)
|
|
139
|
+
if (checked) assigned++
|
|
140
|
+
if (item.checked !== checked) {
|
|
141
|
+
item.setChecked(checked)
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
const value = values[i++]
|
|
145
|
+
const isNull = (value == null)
|
|
146
|
+
item.setValue(isNull ? "" : value)
|
|
147
|
+
if (!isNull) assigned++
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return !!assigned
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
current() {
|
|
155
|
+
return this.items().filter(v => (!v.disabled && (!v.checkable || v.checked)))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
reload(): void {
|
|
159
|
+
const nodeList = getNodeList(this.options)
|
|
160
|
+
const _onChange = this._onChange
|
|
161
|
+
updateEventListener(nodeList, _onChange)
|
|
162
|
+
this._items = formItemList(this, nodeList)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
items(): NS.FormItem[] {
|
|
166
|
+
return this._items
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
toggle(value: string, checked?: boolean): boolean {
|
|
170
|
+
let result: boolean
|
|
171
|
+
const delim = this.options.delim || DELIM
|
|
172
|
+
const values = splitString(value, delim)
|
|
173
|
+
|
|
174
|
+
for (const item of this.items()) {
|
|
175
|
+
if (values.includes(item.value)) {
|
|
176
|
+
if (checked != null) {
|
|
177
|
+
result = checked
|
|
178
|
+
} else {
|
|
179
|
+
result = !item.checked
|
|
180
|
+
}
|
|
181
|
+
item.checked = result
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return result
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
has(value: string): boolean {
|
|
189
|
+
return !!this.current().find(v => v.value === value)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Shortcut to access an item by index (operates on items()).
|
|
194
|
+
* Equivalent to items().at(index). Returns undefined if out of range.
|
|
195
|
+
*/
|
|
196
|
+
itemAt(index: number): NS.FormItem | undefined {
|
|
197
|
+
return this.items().at(index)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Shortcut to access an item by value (operates on items()).
|
|
202
|
+
* Returns the first FormItem whose value equals the given value, or undefined if not found.
|
|
203
|
+
*/
|
|
204
|
+
itemOf(value: string): NS.FormItem | undefined {
|
|
205
|
+
return this.items().find(v => v.value === value)
|
|
206
|
+
}
|
|
207
|
+
}
|