@cntwg/html-helper 0.0.14

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.
@@ -0,0 +1,568 @@
1
+ // [v0.1.036-20220902]
2
+
3
+ // === module init block ===
4
+
5
+ const {
6
+ readAsBool, readAsNumber, readAsString,
7
+ isEmptyString,
8
+ isNotEmptyString,
9
+ isArray, isObject,
10
+ isPlainObject,
11
+ TItemsListEx,
12
+ } = require('@ygracs/bsfoc-lib-js');
13
+
14
+ const {
15
+ isHTMLElement, isSelectedHTMLElement, isHiddenHTMLElement,
16
+ showHtmlElement, hideHtmlElement,
17
+ selectHtmlElement, unselectHtmlElement,
18
+ CSS_CLASS_STRING,
19
+ } = require('./html-helper-lib.js');
20
+
21
+ const { THtmlStubItemsSet } = require('./html-ctrls/lists-stubs.js');
22
+
23
+ const {
24
+ CSS_CLASS_SELECTED,
25
+ CSS_CLASS_DISABLED,
26
+ CSS_CLASS_HIDDEN,
27
+ } = CSS_CLASS_STRING;
28
+
29
+ // === module extra block (helper functions) ===
30
+
31
+ // === module main block (function definitions) ===
32
+
33
+ // === module main block (class definitions) ===
34
+
35
+ class THtmlItemsListContainer {
36
+ #_host = null;
37
+ #_items = null;
38
+ #_options = null;
39
+
40
+ constructor(host, opt) {
41
+ this.#_items = new TItemsListEx();
42
+ this.#_host = isHTMLElement(host) ? host : null;
43
+ // load options
44
+ const _options = isPlainObject(opt) ? opt : {};
45
+ let { autoHideNewItems, } = _options;
46
+ if (typeof autoHideNewItems !== 'boolean') autoHideNewItems = false;
47
+ // save options
48
+ _options.autoHideNewItems = autoHideNewItems;
49
+ this.#_options = _options;
50
+ }
51
+
52
+ get count(){
53
+ return this.#_items.count;
54
+ }
55
+
56
+ get curIndex(){
57
+ return this.#_items.curIndex;
58
+ }
59
+
60
+ get minIndex(){
61
+ return this.#_items.minIndex;
62
+ }
63
+
64
+ get maxIndex(){
65
+ return this.#_items.maxIndex;
66
+ }
67
+
68
+ get prevIndex(){
69
+ return this.#_items.prevIndex;
70
+ }
71
+
72
+ get nextIndex(){
73
+ return this.#_items.nextIndex;
74
+ }
75
+
76
+ get curItem(){
77
+ const item = this.#_items.curItem;
78
+ return isHTMLElement(item) ? item : null;
79
+ }
80
+
81
+ clear(){
82
+ this.#_items.clear();
83
+ if (this.#_host) this.#_host.innerHTML = '';
84
+ }
85
+
86
+ isEmpty(){
87
+ return this.#_items.isEmpty();
88
+ }
89
+
90
+ isNotEmpty(){
91
+ return this.#_items.isNotEmpty();
92
+ }
93
+
94
+ chkIndex(value){
95
+ return this.#_items.chkIndex(value);
96
+ }
97
+
98
+ chkIndexEx(...args){
99
+ return this.#_items.chkIndexEx(...args);
100
+ }
101
+
102
+ srchIndex(item){
103
+ return isHTMLElement(item) ? this.#_items.srchIndex(item) : -1;
104
+ }
105
+
106
+ srchIndexByAttr(name, value){
107
+ let index = -1;
108
+ const _name = typeof name === 'string' ? name.trim() : '';
109
+ const _value = readAsString(value, '', {
110
+ useTrim: true,
111
+ numberToString: true,
112
+ boolToString: true,
113
+ });
114
+ if (_name !== '' && _value !== '') {
115
+ let item = null;
116
+ for (let i = 0; i < this.count; i++) {
117
+ item = this.getItem(i);
118
+ if (
119
+ isHTMLElement(item)
120
+ && item.hasAttribute(_name)
121
+ && item.getAttribute(_name) === _value
122
+ ) {
123
+ index = i;
124
+ break;
125
+ };
126
+ };
127
+ };
128
+ return index;
129
+ };
130
+
131
+ srchIndexByID(value){
132
+ return this.srchIndexByAttr('id', value);
133
+ };
134
+
135
+ setCurIndex(index){
136
+ return this.#_items.setIndex(index);
137
+ }
138
+
139
+ rstCurIndex(){
140
+ this.#_items.rstIndex();
141
+ }
142
+
143
+ getItem(index){
144
+ const item = this.#_items.getItem(index);
145
+ return isHTMLElement(item) ? item : null;
146
+ }
147
+
148
+ getItemByAttr(name, value = ''){
149
+ let _name = '';
150
+ let item = null;
151
+ let index = -1;
152
+ value = readAsString(value, '', {
153
+ useTrim: true,
154
+ numberToString: true,
155
+ boolToString: true,
156
+ });
157
+ if (typeof name === 'string' && ((_name = name.trim()) !== '')){
158
+ const useAnyName = isEmptyString(name);
159
+ const useAnyVal = isEmptyString(value);
160
+ for (let i = 0; i < this.count; i++) {
161
+ item = this.getItem(i);
162
+ if (
163
+ isHTMLElement(item)
164
+ && item.hasAttribute(_name)
165
+ && (useAnyVal || item.getAttribute(_name) === value)
166
+ ) {
167
+ index = i;
168
+ break;
169
+ };
170
+ };
171
+ };
172
+ return index !== -1 ? item : null;
173
+ }
174
+
175
+ getItemByID(value){
176
+ return this.getItemByAttr('id', value);
177
+ }
178
+
179
+ addItem(item, opt){
180
+ let index = isHTMLElement(item) ? this.#_items.addItemEx(item, opt) : -1;
181
+ let isSUCCEED = index !== -1;
182
+ if (isSUCCEED) {
183
+ if (this.#_options.autoHideNewItems) hideHtmlElement(item);
184
+ if (this.#_host) this.#_host.appendChild(item);
185
+ };
186
+ return isSUCCEED ? index : -1;
187
+ }
188
+
189
+ delItem(index, opt){
190
+ let item = this.#_items.delItemEx(index, opt);
191
+ let isSUCCEED = isHTMLElement(item);
192
+ if (isSUCCEED && this.#_host && this.#_host.contains(item)) {
193
+ this.#_host.removeChild(item);
194
+ };
195
+ return isSUCCEED;
196
+ }
197
+
198
+ selectItem(index, opt){
199
+ let isSUCCEED = selectHtmlElement(this.#_items.getItem(index));
200
+ if (isSUCCEED && readAsBool(opt, false)) this.#_items.setIndex(index);
201
+ return isSUCCEED;
202
+ }
203
+
204
+ unselectItem(index){
205
+ return unselectHtmlElement(this.#_items.getItem(index));
206
+ }
207
+
208
+ hideItem(index){
209
+ return hideHtmlElement(this.#_items.getItem(index));
210
+ }
211
+
212
+ showItem(index){
213
+ return showHtmlElement(this.#_items.getItem(index));
214
+ }
215
+
216
+ isSelectedItem(index) {
217
+ return isSelectedHTMLElement(this.#_items.getItem(index));
218
+ }
219
+
220
+ isHiddenItem(index) {
221
+ return isHiddenHTMLElement(this.#_items.getItem(index));
222
+ }
223
+
224
+ };
225
+
226
+ class THtmlItemsListController extends THtmlItemsListContainer {
227
+ #_host = null;
228
+ #_stubs = null;
229
+ #_selects = null;
230
+ #_visibles = null;
231
+ #_options = null;
232
+ #_status = null;
233
+ #_events = null;
234
+
235
+ constructor(host, opt) {
236
+ // check host element
237
+ const isHostEnabled = isHTMLElement(host);
238
+ const _host = isHostEnabled ? host : null;
239
+ // check options
240
+ const _options = isPlainObject(opt) ? opt : {};
241
+ // call parent constructor
242
+ super(_host, _options);
243
+ // load options
244
+ let {
245
+ autoHideNewItems,
246
+ showStubsIfEmpty,
247
+ allowGroupSelection,
248
+ allowSelectionLocks,
249
+ stubs: stubsList,
250
+ } = _options;
251
+ if (typeof autoHideNewItems !== 'boolean') autoHideNewItems = false;
252
+ if (typeof showStubsIfEmpty !== 'boolean') showStubsIfEmpty = false;
253
+ if (typeof allowGroupSelection !== 'boolean') allowGroupSelection = false;
254
+ if (typeof allowSelectionLocks !== 'boolean') allowSelectionLocks = false;
255
+ // save options
256
+ _options.autoHideNewItems = autoHideNewItems;
257
+ _options.showStubsIfEmpty = showStubsIfEmpty;
258
+ _options.allowGroupSelection = allowGroupSelection;
259
+ _options.allowSelectionLocks = allowSelectionLocks;
260
+ this.#_options = _options;
261
+ // init status flags set
262
+ const _status = {
263
+ isSelectionLocked: false,
264
+ isStubItemShown: false,
265
+ isHostEnabled: isHostEnabled,
266
+ catchEventOnHost: false,
267
+ }
268
+ // bind ref to host
269
+ this.#_host = _host;
270
+ // init stub items set
271
+ this.#_stubs = new THtmlStubItemsSet(_host, stubsList);
272
+ // init index of selected items
273
+ this.#_selects = new Set();
274
+ // init index of visible items
275
+ this.#_visibles = new Set();
276
+ // init events controller
277
+ this.#_events = new Map();
278
+ if (isHostEnabled) {
279
+ // set on_click()-event controler
280
+ /**/// use bubblig stage
281
+ _host.addEventListener('click', this.#_on_will_select_item);
282
+ _status.catchEventOnHost = true;
283
+ };
284
+ // save status flags set
285
+ this.#_status = _status;
286
+ }
287
+
288
+ #_on_will_select_item = (e) => {
289
+ //console.log('THtmlItemsListController._on_will_select_item() ==> was called...');
290
+ //console.log('CHECK: e => ditail:['+e.detail+']');
291
+ //console.log('CHECK: e => phase:['+e.eventPhase+']');
292
+ //e.preventDefault(); /* need to reconsider reason for use */
293
+ const _status = this.#_status;
294
+ const { isSelectionLocked, catchEventOnHost } = _status;
295
+ const on_click_num = readAsNumber(e.detail, 0);
296
+ let curItem = null;
297
+ switch (e.eventPhase) {
298
+ //*case 1:
299
+ //* // NOTE: currently on eventPhase = 2 and 3
300
+ case 2:
301
+ /**/// capturing stage
302
+ curItem = e.target;
303
+ break;
304
+ case 3:
305
+ /**/// bubblig stage
306
+ curItem = catchEventOnHost ? e.target : e.currentTarget;
307
+ break;
308
+ default:
309
+ break;
310
+ };
311
+ if (
312
+ !isSelectionLocked
313
+ && curItem instanceof HTMLElement
314
+ && (on_click_num === 0 || on_click_num === 1)
315
+ && !curItem.classList.contains(CSS_CLASS_DISABLED)
316
+ ) {
317
+ const key_ctrl = readAsBool(e.ctrlKey, false);
318
+ const key_shft = readAsBool(e.shiftKey, false);
319
+ //console.log('CHECK: e => isShiftKeyPressed:['+key_ctrl+']');
320
+ //console.log('CHECK: e => isCtrlKeyPressed:['+key_shft+']');
321
+ let c_index = this.curIndex;
322
+ let n_index = this.srchIndex(curItem);
323
+ //console.log('THtmlItemsListController._on_will_select_item() ==> current:['+c_index+']');
324
+ //console.log('THtmlItemsListController._on_will_select_item() ==> next:['+n_index+']');
325
+ let act_in_mode = 0;
326
+ let act_unsel_grp = false;
327
+ if (this.#_options.allowGroupSelection) {
328
+ if (c_index !== -1) {
329
+ if (key_ctrl) {
330
+ act_in_mode = 2;
331
+ } else if (key_shft) {
332
+ act_in_mode = 1;
333
+ } else if (this.#_selects.size > 0) {
334
+ //console.log('THtmlItemsListController._on_will_select_item() ==> _selects.size:['+this.#_selects.size+']');
335
+ act_unsel_grp = true;
336
+ };
337
+ };
338
+ };
339
+ //console.log('THtmlItemsListController._on_will_select_item() ==> mode:['+act_in_mode+']');
340
+ //console.log('THtmlItemsListController._on_will_select_item() ==> act_unsel_grp:['+act_unsel_grp+']');
341
+ if (act_unsel_grp) {
342
+ for (let item of this.#_selects) {
343
+ c_index = this.srchIndex(item);
344
+ if (c_index !== -1) this.unselectItem(c_index);
345
+ };
346
+ c_index = -1;
347
+ };
348
+ if (act_in_mode === 1) {
349
+ if (c_index > n_index) [ c_index, n_index ] = [ n_index, c_index ];
350
+ let t_index = 0;
351
+ for (let item of this.#_selects) {
352
+ t_index = this.srchIndex(item);
353
+ if (t_index !== -1) this.unselectItem(t_index);
354
+ };
355
+ this.#_selects.clear();
356
+ for (let i = c_index; i < n_index + 1; i++) {
357
+ this.selectItem(i, false);
358
+ };
359
+ } else if (act_in_mode === 2) {
360
+ if (this.isSelectedItem(n_index)) {
361
+ this.unselectItem(n_index);
362
+ } else {
363
+ this.selectItem(n_index, false);
364
+ };
365
+ } else if (c_index !== n_index) {
366
+ if (c_index !== -1) this.unselectItem(c_index);
367
+ this.selectItem(n_index, true);
368
+ };
369
+ };
370
+ };
371
+
372
+ #_triggerEvent = (name, ...args) => {
373
+ const _name = typeof name === 'string' ? name.trim() : '';
374
+ if (_name !== '') {
375
+ const _events = this.#_events;
376
+ if (_events.has(_name)) _events.get(_name)(...args);
377
+ };
378
+ };
379
+
380
+ get SelectedItems(){
381
+ let result = [];
382
+ for (let item of this.#_selects) {
383
+ if (item) result.push(item);
384
+ };
385
+ return result;
386
+ }
387
+
388
+ get StubItems(){
389
+ return this.#_stubs;
390
+ }
391
+
392
+ get isSelectionLocked(){
393
+ return this.#_status.isSelectionLocked;
394
+ }
395
+
396
+ clear(){
397
+ // // TODO: clear event handler on elements if set
398
+ super.clear();
399
+ this.#_selects.clear();
400
+ this.#_visibles.clear();
401
+ if (this.#_options.showStubsIfEmpty) {
402
+ // show default stub-item
403
+ this.#_status.isStubItemShown = this.#_stubs.showDefItem();
404
+ };
405
+ this.#_triggerEvent('list-clear');
406
+ }
407
+
408
+ lockItemsSelection(){
409
+ if (this.#_options.allowSelectionLocks) {
410
+ this.#_status.isSelectionLocked = true;
411
+ };
412
+ }
413
+
414
+ unlockItemsSelection(){
415
+ this.#_status.isSelectionLocked = false;
416
+ }
417
+
418
+ setCurIndex(index){
419
+ const isSUCCEED = super.setCurIndex(index);
420
+ if (isSUCCEED) this.#_triggerEvent('current-item-chosen', {
421
+ index: Number(index),
422
+ item: null,
423
+ });
424
+ return isSUCCEED;
425
+ }
426
+
427
+ addItem(item, opt){
428
+ let index = super.addItem(item, false);
429
+ let newItem = index !== -1 ? super.getItem(index) : null;
430
+ if (newItem) {
431
+ const _options = this.#_options;
432
+ const _status = this.#_status;
433
+ if (!_status.catchEventOnHost) {
434
+ // set event handler on element if it is not set on host
435
+ newItem.addEventListener('click', this.#_on_will_select_item);
436
+ };
437
+ // TODO: consider if a "boolean" <opt> is enough
438
+ let setItemAsCur = typeof opt === 'boolean' ? opt : false;
439
+ if (_options.autoHideNewItems && !setItemAsCur) {
440
+ super.hideItem(index);
441
+ } else {
442
+ if (index === 0) {
443
+ if (
444
+ _options.showStubsIfEmpty
445
+ && _status.isStubItemShown
446
+ && this.#_stubs.hideDefItem()
447
+ ) {
448
+ // hide default stub-item
449
+ _status.isStubItemShown = false;
450
+ };
451
+ this.#_triggerEvent('first-item-added', {
452
+ index: index,
453
+ item: newItem,
454
+ });
455
+ };
456
+ this.#_triggerEvent('item-added', {
457
+ index: index,
458
+ item: newItem,
459
+ });
460
+ };
461
+ if (setItemAsCur) this.setCurIndex(index);
462
+ };
463
+ return index;
464
+ }
465
+
466
+ delItem(index, opt){
467
+ let item = super.getItem(index);
468
+ let isSUCCEED = isHTMLElement(item) ? super.delItem(index, opt) : false;
469
+ if (isSUCCEED) {
470
+ const _options = this.#_options;
471
+ const _status = this.#_status;
472
+ if (!_status.catchEventOnHost) {
473
+ // remove event handler on element if it was set by addItem()
474
+ item.removeEventListener('click', this.#_on_will_select_item);
475
+ };
476
+ this.#_selects.delete(item);
477
+ this.#_visibles.delete(item);
478
+ if (this.isEmpty()) {
479
+ if (_options.showStubsIfEmpty) {
480
+ // show default stub-item
481
+ _status.isStubItemShown = this.#_stubs.showDefItem();
482
+ };
483
+ this.#_triggerEvent('list-clear');
484
+ } else {
485
+ this.#_triggerEvent('item-removed', {
486
+ index: Number(index),
487
+ item: item,
488
+ });
489
+ };
490
+ };
491
+ return isSUCCEED;
492
+ }
493
+
494
+ selectItem(index, opt){
495
+ let item = super.getItem(index);
496
+ let isSUCCEED = selectHtmlElement(item);
497
+ if (isSUCCEED) {
498
+ this.#_selects.add(item);
499
+ this.#_triggerEvent('item-selected', {
500
+ index: Number(index),
501
+ item: item,
502
+ });
503
+ if (readAsBool(opt, false)) isSUCCEED = this.setCurIndex(index);
504
+ };
505
+ return isSUCCEED;
506
+ }
507
+
508
+ unselectItem(index){
509
+ let item = super.getItem(index);
510
+ let isSUCCEED = unselectHtmlElement(item);
511
+ if (isSUCCEED) {
512
+ this.#_selects.delete(item);
513
+ this.#_triggerEvent('item-unselected', {
514
+ index: Number(index),
515
+ item: item,
516
+ });
517
+ };
518
+ return isSUCCEED;
519
+ }
520
+
521
+ hideItem(index){
522
+ let item = super.getItem(index);
523
+ let isSUCCEED = hideHtmlElement(item);
524
+ if (isSUCCEED) {
525
+ this.#_visibles.delete(item);
526
+ this.#_triggerEvent('item-hidden', {
527
+ index: Number(index),
528
+ item: item,
529
+ });
530
+ };
531
+ return isSUCCEED;
532
+ }
533
+
534
+ showItem(index){
535
+ let item = super.getItem(index);
536
+ let isSUCCEED = showHtmlElement(item);
537
+ if (isSUCCEED) {
538
+ this.#_visibles.add(item);
539
+ this.#_triggerEvent('item-shown', {
540
+ index: Number(index),
541
+ item: item,
542
+ });
543
+ };
544
+ return isSUCCEED;
545
+ }
546
+
547
+ on(name, evnt){
548
+ const _name = typeof name === 'string' ? name.trim() : '';
549
+ if (_name !== '' && typeof evnt === 'function') {
550
+ const _events = this.#_events;
551
+ if (!_events.has(_name)) {
552
+ _events.set(_name, evnt);
553
+ } else {
554
+ /* NOTE:
555
+ * for current you can't reset or set new one on the same event
556
+ */
557
+ //console.log('THtmlItemsListController.on() ==> event:['+name+'] - already exists...');
558
+ };
559
+ };
560
+ }
561
+
562
+ };
563
+
564
+ // === module exports block ===
565
+
566
+ exports.THtmlStubItemsSet = THtmlStubItemsSet;
567
+ exports.THtmlItemsListContainer = THtmlItemsListContainer;
568
+ exports.THtmlItemsListController = THtmlItemsListController;