@syncfusion/ej2-lists 23.1.36 → 23.1.42

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,2653 @@
1
+ import { Virtualization } from './virtualization';
2
+ import { merge, formatUnit, isNullOrUndefined, append, detach, ModuleDeclaration, extend } from '@syncfusion/ej2-base';
3
+ import { attributes, addClass, removeClass, prepend, closest, remove } from '@syncfusion/ej2-base';
4
+ import { Component, EventHandler, BaseEventArgs, Property, Complex, Event } from '@syncfusion/ej2-base';
5
+ import { NotifyPropertyChanges, INotifyPropertyChanged, ChildProperty } from '@syncfusion/ej2-base';
6
+ import { KeyboardEventArgs, EmitType, compile, SanitizeHtmlHelper } from '@syncfusion/ej2-base';
7
+ import { Animation, AnimationOptions, Effect, rippleEffect, Touch, SwipeEventArgs } from '@syncfusion/ej2-base';
8
+ import { DataManager, Query } from '@syncfusion/ej2-data';
9
+ import { createCheckBox } from '@syncfusion/ej2-buttons';
10
+ import { ListBase, ListBaseOptions, SortOrder, getFieldValues, FieldsMapping } from '../common/list-base';
11
+ import { ListViewModel, FieldSettingsModel } from './list-view-model';
12
+
13
+ // Effect Configuration Effect[] = [fromViewBackward,fromViewForward,toViewBackward,toviewForward];
14
+ const effectsConfig: { [key: string]: Effect[] } = {
15
+ 'None': [],
16
+ 'SlideLeft': ['SlideRightOut', 'SlideLeftOut', 'SlideLeftIn', 'SlideRightIn'],
17
+ 'SlideDown': ['SlideTopOut', 'SlideBottomOut', 'SlideBottomIn', 'SlideTopIn'],
18
+ 'Zoom': ['FadeOut', 'FadeZoomOut', 'FadeZoomIn', 'FadeIn'],
19
+ 'Fade': ['FadeOut', 'FadeOut', 'FadeIn', 'FadeIn']
20
+ };
21
+
22
+ const effectsRTLConfig: { [key: string]: Effect[] } = {
23
+ 'None': [],
24
+ 'SlideLeft': ['SlideLeftOut', 'SlideRightOut', 'SlideRightIn', 'SlideLeftIn'],
25
+ 'SlideDown': ['SlideBottomOut', 'SlideTopOut', 'SlideTopIn', 'SlideBottomIn'],
26
+ 'Zoom': ['FadeZoomOut', 'FadeOut', 'FadeIn', 'FadeZoomIn'],
27
+ 'Fade': ['FadeOut', 'FadeOut', 'FadeIn', 'FadeIn']
28
+ };
29
+
30
+ // don't use space in classnames.
31
+ export const classNames: ClassNames = {
32
+ root: 'e-listview',
33
+ hover: 'e-hover',
34
+ selected: 'e-active',
35
+ focused: 'e-focused',
36
+ parentItem: 'e-list-parent',
37
+ listItem: 'e-list-item',
38
+ listIcon: 'e-list-icon',
39
+ textContent: 'e-text-content',
40
+ listItemText: 'e-list-text',
41
+ groupListItem: 'e-list-group-item',
42
+ hasChild: 'e-has-child',
43
+ view: 'e-view',
44
+ header: 'e-list-header',
45
+ headerText: 'e-headertext',
46
+ headerTemplateText: 'e-headertemplate-text',
47
+ text: 'e-text',
48
+ disable: 'e-disabled',
49
+ container: 'e-list-container',
50
+ icon: 'e-icons',
51
+ backIcon: 'e-icon-back',
52
+ backButton: 'e-back-button',
53
+ checkboxWrapper: 'e-checkbox-wrapper',
54
+ checkbox: 'e-checkbox',
55
+ checked: 'e-check',
56
+ checklist: 'e-checklist',
57
+ checkboxIcon: 'e-frame',
58
+ checkboxRight: 'e-checkbox-right',
59
+ checkboxLeft: 'e-checkbox-left',
60
+ listviewCheckbox: 'e-listview-checkbox',
61
+ itemCheckList: 'e-checklist',
62
+ virtualElementContainer: 'e-list-virtualcontainer'
63
+ };
64
+
65
+ const LISTVIEW_TEMPLATE_PROPERTY: string = 'Template';
66
+ const LISTVIEW_GROUPTEMPLATE_PROPERTY: string = 'GroupTemplate';
67
+ const LISTVIEW_HEADERTEMPLATE_PROPERTY: string = 'HeaderTemplate';
68
+ const swipeVelocity: number = 0.5;
69
+
70
+ /**
71
+ * An interface that holds options of fields.
72
+ */
73
+ export interface Fields {
74
+ /**
75
+ * Specifies the id field mapped in dataSource.
76
+ */
77
+ id?: string | number;
78
+ /**
79
+ * The `text` property is used to map the text value from the data source for each list item.
80
+ */
81
+ text?: string | number;
82
+ /**
83
+ * It is used to map the custom field values of list item from the dataSource.
84
+ */
85
+ [key: string]: Object | string | number | undefined;
86
+ }
87
+
88
+
89
+ /**
90
+ * Represents the field settings of the ListView.
91
+ */
92
+
93
+ export class FieldSettings extends ChildProperty<FieldSettings> {
94
+
95
+ /**
96
+ * Specifies the id field mapped in dataSource.
97
+ */
98
+ @Property('id')
99
+ public id: string;
100
+ /**
101
+ * The `text` property is used to map the text value from the data source for each list item.
102
+ */
103
+ @Property('text')
104
+ public text: string;
105
+
106
+ /**
107
+ * The `isChecked` property is used to check whether the list items are in checked state or not.
108
+ */
109
+ @Property('isChecked')
110
+ public isChecked: string;
111
+ /**
112
+ * The `isVisible` property is used to check whether the list items are in visible state or not.
113
+ */
114
+ @Property('isVisible')
115
+ public isVisible: string;
116
+ /**
117
+ * Specifies the enabled state of the ListView component.
118
+ * And, we can disable the component using this property by setting its value as false.
119
+ */
120
+ @Property('enabled')
121
+ public enabled: string;
122
+ /**
123
+ * The `iconCss` is used to customize the icon to the list items dynamically.
124
+ * We can add a specific image to the icons using `iconCss` property.
125
+ */
126
+ @Property('iconCss')
127
+ public iconCss: string;
128
+ /**
129
+ * The `child` property is used for nested navigation of listed items.
130
+ */
131
+ @Property('child')
132
+ public child: string;
133
+ /**
134
+ * The `tooltip` is used to display the information about the target element while hovering on list items.
135
+ */
136
+ @Property('tooltip')
137
+ public tooltip: string;
138
+
139
+ /**
140
+ * The `groupBy` property is used to wraps the ListView elements into a group.
141
+ */
142
+ @Property('groupBy')
143
+ public groupBy: string;
144
+
145
+ /**
146
+ * The `sortBy` property used to enable the sorting of list items to be ascending or descending order.
147
+ */
148
+ @Property('text')
149
+ public sortBy: string;
150
+
151
+ /**
152
+ * The `htmlAttributes` allows additional attributes such as id, class, etc., and
153
+ * accepts n number of attributes in a key-value pair format.
154
+ */
155
+ @Property('htmlAttributes')
156
+ public htmlAttributes: string;
157
+ /**
158
+ * Specifies the `tableName` used to fetch data from a specific table in the server.
159
+ */
160
+ @Property('tableName')
161
+ public tableName: string;
162
+ }
163
+
164
+ /**
165
+ * An interface that holds animation settings.
166
+ */
167
+ export interface AnimationSettings {
168
+ /**
169
+ * It is used to specify the effect which is shown in sub list transform.
170
+ */
171
+ effect?: ListViewEffect;
172
+ /**
173
+ * It is used to specify the time duration of transform object.
174
+ */
175
+ duration?: number;
176
+ /**
177
+ * It is used to specify the easing effect applied while transform
178
+ */
179
+ easing?: string;
180
+ }
181
+
182
+ /**
183
+ * An enum type that denotes the effects of the ListView. Available options are as follows None, SlideLeft, SlideDown, Zoom, Fade;
184
+ * ```props
185
+ * None :- No animation is applied when items are added or removed from the ListView.
186
+ * SlideLeft :- Items slide in from the left when added and slide out to the left when removed.
187
+ * SlideDown :- Items slide in from the top when added and slide out to the top when removed.
188
+ * Zoom :- Items zoom in or out when added or removed.
189
+ * Fade :- Items fade in or out when added or removed.
190
+ * ```
191
+ */
192
+ export type ListViewEffect = 'None' | 'SlideLeft' | 'SlideDown' | 'Zoom' | 'Fade';
193
+
194
+ /**
195
+ * An enum type that denotes the position of checkbox of the ListView. Available options are as follows Left and Right;
196
+ * ```props
197
+ * Left :- The checkbox is positioned on the left side of the ListView item.
198
+ * Right :- The checkbox is positioned on the right side of the ListView item.
199
+ * ```
200
+ */
201
+ export type checkBoxPosition = 'Left' | 'Right';
202
+
203
+ /**
204
+ * Represents the EJ2 ListView control.
205
+ * ```html
206
+ * <div id="listview">
207
+ * <ul>
208
+ * <li>Favorite</li>
209
+ * <li>Documents</li>
210
+ * <li>Downloads</li>
211
+ * </ul>
212
+ * </div>
213
+ * ```
214
+ * ```typescript
215
+ * var listviewObject = new ListView({});
216
+ * listviewObject.appendTo("#listview");
217
+ * ```
218
+ */
219
+ @NotifyPropertyChanges
220
+ export class ListView extends Component<HTMLElement> implements INotifyPropertyChanged {
221
+
222
+ private ulElement: HTMLElement;
223
+ private selectedLI: Element;
224
+ // eslint-disable-next-line
225
+ private onUIScrolled: Function;
226
+ private curUL: HTMLElement;
227
+ private curDSLevel: string[];
228
+ private curViewDS: DataSource[] | string[] | number[];
229
+ private curDSJSON: DataSource;
230
+ public localData: DataSource[];
231
+ private liCollection: HTMLElement[];
232
+ private headerEle: HTMLElement;
233
+ private contentContainer: HTMLElement;
234
+ private touchModule: Touch;
235
+ private listBaseOption: ListBaseOptions;
236
+ public virtualizationModule: Virtualization;
237
+ private animateOptions: AnimationOptions;
238
+ // eslint-disable-next-line
239
+ private rippleFn: Function;
240
+ private isNestedList: boolean;
241
+ private currentLiElements: HTMLElement[];
242
+ private selectedData: string[] | string;
243
+ private selectedId: string[];
244
+ private isWindow: boolean;
245
+ private selectedItems: SelectedItem;
246
+ private aniObj: Animation;
247
+ private LISTVIEW_TEMPLATE_ID: string;
248
+ private LISTVIEW_GROUPTEMPLATE_ID: string;
249
+ private LISTVIEW_HEADERTEMPLATE_ID: string;
250
+ private liElement: Element;
251
+ private virtualCheckBox: Element | string;
252
+ private liElementHeight: number;
253
+ private previousSelectedItems: string[] = [];
254
+ private hiddenItems: string[] = [];
255
+ private enabledItems: string[] = [];
256
+ private disabledItems: string[] = [];
257
+ private isOffline: boolean;
258
+ /**
259
+ * The `cssClass` property is used to add a user-preferred class name in the root element of the ListView,
260
+ * using which we can customize the component (both CSS and functionality customization)
261
+ *
262
+ * {% codeBlock src='listview/cssClass/index.md' %}{% endcodeBlock %}
263
+ *
264
+ * @default ''
265
+ */
266
+ @Property('')
267
+ public cssClass: string;
268
+
269
+ /**
270
+ * If `enableVirtualization` set to true, which will increase the ListView performance, while loading a large amount of data.
271
+ *
272
+ * {% codeBlock src='listview/enableVirtualization/index.md' %}{% endcodeBlock %}
273
+ *
274
+ * @default false
275
+ */
276
+ @Property(false)
277
+ public enableVirtualization: boolean;
278
+
279
+ /**
280
+ * The `htmlAttributes` allows additional attributes such as id, class, etc., and
281
+ * accepts n number of attributes in a key-value pair format.
282
+ *
283
+ * {% codeBlock src='listview/htmlAttributes/index.md' %}{% endcodeBlock %}
284
+ *
285
+ * @default {}
286
+ */
287
+ @Property({})
288
+ public htmlAttributes: { [key: string]: string; };
289
+
290
+ /**
291
+ * If `enable` set to true, the list items are enabled.
292
+ * And, we can disable the component using this property by setting its value as false.
293
+ *
294
+ * {% codeBlock src='listview/enable/index.md' %}{% endcodeBlock %}
295
+ *
296
+ * @default true
297
+ */
298
+ @Property(true)
299
+ public enable: boolean;
300
+ /* es-lint disable */
301
+ /**
302
+ * The `dataSource` provides the data to render the ListView component which is mapped with the fields of ListView.
303
+ *
304
+ * @isGenericType true
305
+ *
306
+ * {% codeBlock src='listview/dataSource/index.md' %}{% endcodeBlock %}
307
+ *
308
+ * @default []
309
+ */
310
+ @Property([])
311
+ public dataSource: { [key: string]: Object }[] | string[] | number[] | DataManager;
312
+ /* es-lint enable */
313
+ /**
314
+ * The `query` is used to fetch the specific data from dataSource by using where and select keywords.
315
+ *
316
+ * {% codeBlock src='listview/query/index.md' %}{% endcodeBlock %}
317
+ *
318
+ * @default null
319
+ */
320
+ @Property()
321
+ public query: Query;
322
+
323
+ /**
324
+ * The `fields` is used to map keys from the dataSource which extracts the appropriate data from the dataSource
325
+ * with specified mapped with the column fields to render the ListView.
326
+ *
327
+ * {% codeBlock src='listview/fields/index.md' %}{% endcodeBlock %}
328
+ *
329
+ * @default defaultMappedFields
330
+ */
331
+ @Complex<FieldSettingsModel>(ListBase.defaultMappedFields, FieldSettings)
332
+ public fields: FieldSettingsModel;
333
+
334
+ /**
335
+ * The `animation` property provides an option to apply the different
336
+ * animations on the ListView component.
337
+ *
338
+ * {% codeBlock src='listview/animation/index.md' %}{% endcodeBlock %}
339
+ *
340
+ *
341
+ * @default { effect: 'SlideLeft', duration: 400, easing: 'ease' }
342
+ */
343
+ @Property<AnimationSettings>({ effect: 'SlideLeft', duration: 400, easing: 'ease' })
344
+ public animation: AnimationSettings;
345
+
346
+ /**
347
+ * The `sortOrder` is used to sort the data source. The available type of sort orders are,
348
+ * * `None` - The data source is not sorting.
349
+ * * `Ascending` - The data source is sorting with ascending order.
350
+ * * `Descending` - The data source is sorting with descending order.
351
+ *
352
+ * {% codeBlock src='listview/sortOrder/index.md' %}{% endcodeBlock %}
353
+ *
354
+ * @default 'None'
355
+ */
356
+ @Property<SortOrder>('None')
357
+ public sortOrder: SortOrder;
358
+
359
+ /**
360
+ * If `showIcon` set to true, which will show or hide the icon of the list item.
361
+ *
362
+ * {% codeBlock src='listview/showIcon/index.md' %}{% endcodeBlock %}
363
+ *
364
+ * @default false
365
+ */
366
+ @Property<boolean>(false)
367
+ public showIcon: boolean;
368
+
369
+ /**
370
+ * If `showCheckBox` set to true, which will show or hide the checkbox.
371
+ *
372
+ * {% codeBlock src='listview/showCheckBox/index.md' %}{% endcodeBlock %}
373
+ *
374
+ *
375
+ * @default false
376
+ */
377
+ @Property<boolean>(false)
378
+ public showCheckBox: boolean;
379
+
380
+ /**
381
+ * The `checkBoxPosition` is used to set the position of check box in a list item.
382
+ * By default, the `checkBoxPosition` is Left, which will appear before the text content in a list item.
383
+ *
384
+ * {% codeBlock src='listview/checkBoxPosition/index.md' %}{% endcodeBlock %}
385
+ *
386
+ * @default 'Left'
387
+ */
388
+ @Property<string>('Left')
389
+ public checkBoxPosition: checkBoxPosition;
390
+
391
+ /**
392
+ * The `headerTitle` is used to set the title of the ListView component.
393
+ *
394
+ * {% codeBlock src='listview/headerTitle/index.md' %}{% endcodeBlock %}
395
+ *
396
+ *
397
+ * @default ""
398
+ */
399
+ @Property<string>('')
400
+ public headerTitle: string;
401
+
402
+ /**
403
+ * If `showHeader` set to true, which will show or hide the header of the ListView component.
404
+ *
405
+ * {% codeBlock src='listview/showHeader/index.md' %}{% endcodeBlock %}
406
+ *
407
+ * @default false
408
+ */
409
+ @Property<boolean>(false)
410
+ public showHeader: boolean;
411
+
412
+ /**
413
+ * If `enableHtmlSanitizer` set to true, allows the cross-scripting site.
414
+ *
415
+ * {% codeBlock src='listview/enableHtmlSanitizer/index.md' %}{% endcodeBlock %}
416
+ *
417
+ * @default false
418
+ */
419
+ @Property(false)
420
+ public enableHtmlSanitizer: boolean;
421
+
422
+ /**
423
+ * Defines the height of the ListView component which accepts both string and number values.
424
+ *
425
+ * {% codeBlock src='listview/height/index.md' %}{% endcodeBlock %}
426
+ *
427
+ * @default ''
428
+ */
429
+ @Property('')
430
+ public height: number | string;
431
+
432
+ /**
433
+ * Defines the width of the ListView component which accepts both string and number values.
434
+ *
435
+ * {% codeBlock src='listview/width/index.md' %}{% endcodeBlock %}
436
+ *
437
+ * @default ''
438
+ */
439
+ @Property('')
440
+ public width: number | string;
441
+
442
+ /**
443
+ * The ListView component supports to customize the content of each list items with the help of `template` property.
444
+ *
445
+ * {% codeBlock src='listview/template/index.md' %}{% endcodeBlock %}
446
+ *
447
+ * @default null
448
+ * @angularType string | object
449
+ * @reactType string | function | JSX.Element
450
+ * @vueType string | function
451
+ * @deprecated
452
+ */
453
+ @Property(null)
454
+ public template: string;
455
+
456
+ /**
457
+ * The ListView has an option to custom design the ListView header title with the help of `headerTemplate` property.
458
+ *
459
+ * {% codeBlock src="listview/headerTemplate/index.md" %}{% endcodeBlock %}
460
+ *
461
+ * @default null
462
+ * @angularType string | object
463
+ * @reactType string | function | JSX.Element
464
+ * @vueType string | function
465
+ * @deprecated
466
+ */
467
+ @Property(null)
468
+ public headerTemplate: string;
469
+
470
+ /**
471
+ * The ListView has an option to custom design the group header title with the help of `groupTemplate` property.
472
+ *
473
+ * {% codeBlock src="listview/groupTemplate/index.md" %}{% endcodeBlock %}
474
+ *
475
+ * @default null
476
+ * @angularType string | object
477
+ * @reactType string | function | JSX.Element
478
+ * @vueType string | function
479
+ * @deprecated
480
+ */
481
+ @Property(null)
482
+ public groupTemplate: string;
483
+
484
+ /**
485
+ * Triggers when we select the list item in the component.
486
+ *
487
+ * @event 'object'
488
+ */
489
+ @Event()
490
+ public select: EmitType<SelectEventArgs>;
491
+
492
+ /**
493
+ * Triggers when every ListView action starts.
494
+ *
495
+ * @event 'object'
496
+ */
497
+ @Event()
498
+ public actionBegin: EmitType<object>;
499
+
500
+ /**
501
+ * Triggers when every ListView actions completed.
502
+ *
503
+ * @event 'object'
504
+ */
505
+ /* es-lint disable */
506
+ @Event()
507
+ public actionComplete: EmitType<MouseEvent>;
508
+ /* es-lint enable */
509
+ /**
510
+ * Triggers, when the data fetch request from the remote server, fails.
511
+ *
512
+ * @event 'object'
513
+ *
514
+ */
515
+ @Event()
516
+ public actionFailure: EmitType<MouseEvent>;
517
+
518
+
519
+ /**
520
+ * Constructor for creating the widget
521
+ *
522
+ * @param options
523
+ *
524
+ * @param element
525
+ */
526
+
527
+ constructor(options?: ListViewModel, element?: string | HTMLElement) {
528
+ super(options, <HTMLElement | string>element);
529
+ }
530
+
531
+ /**
532
+ * @param newProp
533
+ *
534
+ * @param oldProp
535
+ *
536
+ * @private
537
+ */
538
+
539
+ public onPropertyChanged(newProp: ListViewModel, oldProp: ListViewModel): void {
540
+ for (const prop of Object.keys(newProp)) {
541
+ switch (prop) {
542
+ case 'htmlAttributes':
543
+ this.setHTMLAttribute();
544
+ break;
545
+ case 'cssClass':
546
+ this.setCSSClass(oldProp.cssClass);
547
+ break;
548
+ case 'enable':
549
+ this.setEnable();
550
+ break;
551
+ case 'width':
552
+ case 'height':
553
+ this.setSize();
554
+ break;
555
+ case 'enableRtl':
556
+ this.setEnableRTL();
557
+ break;
558
+ case 'fields':
559
+ // eslint-disable-next-line
560
+ this.listBaseOption.fields = (this.fields as any & { properties: object }).properties;
561
+ if (this.enableVirtualization) {
562
+ this.virtualizationModule.reRenderUiVirtualization();
563
+ } else {
564
+ this.reRender();
565
+ }
566
+ break;
567
+ case 'headerTitle':
568
+ if (!this.curDSLevel.length) {
569
+ this.header(this.headerTitle, false, 'header');
570
+ }
571
+ break;
572
+ case 'query':
573
+ if (this.enableVirtualization) {
574
+ this.virtualizationModule.reRenderUiVirtualization();
575
+ } else {
576
+ this.reRender();
577
+ }
578
+ break;
579
+ case 'showHeader':
580
+ this.header(this.headerTitle, false, 'header');
581
+ break;
582
+ case 'enableVirtualization':
583
+ if (!isNullOrUndefined(this.contentContainer)) {
584
+ detach(this.contentContainer);
585
+ }
586
+ this.refresh();
587
+ break;
588
+ case 'showCheckBox':
589
+ case 'checkBoxPosition':
590
+ if (this.enableVirtualization) {
591
+ this.virtualizationModule.reRenderUiVirtualization();
592
+ } else {
593
+ this.setCheckbox();
594
+ }
595
+ break;
596
+ case 'dataSource':
597
+ if (this.enableVirtualization) {
598
+ this.virtualizationModule.reRenderUiVirtualization();
599
+ } else {
600
+ this.reRender();
601
+ }
602
+ break;
603
+ case 'sortOrder':
604
+ case 'template':
605
+ if (!this.enableVirtualization) {
606
+ this.refresh();
607
+ }
608
+ break;
609
+ case 'showIcon':
610
+ if (this.enableVirtualization) {
611
+ this.virtualizationModule.reRenderUiVirtualization();
612
+ } else {
613
+ this.listBaseOption.showIcon = this.showIcon;
614
+ this.curViewDS = this.getSubDS();
615
+ this.resetCurrentList();
616
+ }
617
+ break;
618
+ default:
619
+ break;
620
+ }
621
+ }
622
+ }
623
+
624
+ // Model Changes
625
+ private setHTMLAttribute(): void {
626
+ if (Object.keys(this.htmlAttributes).length) {
627
+ attributes(this.element, this.htmlAttributes);
628
+ }
629
+ }
630
+
631
+ private setCSSClass(oldCSSClass?: string): void {
632
+ if (this.cssClass) {
633
+ addClass([this.element], this.cssClass.split(' ').filter((css: string) => css));
634
+ }
635
+ if (oldCSSClass) {
636
+ removeClass([this.element], oldCSSClass.split(' ').filter((css: string) => css));
637
+ }
638
+ }
639
+
640
+ private setSize(): void {
641
+ this.element.style.height = formatUnit(this.height);
642
+ this.element.style.width = formatUnit(this.width);
643
+ this.isWindow = this.element.clientHeight ? false : true;
644
+ }
645
+
646
+ private setEnable(): void {
647
+ this.enableElement(this.element, this.enable);
648
+ }
649
+
650
+ private setEnableRTL(): void {
651
+ if (this.enableRtl) {
652
+ this.element.classList.add('e-rtl');
653
+ } else {
654
+ this.element.classList.remove('e-rtl');
655
+ }
656
+ }
657
+
658
+ private enableElement(element: HTMLElement, isEnabled?: boolean): void {
659
+ if (isEnabled) {
660
+ element.classList.remove(classNames.disable);
661
+ } else {
662
+ element.classList.add(classNames.disable);
663
+ }
664
+ }
665
+
666
+ // Support Component Functions
667
+ private header(text?: string, showBack?: boolean, prop?: string): void {
668
+ if (this.headerEle === undefined && this.showHeader) {
669
+ if (this.enableHtmlSanitizer) {
670
+ this.setProperties({ headerTitle: SanitizeHtmlHelper.sanitize(this.headerTitle) }, true);
671
+ }
672
+ this.headerEle = this.createElement('div', { className: classNames.header });
673
+ const innerHeaderEle: HTMLElement = this.createElement('span', { className: classNames.headerText, innerHTML: this.headerTitle });
674
+ const textEle: HTMLElement = this.createElement('div', { className: classNames.text, innerHTML: innerHeaderEle.outerHTML });
675
+ const hedBackButton: HTMLElement = this.createElement('div', {
676
+ className: classNames.icon + ' ' + classNames.backIcon + ' ' + classNames.backButton,
677
+ attrs: { style: 'display:none;' }
678
+ });
679
+ this.headerEle.appendChild(hedBackButton);
680
+ this.headerEle.appendChild(textEle);
681
+ if (this.headerTemplate) {
682
+ // eslint-disable-next-line
683
+ const compiledString: Function = compile(this.headerTemplate);
684
+ const headerTemplateEle: HTMLElement = this.createElement('div', { className: classNames.headerTemplateText });
685
+ // eslint-disable-next-line
686
+ const compiledElement: any = compiledString({}, this, prop, this.LISTVIEW_HEADERTEMPLATE_ID, null, null, this.headerEle);
687
+ if (compiledElement) {
688
+ append(compiledElement, headerTemplateEle);
689
+ }
690
+ append([headerTemplateEle], this.headerEle);
691
+ // eslint-disable-next-line
692
+ if ((this as any).isReact) {
693
+ this.renderReactTemplates();
694
+ }
695
+ }
696
+ if (this.headerTemplate && this.headerTitle) {
697
+ textEle.classList.add('header');
698
+ }
699
+ this.element.classList.add('e-has-header');
700
+ prepend([this.headerEle], this.element);
701
+ } else if (this.headerEle) {
702
+ if (this.showHeader) {
703
+ this.headerEle.style.display = '';
704
+ const textEle: Element = this.headerEle.querySelector('.' + classNames.headerText);
705
+ const hedBackButton: Element = this.headerEle.querySelector('.' + classNames.backIcon);
706
+ if (this.enableHtmlSanitizer) {
707
+ text = SanitizeHtmlHelper.sanitize(text);
708
+ }
709
+ textEle.innerHTML = text;
710
+ if (this.headerTemplate && showBack) {
711
+ textEle.parentElement.classList.remove('header');
712
+ this.headerEle.querySelector('.' + classNames.headerTemplateText).classList.add('nested-header');
713
+ }
714
+ if (this.headerTemplate && !showBack) {
715
+ textEle.parentElement.classList.add('header');
716
+ this.headerEle.querySelector('.' + classNames.headerTemplateText).classList.remove('nested-header');
717
+ this.headerEle.querySelector('.' + classNames.headerTemplateText).classList.add('header');
718
+ }
719
+ if (showBack === true) {
720
+ (hedBackButton as HTMLElement).style.display = '';
721
+ } else {
722
+ (hedBackButton as HTMLElement).style.display = 'none';
723
+ }
724
+ } else {
725
+ this.headerEle.style.display = 'none';
726
+ }
727
+ }
728
+ }
729
+
730
+ // Animation Related Functions
731
+ private switchView(fromView: HTMLElement, toView: HTMLElement, reverse?: boolean): void {
732
+ if (fromView && toView) {
733
+ const fPos: string = fromView.style.position;
734
+ const overflow: string = (this.element.style.overflow !== 'hidden') ? this.element.style.overflow : '';
735
+
736
+ fromView.style.position = 'absolute';
737
+ fromView.classList.add('e-view');
738
+ let anim: Effect[];
739
+ let duration: number = this.animation.duration;
740
+ if (this.animation.effect) {
741
+ anim = (this.enableRtl ? effectsRTLConfig[this.animation.effect] : effectsConfig[this.animation.effect]);
742
+ } else {
743
+ const slideLeft: string = 'SlideLeft';
744
+ anim = effectsConfig[`${slideLeft}`];
745
+ reverse = this.enableRtl;
746
+ duration = 0;
747
+ }
748
+ this.element.style.overflow = 'hidden';
749
+ this.aniObj.animate(fromView, {
750
+ name: (reverse === true ? anim[0] : anim[1]),
751
+ duration: duration,
752
+ timingFunction: this.animation.easing,
753
+ // eslint-disable-next-line
754
+ end: (model: AnimationOptions): void => {
755
+ fromView.style.display = 'none';
756
+ this.element.style.overflow = overflow;
757
+ fromView.style.position = fPos;
758
+ fromView.classList.remove('e-view');
759
+ }
760
+ });
761
+ toView.style.display = '';
762
+ this.aniObj.animate(toView, {
763
+ name: (reverse === true ? anim[2] : anim[3]),
764
+ duration: duration,
765
+ timingFunction: this.animation.easing,
766
+ end: (): void => {
767
+ this.trigger('actionComplete');
768
+ }
769
+ });
770
+ this.curUL = toView;
771
+ }
772
+ }
773
+
774
+ protected preRender(): void {
775
+ if (this.template) {
776
+ try {
777
+ if (document.querySelectorAll(this.template).length) {
778
+ this.setProperties({ template: document.querySelector(this.template).innerHTML.trim() }, true);
779
+ }
780
+ } catch (e) {
781
+ compile(this.template);
782
+ }
783
+ }
784
+ this.listBaseOption = {
785
+ template: this.template,
786
+ headerTemplate: this.headerTemplate,
787
+ groupTemplate: this.groupTemplate, expandCollapse: true, listClass: '',
788
+ ariaAttributes: {
789
+ itemRole: 'listitem', listRole: 'group', itemText: '',
790
+ groupItemRole: 'presentation', wrapperRole: 'presentation'
791
+ },
792
+ // eslint-disable-next-line
793
+ fields: ((this.fields as any & { properties: Object }).properties) as FieldsMapping,
794
+ sortOrder: this.sortOrder,
795
+ showIcon: this.showIcon,
796
+ itemCreated: this.renderCheckbox.bind(this),
797
+ templateID: `${this.element.id}${LISTVIEW_TEMPLATE_PROPERTY}`,
798
+ groupTemplateID: `${this.element.id}${LISTVIEW_GROUPTEMPLATE_PROPERTY}`,
799
+ enableHtmlSanitizer: this.enableHtmlSanitizer,
800
+ };
801
+ this.initialization();
802
+ }
803
+
804
+ private initialization(): void {
805
+ this.curDSLevel = [];
806
+ this.animateOptions = {};
807
+ this.curViewDS = [];
808
+ this.currentLiElements = [];
809
+ this.isNestedList = false;
810
+ this.selectedData = [];
811
+ this.selectedId = this.enablePersistence ?this.selectedId: [];
812
+ this.LISTVIEW_TEMPLATE_ID = `${this.element.id}${LISTVIEW_TEMPLATE_PROPERTY}`;
813
+ this.LISTVIEW_GROUPTEMPLATE_ID = `${this.element.id}${LISTVIEW_GROUPTEMPLATE_PROPERTY}`;
814
+ this.LISTVIEW_HEADERTEMPLATE_ID = `${this.element.id}${LISTVIEW_HEADERTEMPLATE_PROPERTY}`;
815
+ this.aniObj = new Animation(this.animateOptions);
816
+ this.removeElement(this.curUL);
817
+ this.removeElement(this.ulElement);
818
+ this.removeElement(this.headerEle);
819
+ this.removeElement(this.contentContainer);
820
+ this.curUL = this.ulElement = this.liCollection = this.headerEle = this.contentContainer = undefined;
821
+ }
822
+
823
+ private renderCheckbox(args: ItemCreatedArgs): void {
824
+ if (args.item.classList.contains(classNames.hasChild)) {
825
+ this.isNestedList = true;
826
+ }
827
+ if (this.showCheckBox && this.isValidLI(args.item)) {
828
+ let checkboxElement: Element;
829
+ let fieldData: DataSource;
830
+ // eslint-disable-next-line prefer-const
831
+ checkboxElement = createCheckBox(this.createElement, false, {
832
+ checked: false, enableRtl: this.enableRtl,
833
+ cssClass: classNames.listviewCheckbox
834
+ });
835
+ checkboxElement.setAttribute('role', 'checkbox');
836
+ const frameElement: Element = checkboxElement.querySelector('.' + classNames.checkboxIcon);
837
+ args.item.classList.add(classNames.itemCheckList);
838
+ args.item.firstElementChild.classList.add(classNames.checkbox);
839
+ if (typeof (this.dataSource as string[])[0] !== 'string' && typeof (this.dataSource as number[])[0] !== 'number') {
840
+ fieldData = <DataSource>getFieldValues(args.curData, this.listBaseOption.fields);
841
+ if (this.enablePersistence && !isNullOrUndefined(this.selectedId)) {
842
+ let index: number = this.selectedId.findIndex(e => e == fieldData[this.listBaseOption.fields.id].toString());
843
+ if (index != -1) {
844
+ this.checkInternally(args, checkboxElement);
845
+ }
846
+ }
847
+ else if (<object>fieldData[this.listBaseOption.fields.isChecked]) {
848
+ this.checkInternally(args, checkboxElement);
849
+ }
850
+ } else if (((typeof (this.dataSource as string[])[0] === 'string' ||
851
+ typeof (this.dataSource as number[])[0] === 'number') && this.selectedData.indexOf(args.text) !== -1)) {
852
+ this.checkInternally(args, checkboxElement);
853
+ }
854
+ checkboxElement.setAttribute('aria-checked', frameElement.classList.contains(classNames.checked) ? 'true' : 'false');
855
+ checkboxElement.setAttribute('aria-label', 'checkbox');
856
+ if (this.checkBoxPosition === 'Left') {
857
+ checkboxElement.classList.add(classNames.checkboxLeft);
858
+ args.item.firstElementChild.classList.add(classNames.checkboxLeft);
859
+ args.item.firstElementChild.insertBefore(checkboxElement, args.item.firstElementChild.childNodes[0]);
860
+ } else {
861
+ checkboxElement.classList.add(classNames.checkboxRight);
862
+ args.item.firstElementChild.classList.add(classNames.checkboxRight);
863
+ args.item.firstElementChild.appendChild(checkboxElement);
864
+ }
865
+ this.currentLiElements.push(args.item);
866
+ if (this.checkBoxPosition === 'Left') {
867
+ this.virtualCheckBox = args.item.firstElementChild.children[0];
868
+ }
869
+ else {
870
+ this.virtualCheckBox = args.item.firstElementChild.lastElementChild;
871
+ }
872
+ }
873
+ }
874
+
875
+ private checkInternally(args: ItemCreatedArgs, checkboxElement: Element): void {
876
+ args.item.classList.add(classNames.selected);
877
+ checkboxElement.querySelector('.' + classNames.checkboxIcon).classList.add(classNames.checked);
878
+ checkboxElement.setAttribute('aria-checked', 'true');
879
+ }
880
+
881
+ /**
882
+ * Checks the specific list item by passing the unchecked fields as an argument to this method.
883
+ *
884
+ * @param {Fields | HTMLElement | Element} item - It accepts Fields or HTML list element as an argument.
885
+ */
886
+
887
+ public checkItem(item: Fields | HTMLElement | Element): void {
888
+ this.toggleCheckBase(item, true);
889
+ }
890
+
891
+ private toggleCheckBase(item: Fields | Element | HTMLElement, checked: boolean): void {
892
+ if (this.showCheckBox) {
893
+ let liElement: Element = item as Element;
894
+ if (item instanceof Object && (item as Object).constructor !== HTMLLIElement) {
895
+ liElement = this.getLiFromObjOrElement(item);
896
+ }
897
+ if (!isNullOrUndefined(liElement)) {
898
+ const checkboxIcon: Element = liElement.querySelector('.' + classNames.checkboxIcon);
899
+ if (checked === true) {
900
+ liElement.classList.add(classNames.selected);
901
+ }
902
+ else {
903
+ liElement.classList.remove(classNames.selected);
904
+ }
905
+ if (checked === true) {
906
+ checkboxIcon.classList.add(classNames.checked);
907
+ }
908
+ else {
909
+ checkboxIcon.classList.remove(classNames.checked);
910
+ }
911
+ checkboxIcon.parentElement.setAttribute('aria-checked', checked ? 'true' : 'false');
912
+ }
913
+ this.setSelectedItemData(liElement);
914
+ this.updateSelectedId();
915
+ }
916
+ }
917
+
918
+ /**
919
+ * Uncheck the specific list item by passing the checked fields as an argument to this method.
920
+ *
921
+ * @param {Fields | HTMLElement | Element} item - It accepts Fields or HTML list element as an argument.
922
+ */
923
+
924
+ public uncheckItem(item: Fields | HTMLElement | Element): void {
925
+ this.toggleCheckBase(item, false);
926
+ }
927
+
928
+ /**
929
+ * Checks all the unchecked items in the ListView.
930
+ */
931
+
932
+ public checkAllItems(): void {
933
+ this.toggleAllCheckBase(true);
934
+ }
935
+
936
+ /**
937
+ * Uncheck all the checked items in ListView.
938
+ */
939
+
940
+ public uncheckAllItems(): void {
941
+ this.toggleAllCheckBase(false);
942
+ }
943
+
944
+ private toggleAllCheckBase(checked: boolean): void {
945
+ if (this.showCheckBox) {
946
+ for (let i: number = 0; i < this.liCollection.length; i++) {
947
+ const checkIcon: Element = this.liCollection[i as number].querySelector('.' + classNames.checkboxIcon);
948
+ if (checkIcon) {
949
+ if (checked) {
950
+ if (!checkIcon.classList.contains(classNames.checked)) {
951
+ this.checkItem(this.liCollection[i as number]);
952
+ }
953
+ } else {
954
+ if (checkIcon.classList.contains(classNames.checked)) {
955
+ this.uncheckItem(this.liCollection[i as number]);
956
+ }
957
+ }
958
+ }
959
+ }
960
+ if (this.enableVirtualization) {
961
+ this.virtualizationModule.checkedItem(checked);
962
+ }
963
+ this.updateSelectedId();
964
+ }
965
+ }
966
+
967
+ private setCheckbox(): void {
968
+ if (this.showCheckBox) {
969
+ const liCollection: HTMLElement[] = Array.prototype.slice.call(this.element.querySelectorAll('.' + classNames.listItem));
970
+ const args: ItemCreatedArgs = {
971
+ item: undefined, curData: undefined, dataSource: undefined, fields: undefined,
972
+ options: undefined, text: ''
973
+ };
974
+ for (let i: number = 0; i < liCollection.length; i++) {
975
+ const element: HTMLElement = liCollection[i as number];
976
+ args.item = element;
977
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
978
+ args.curData = this.getItemData(element) as { [key: string]: Object } as any;
979
+ if (element.querySelector('.' + classNames.checkboxWrapper)) {
980
+ this.removeElement(element.querySelector('.' + classNames.checkboxWrapper));
981
+ }
982
+ this.renderCheckbox(args);
983
+ if (args.item.classList.contains(classNames.selected)) {
984
+ this.checkInternally(args, args.item.querySelector('.' + classNames.checkboxWrapper));
985
+ }
986
+ }
987
+ } else {
988
+ const liCollection: HTMLElement[] = Array.prototype.slice.call(this.element.querySelectorAll('.' + classNames.itemCheckList));
989
+ for (let i: number = 0; i < liCollection.length; i++) {
990
+ const element: HTMLElement = liCollection[i as number];
991
+ element.classList.remove(classNames.selected);
992
+ element.firstElementChild.classList.remove(classNames.checkbox);
993
+ this.removeElement(element.querySelector('.' + classNames.checkboxWrapper));
994
+ }
995
+ if (this.selectedItems) {
996
+ this.selectedItems.item.classList.add(classNames.selected);
997
+ }
998
+ }
999
+ }
1000
+
1001
+ /**
1002
+ * Refresh the height of the list item only on enabling the virtualization property.
1003
+ */
1004
+
1005
+ public refreshItemHeight(): void {
1006
+ if (this.virtualizationModule) {
1007
+ this.virtualizationModule.refreshItemHeight();
1008
+ }
1009
+ }
1010
+
1011
+ targetElement: any;
1012
+ private clickHandler(e: MouseEvent): void {
1013
+ const target: Element = <Element>e.target;
1014
+ this.targetElement = target;
1015
+ const classList: DOMTokenList = target.classList;
1016
+ let closestElement: HTMLElement;
1017
+ if (classList.contains(classNames.backIcon) || classList.contains(classNames.headerText)) {
1018
+ if (this.showCheckBox && this.curDSLevel[this.curDSLevel.length - 1]) {
1019
+ this.uncheckAllItems();
1020
+ }
1021
+ this.back();
1022
+ } else {
1023
+ let li: HTMLElement = <HTMLElement>closest(target.parentNode, '.' + classNames.listItem);
1024
+ if (li === null) { li = <HTMLElement>target; }
1025
+ this.removeFocus();
1026
+ if (this.enable && this.showCheckBox && this.isValidLI(li)) {
1027
+ if ((e.target as HTMLElement).classList.contains(classNames.checkboxIcon)) {
1028
+ li.classList.add(classNames.focused);
1029
+ if (isNullOrUndefined(li.querySelector('.' + classNames.checked))) {
1030
+ const args: ItemCreatedArgs = {
1031
+ curData: undefined, dataSource: undefined, fields: undefined, options: undefined,
1032
+ text: undefined, item: li
1033
+ };
1034
+ this.checkInternally(args, args.item.querySelector('.' + classNames.checkboxWrapper));
1035
+ } else {
1036
+ this.uncheckItem(li);
1037
+ li.classList.add(classNames.focused);
1038
+ }
1039
+ if (this.enableVirtualization) {
1040
+ this.virtualizationModule.setCheckboxLI(li, e);
1041
+ }
1042
+ if (e) {
1043
+ const eventArgs: Object = this.selectEventData(li, e);
1044
+ const checkIcon: Element = li.querySelector('.' + classNames.checkboxIcon);
1045
+ merge(eventArgs, { isChecked: checkIcon.classList.contains(classNames.checked) });
1046
+ this.trigger('select', eventArgs);
1047
+ }
1048
+ } else if (li.classList.contains(classNames.hasChild)) {
1049
+ this.removeHover();
1050
+ this.removeSelect();
1051
+ this.removeSelect(li);
1052
+ this.setSelectLI(li, e);
1053
+ li.classList.remove(classNames.selected);
1054
+ } else {
1055
+ this.setCheckboxLI(li, e);
1056
+ if((target.nodeName == "INPUT") || (target.nodeName == "TEXTAREA")) {
1057
+ target.classList.add('e-focused');
1058
+ this.targetElement = target;
1059
+ }
1060
+ }
1061
+ } else {
1062
+ this.setSelectLI(li, e);
1063
+ if((target.nodeName == "INPUT") || (target.nodeName == "TEXTAREA")) {
1064
+ target.classList.add('e-focused');
1065
+ this.targetElement = target;
1066
+ }
1067
+ }
1068
+ closestElement = closest((e.target as HTMLElement), 'li') as HTMLElement;
1069
+ if (!isNullOrUndefined(closestElement)) {
1070
+ if (closestElement.classList.contains('e-has-child') &&
1071
+ !(e.target as HTMLElement).parentElement.classList.contains('e-listview-checkbox')) {
1072
+ closestElement.classList.add(classNames.disable);
1073
+ }
1074
+ }
1075
+ }
1076
+ this.updateSelectedId();
1077
+ }
1078
+
1079
+ private removeElement(element: HTMLElement | Element): HTMLElement | Element {
1080
+ return element && element.parentNode && element.parentNode.removeChild(element);
1081
+ }
1082
+
1083
+ private hoverHandler(e: MouseEvent): void {
1084
+ const curLi: HTMLElement = <HTMLElement>closest((<Element>e.target).parentNode, '.' + classNames.listItem);
1085
+ this.setHoverLI(curLi);
1086
+ }
1087
+
1088
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1089
+ private leaveHandler(e: MouseEvent): void {
1090
+ this.removeHover();
1091
+ }
1092
+ private homeKeyHandler(e: KeyboardEventArgs, end?: boolean): void {
1093
+ e.preventDefault();
1094
+ if (Object.keys(this.dataSource).length && this.curUL) {
1095
+ const li: Element[] = <NodeListOf<Element> & Element[]>this.curUL.querySelectorAll('.' + classNames.listItem);
1096
+ const focusedElement: Element = this.curUL.querySelector('.' + classNames.focused) ||
1097
+ this.curUL.querySelector('.' + classNames.selected);
1098
+ if (focusedElement) {
1099
+ focusedElement.classList.remove(classNames.focused);
1100
+ if (!this.showCheckBox) {
1101
+ focusedElement.classList.remove(classNames.selected);
1102
+ }
1103
+ }
1104
+ const index: number = !end ? 0 : li.length - 1;
1105
+ if (li[index as number].classList.contains(classNames.hasChild) || this.showCheckBox) {
1106
+ li[index as number].classList.add(classNames.focused);
1107
+ } else {
1108
+ this.setSelectLI(li[index as number], e);
1109
+ }
1110
+ if (li[index as number]) {
1111
+ this.element.setAttribute('aria-activedescendant', (<HTMLElement>li[index as number]).id.toString());
1112
+ } else {
1113
+ this.element.removeAttribute('aria-activedescendant');
1114
+ }
1115
+ }
1116
+ }
1117
+
1118
+ private onArrowKeyDown(e: KeyboardEventArgs, prev: boolean): Element {
1119
+ let siblingLI: Element;
1120
+ let li: Element;
1121
+ const hasChild: boolean = !isNullOrUndefined(this.curUL.querySelector('.' + classNames.hasChild)) ? true : false;
1122
+ if (hasChild || this.showCheckBox) {
1123
+ li = this.curUL.querySelector('.' + classNames.focused) || this.curUL.querySelector('.' + classNames.selected);
1124
+ siblingLI = ListBase.getSiblingLI(this.curUL.querySelectorAll('.' + classNames.listItem), li, prev);
1125
+ if (!isNullOrUndefined(siblingLI)) {
1126
+ if (li) {
1127
+ li.classList.remove(classNames.focused);
1128
+ if (!this.showCheckBox) {
1129
+ li.classList.remove(classNames.selected);
1130
+ }
1131
+ }
1132
+ if (siblingLI.classList.contains(classNames.hasChild) || this.showCheckBox) {
1133
+ siblingLI.classList.add(classNames.focused);
1134
+ } else {
1135
+ this.setSelectLI(siblingLI, e);
1136
+ }
1137
+ }
1138
+ } else {
1139
+ li = this.curUL.querySelector('.' + classNames.selected);
1140
+ siblingLI = ListBase.getSiblingLI(this.curUL.querySelectorAll('.' + classNames.listItem), li, prev);
1141
+ this.setSelectLI(siblingLI, e);
1142
+ }
1143
+ if (siblingLI) {
1144
+ this.element.setAttribute('aria-activedescendant', (<HTMLElement>siblingLI).id.toString());
1145
+ } else {
1146
+ this.element.removeAttribute('aria-activedescendant');
1147
+ }
1148
+ return siblingLI;
1149
+ }
1150
+
1151
+ private arrowKeyHandler(e: KeyboardEventArgs, prev?: boolean): void {
1152
+ e.preventDefault();
1153
+ if (Object.keys(this.dataSource).length && this.curUL) {
1154
+ const siblingLI: Element = this.onArrowKeyDown(e, prev);
1155
+ const elementTop: number = this.element.getBoundingClientRect().top;
1156
+ const elementHeight: number = this.element.getBoundingClientRect().height;
1157
+ const firstItemBounds: ClientRect = this.curUL.querySelector('.' + classNames.listItem).getBoundingClientRect();
1158
+ let heightDiff: number;
1159
+ let groupItemBounds: ClientRect;
1160
+ if (this.fields.groupBy) {
1161
+ groupItemBounds = this.curUL.querySelector('.' + classNames.groupListItem).getBoundingClientRect();
1162
+ }
1163
+ if (siblingLI) {
1164
+ const siblingTop: number = siblingLI.getBoundingClientRect().top;
1165
+ const siblingHeight: number = siblingLI.getBoundingClientRect().height;
1166
+ if (!prev) {
1167
+ const height: number = this.isWindow ? window.innerHeight : elementHeight;
1168
+ heightDiff = this.isWindow ? (siblingTop + siblingHeight) :
1169
+ ((siblingTop - elementTop) + siblingHeight);
1170
+ if (heightDiff > height) {
1171
+ if (this.isWindow === true) {
1172
+ window.scroll(0, pageYOffset + (heightDiff - height));
1173
+ }
1174
+ else {
1175
+ this.element.scrollTop = this.element.scrollTop + (heightDiff - height);
1176
+ }
1177
+ }
1178
+ } else {
1179
+ heightDiff = this.isWindow ? siblingTop : (siblingTop - elementTop);
1180
+ if (heightDiff < 0) {
1181
+ if (this.isWindow === true) {
1182
+ window.scroll(0, pageYOffset + heightDiff);
1183
+ }
1184
+ else {
1185
+ this.element.scrollTop = this.element.scrollTop + heightDiff;
1186
+ }
1187
+ }
1188
+ }
1189
+ } else if (this.enableVirtualization && prev && this.virtualizationModule.uiFirstIndex) {
1190
+ this.onUIScrolled = () => {
1191
+ this.onArrowKeyDown(e, prev);
1192
+ this.onUIScrolled = undefined;
1193
+ };
1194
+ heightDiff = this.virtualizationModule.listItemHeight;
1195
+ if (this.isWindow === true) {
1196
+ window.scroll(0, pageYOffset - heightDiff);
1197
+ }
1198
+ else {
1199
+ this.element.scrollTop = this.element.scrollTop - heightDiff;
1200
+ }
1201
+ } else if (prev) {
1202
+ if (this.showHeader && this.headerEle) {
1203
+ const topHeight: number = groupItemBounds ? groupItemBounds.top : firstItemBounds.top;
1204
+ const headerBounds: ClientRect = this.headerEle.getBoundingClientRect();
1205
+ heightDiff = headerBounds.top < 0 ? (headerBounds.height - topHeight) : 0;
1206
+ if (this.isWindow === true) {
1207
+ window.scroll(0, pageYOffset - heightDiff);
1208
+ }
1209
+ else {
1210
+ this.element.scrollTop = 0;
1211
+ }
1212
+ } else if (this.fields.groupBy) {
1213
+ heightDiff = this.isWindow ? (groupItemBounds.top < 0 ? groupItemBounds.top : 0) :
1214
+ (elementTop - firstItemBounds.top) + groupItemBounds.height;
1215
+ if (this.isWindow === true) {
1216
+ window.scroll(0, pageYOffset + heightDiff);
1217
+ }
1218
+ else {
1219
+ this.element.scrollTop = this.element.scrollTop - heightDiff;
1220
+ }
1221
+ }
1222
+ }
1223
+ }
1224
+ }
1225
+
1226
+ private enterKeyHandler(e: KeyboardEventArgs): void {
1227
+ if (Object.keys(this.dataSource).length && this.curUL) {
1228
+ const hasChild: boolean = !isNullOrUndefined(this.curUL.querySelector('.' + classNames.hasChild)) ? true : false;
1229
+ const li: Element = this.curUL.querySelector('.' + classNames.focused);
1230
+ if (hasChild && li) {
1231
+ li.classList.remove(classNames.focused);
1232
+ if (this.showCheckBox) {
1233
+ this.removeSelect();
1234
+ this.removeSelect(<HTMLElement>li);
1235
+ this.removeHover();
1236
+ }
1237
+ this.setSelectLI(li, e);
1238
+ }
1239
+ }
1240
+ }
1241
+ private spaceKeyHandler(e: KeyboardEventArgs): void {
1242
+ e.preventDefault();
1243
+ if (this.enable && this.showCheckBox && Object.keys(this.dataSource).length && this.curUL) {
1244
+ const li: Element = this.curUL.querySelector('.' + classNames.focused);
1245
+ let checkboxElement: Element;
1246
+ let checkIcon: Element;
1247
+ if (!isNullOrUndefined(li) && isNullOrUndefined(li.querySelector('.' + classNames.checked))) {
1248
+ const args: ItemCreatedArgs = {
1249
+ curData: undefined, dataSource: undefined, fields: undefined, options: undefined,
1250
+ text: undefined, item: <HTMLElement>li
1251
+ };
1252
+ checkboxElement = args.item.querySelector('.' + classNames.checkboxWrapper);
1253
+ this.checkInternally(args, checkboxElement);
1254
+ checkIcon = checkboxElement.querySelector('.' + classNames.checkboxIcon + '.' + classNames.icon);
1255
+ } else {
1256
+ this.uncheckItem(li);
1257
+ }
1258
+ const eventArgs: object = this.selectEventData(li, e);
1259
+ merge(eventArgs, { isChecked: checkIcon ? checkIcon.classList.contains(classNames.checked) : false });
1260
+ this.trigger('select', eventArgs);
1261
+ this.updateSelectedId();
1262
+ }
1263
+ }
1264
+
1265
+ private keyActionHandler(e: KeyboardEventArgs): void {
1266
+ switch (e.keyCode) {
1267
+ case 36:
1268
+ this.homeKeyHandler(e);
1269
+ break;
1270
+ case 35:
1271
+ this.homeKeyHandler(e, true);
1272
+ break;
1273
+ case 40:
1274
+ this.arrowKeyHandler(e);
1275
+ break;
1276
+ case 38:
1277
+ this.arrowKeyHandler(e, true);
1278
+ break;
1279
+ case 13:
1280
+ this.enterKeyHandler(e);
1281
+ break;
1282
+ case 8:
1283
+ if (this.showCheckBox && this.curDSLevel[this.curDSLevel.length - 1]) {
1284
+ this.uncheckAllItems();
1285
+ }
1286
+ this.back();
1287
+ break;
1288
+ case 32:
1289
+ if (isNullOrUndefined(this.targetElement) || !(this.targetElement.classList.contains('e-focused'))) {
1290
+ this.spaceKeyHandler(e);
1291
+ }
1292
+ break;
1293
+ }
1294
+ }
1295
+
1296
+ private swipeActionHandler(e: SwipeEventArgs): void {
1297
+ if (e.swipeDirection === 'Right' && e.velocity > swipeVelocity && e.originalEvent.type === 'touchend') {
1298
+ if (this.showCheckBox && this.curDSLevel[this.curDSLevel.length - 1]) {
1299
+ this.uncheckAllItems();
1300
+ }
1301
+ this.back();
1302
+ }
1303
+ }
1304
+
1305
+ private focusout(): void {
1306
+ if (Object.keys(this.dataSource).length && this.curUL) {
1307
+ const focusedElement: Element = this.curUL.querySelector('.' + classNames.focused);
1308
+ if (focusedElement) {
1309
+ focusedElement.classList.remove(classNames.focused);
1310
+ if (!this.showCheckBox && !isNullOrUndefined(this.selectedLI)) {
1311
+ this.selectedLI.classList.add(classNames.selected);
1312
+ }
1313
+ }
1314
+ }
1315
+ }
1316
+
1317
+ private wireEvents(): void {
1318
+ EventHandler.add(this.element, 'keydown', this.keyActionHandler, this);
1319
+ EventHandler.add(this.element, 'click', this.clickHandler, this);
1320
+ EventHandler.add(this.element, 'mouseover', this.hoverHandler, this);
1321
+ EventHandler.add(this.element, 'mouseout', this.leaveHandler, this);
1322
+ EventHandler.add(this.element, 'focusout', this.focusout, this);
1323
+ this.touchModule = new Touch(this.element, { swipe: this.swipeActionHandler.bind(this) });
1324
+ }
1325
+
1326
+ private unWireEvents(): void {
1327
+ EventHandler.remove(this.element, 'click', this.clickHandler);
1328
+ EventHandler.remove(this.element, 'mouseover', this.hoverHandler);
1329
+ EventHandler.remove(this.element, 'mouseout', this.leaveHandler);
1330
+ EventHandler.remove(this.element, 'mouseover', this.hoverHandler);
1331
+ EventHandler.remove(this.element, 'mouseout', this.leaveHandler);
1332
+ this.touchModule.destroy();
1333
+ this.touchModule = null;
1334
+ }
1335
+
1336
+ private removeFocus(): void {
1337
+ const focusedLI: Element[] = <NodeListOf<Element> & Element[]>this.element.querySelectorAll('.' + classNames.focused);
1338
+ for (const ele of focusedLI) {
1339
+ ele.classList.remove(classNames.focused);
1340
+ }
1341
+ }
1342
+
1343
+ private removeHover(): void {
1344
+ const hoverLI: Element = this.element.querySelector('.' + classNames.hover);
1345
+ if (hoverLI) { hoverLI.classList.remove(classNames.hover); }
1346
+ }
1347
+
1348
+ private removeSelect(li?: HTMLElement): void {
1349
+ if (isNullOrUndefined(li)) {
1350
+ const selectedLI: Element[] = <NodeListOf<Element> & Element[]>this.element.querySelectorAll('.' + classNames.selected);
1351
+ for (const ele of selectedLI) {
1352
+ if (this.showCheckBox && ele.querySelector('.' + classNames.checked)) {
1353
+ continue;
1354
+ } else {
1355
+ ele.classList.remove(classNames.selected);
1356
+ }
1357
+ }
1358
+ } else {
1359
+ li.classList.remove(classNames.selected);
1360
+ }
1361
+ }
1362
+
1363
+ private isValidLI(li: Element | HTMLElement): boolean {
1364
+ return (li && li.classList.contains(classNames.listItem)
1365
+ && !li.classList.contains(classNames.groupListItem)
1366
+ && !li.classList.contains(classNames.disable));
1367
+ }
1368
+
1369
+ private setCheckboxLI(li: Element, e?: MouseEvent | KeyboardEvent | FocusEvent): void {
1370
+ if (this.isValidLI(li) && this.enable && this.showCheckBox) {
1371
+ if (this.curUL.querySelector('.' + classNames.focused)) {
1372
+ this.curUL.querySelector('.' + classNames.focused).classList.remove(classNames.focused);
1373
+ }
1374
+ let textAreaFocus = li.querySelector('textarea') || li.querySelector('input');
1375
+ li.classList.add(classNames.focused);
1376
+ if(!isNullOrUndefined(e)){
1377
+ if(e.target === textAreaFocus){
1378
+ textAreaFocus.classList.add('e-focused');
1379
+ }
1380
+ }
1381
+ const checkboxElement: Element = li.querySelector('.' + classNames.checkboxWrapper);
1382
+ const checkIcon: Element = checkboxElement.querySelector('.' + classNames.checkboxIcon + '.' + classNames.icon);
1383
+ this.removeHover();
1384
+ if (!checkIcon.classList.contains(classNames.checked)) {
1385
+ checkIcon.classList.add(classNames.checked);
1386
+ li.classList.add(classNames.selected);
1387
+ } else {
1388
+ checkIcon.classList.remove(classNames.checked);
1389
+ li.classList.remove(classNames.selected);
1390
+ }
1391
+ checkboxElement.setAttribute('aria-checked', checkIcon.classList.contains(classNames.checked) ?
1392
+ 'true' : 'false');
1393
+ const eventArgs: object = this.selectEventData(li, e);
1394
+ merge(eventArgs, { isChecked: checkIcon.classList.contains(classNames.checked) });
1395
+ if (this.enableVirtualization) {
1396
+ this.virtualizationModule.setCheckboxLI(li, e);
1397
+ }
1398
+ this.trigger('select', eventArgs);
1399
+ this.setSelectedItemData(li);
1400
+ this.renderSubList(li);
1401
+ }
1402
+ }
1403
+
1404
+ private selectEventData(li: Element, e?: MouseEvent | KeyboardEvent | FocusEvent): Object {
1405
+ const data: DataSource = this.getItemData(li);
1406
+ const fieldData: DataSource = getFieldValues(data as { [key: string]: Object }, this.listBaseOption.fields) as DataSource;
1407
+ let selectedItem: SelectedItem;
1408
+ if (!isNullOrUndefined(data)
1409
+ && typeof (this.dataSource as string[])[0] === 'string' || typeof (this.dataSource as number[])[0] === 'number') {
1410
+ selectedItem = { item: li, text: li && (li as HTMLElement).innerText.trim(), data: this.dataSource as string[] | number[] };
1411
+ } else {
1412
+ selectedItem =
1413
+ // eslint-disable-next-line
1414
+ {
1415
+ item: li, text: fieldData && <object>fieldData[this.listBaseOption.fields.text] as any,
1416
+ // eslint-disable-next-line
1417
+ data: data as { [key: string]: Object } as any
1418
+ };
1419
+ }
1420
+ const eventArgs: object = {};
1421
+ merge(eventArgs, selectedItem);
1422
+ if (e) {
1423
+ merge(eventArgs, { isInteracted: true, event: e, cancel: false, index: this.curUL && Array.prototype.indexOf.call(this.curUL.children, li) });
1424
+ }
1425
+ return eventArgs;
1426
+ }
1427
+
1428
+ private setSelectedItemData(li: Element): void {
1429
+ const data: DataSource = this.getItemData(li);
1430
+ // eslint-disable-next-line
1431
+ const fieldData: DataSource = <DataSource>getFieldValues(data as { [key: string]: Object } as any, this.listBaseOption.fields);
1432
+ if (!isNullOrUndefined(data) && ((typeof (this.dataSource as string[])[0] === 'string') ||
1433
+ (typeof (this.dataSource as number[])[0] === 'number'))) {
1434
+ this.selectedItems = {
1435
+ item: li,
1436
+ text: li && (li as HTMLElement).innerText.trim(),
1437
+ data: this.dataSource as string[]
1438
+ };
1439
+ } else {
1440
+ this.selectedItems = {
1441
+ item: li,
1442
+ // eslint-disable-next-line
1443
+ text: fieldData && fieldData[this.listBaseOption.fields.text] as any,
1444
+ // eslint-disable-next-line
1445
+ data: data as { [key: string]: Object } as any
1446
+ };
1447
+ }
1448
+ }
1449
+
1450
+ private setSelectLI(li: Element, e?: MouseEvent | KeyboardEvent | FocusEvent): void {
1451
+ if (this.isValidLI(li) && !li.classList.contains(classNames.selected) && this.enable) {
1452
+ if (!this.showCheckBox) {
1453
+ this.removeSelect();
1454
+ }
1455
+ li.classList.add(classNames.selected);
1456
+ this.removeHover();
1457
+ this.setSelectedItemData(li);
1458
+ if (this.enableVirtualization) {
1459
+ this.virtualizationModule.setSelectLI(li, e);
1460
+ }
1461
+ const eventArgs: object = this.selectEventData(li, e);
1462
+ this.trigger('select', eventArgs, (observedArgs: SelectEventArgs) => {
1463
+ if (!observedArgs.cancel) {
1464
+ this.selectedLI = li;
1465
+ this.renderSubList(li);
1466
+ }
1467
+ });
1468
+ }
1469
+ }
1470
+
1471
+ private setHoverLI(li: Element): void {
1472
+ if (this.isValidLI(li) && !li.classList.contains(classNames.hover) && this.enable) {
1473
+ const lastLi: Element[] = <NodeListOf<Element> & Element[]>this.element.querySelectorAll('.' + classNames.hover);
1474
+ if (lastLi && lastLi.length) { removeClass(lastLi, classNames.hover); }
1475
+ if (!(li as Element).classList.contains(classNames.selected) || this.showCheckBox) {
1476
+ (li as Element).classList.add(classNames.hover);
1477
+ }
1478
+ }
1479
+ }
1480
+
1481
+ //Data Source Related Functions
1482
+ private getSubDS(): DataSource[] {
1483
+ const levelKeys: string[] = this.curDSLevel;
1484
+ if (levelKeys.length) {
1485
+ let ds: DataSource[] = <DataSource[]>this.localData;
1486
+ for (const key of levelKeys) {
1487
+ const field: DataSource = {};
1488
+ // eslint-disable-next-line
1489
+ field[this.fields.id] = key as any;
1490
+ this.curDSJSON = <DataSource[] & DataSource>this.findItemFromDS(ds, field);
1491
+ const fieldData: DataSource = <DataSource>getFieldValues
1492
+ // eslint-disable-next-line no-unexpected-multiline
1493
+ (this.curDSJSON as { [key: string]: Object }, this.listBaseOption.fields);
1494
+ ds = this.curDSJSON ? <DataSource[] & DataSource>fieldData[this.fields.child] : ds;
1495
+ }
1496
+ return ds;
1497
+ }
1498
+ return <DataSource[]>this.localData;
1499
+ }
1500
+
1501
+ private getItemData(li: Element | HTMLElement | Fields): DataSource {
1502
+ const dataSource: DataSource[] | string[] | number[] | DataManager = this.dataSource instanceof DataManager ?
1503
+ this.localData : this.dataSource;
1504
+ const fields: Fields = this.getElementUID(li);
1505
+ let curDS: DataSource[];
1506
+ if (isNullOrUndefined(this.element.querySelector('.' + classNames.hasChild)) && this.fields.groupBy) {
1507
+ curDS = <DataSource[]>this.curViewDS;
1508
+ } else {
1509
+ curDS = <DataSource[]>dataSource;
1510
+ }
1511
+
1512
+ return <DataSource>this.findItemFromDS(curDS, <DataSource>fields);
1513
+ }
1514
+
1515
+ private findItemFromDS(
1516
+ dataSource: DataSource[],
1517
+ fields: DataSource,
1518
+ parent?: boolean): DataSource[] | DataSource {
1519
+
1520
+ let resultJSON: DataSource[] | DataSource;
1521
+
1522
+ if (dataSource && dataSource.length && fields) {
1523
+ dataSource.some((data: DataSource) => {
1524
+ const fieldData: DataSource =
1525
+ // eslint-disable-next-line
1526
+ <DataSource>getFieldValues(data as { [key: string]: Object } as any, this.listBaseOption.fields);
1527
+ //(!(fid) || id === fid) && (!(ftext) || text === ftext) && (!!fid || !!ftext)
1528
+ if ((fields[this.fields.id] || fields[this.fields.text]) &&
1529
+ (!fields[this.fields.id] || (!isNullOrUndefined(fieldData[this.fields.id]) &&
1530
+ fieldData[this.fields.id].toString()) === fields[this.fields.id].toString()) &&
1531
+ (!fields[this.fields.text] || fieldData[this.fields.text] === fields[this.fields.text])) {
1532
+ resultJSON = (parent ? <DataSource[] | DataSource>dataSource : data);
1533
+ } else if (typeof data !== 'object' && dataSource.indexOf(data) !== -1) {
1534
+ resultJSON = (parent ? <DataSource[] | DataSource>dataSource : data);
1535
+ } else if (!isNullOrUndefined(fields[this.fields.id]) && isNullOrUndefined(fieldData[this.fields.id])) {
1536
+ const li: Element = this.element.querySelector('[data-uid="'
1537
+ + fields[this.fields.id] + '"]');
1538
+ // eslint-disable-next-line
1539
+ if (li && (li as HTMLElement).innerText.trim() === fieldData[this.fields.text] as any) {
1540
+ resultJSON = data;
1541
+ }
1542
+ // eslint-disable-next-line
1543
+ } else if ((fieldData as object).hasOwnProperty(this.fields.child) && (fieldData[this.fields.child] as object[]).length) {
1544
+ resultJSON = <DataSource & DataSource[]>this.findItemFromDS(
1545
+ <DataSource[]>fieldData[this.fields.child], fields, parent);
1546
+ }
1547
+ return !!resultJSON;
1548
+ });
1549
+ } else {
1550
+ resultJSON = <DataSource[] & DataSource>dataSource;
1551
+ }
1552
+
1553
+ return <DataSource[] & DataSource>resultJSON;
1554
+ }
1555
+
1556
+ private getQuery(): Query {
1557
+ const columns: string[] = [];
1558
+ const query: Query = (this.query ? this.query : new Query());
1559
+ if (!this.query) {
1560
+ // eslint-disable-next-line
1561
+ for (const column of Object.keys((this.fields as any & { properties: object }).properties)) {
1562
+ if (column !== 'tableName' && !!((this.fields as DataSource)[`${column}`]) &&
1563
+ (this.fields as DataSource)[`${column}`] !==
1564
+ (ListBase.defaultMappedFields as DataSource)[`${column}`]
1565
+ && columns.indexOf((this.fields as { [key: string]: string })[`${column}`]) === -1) {
1566
+
1567
+ columns.push((this.fields as { [key: string]: string })[`${column}`]);
1568
+
1569
+ }
1570
+ }
1571
+ query.select(columns);
1572
+ // eslint-disable-next-line
1573
+ if ((this.fields as any & { properties: object }).properties.hasOwnProperty('tableName')) {
1574
+ query.from(this.fields.tableName);
1575
+ }
1576
+ }
1577
+
1578
+ return query;
1579
+ }
1580
+
1581
+ private setViewDataSource(dataSource: DataSource[] = <DataSource[]>this.localData): void {
1582
+ // eslint-disable-next-line
1583
+ let fieldValue: any = (isNullOrUndefined(this.fields.sortBy)) ? this.fields.text : this.fields.sortBy;
1584
+ let query: Query = ListBase.addSorting(this.sortOrder, fieldValue);
1585
+ if (dataSource && this.fields.groupBy) {
1586
+ if (this.sortOrder !== 'None') {
1587
+ this.curViewDS = ListBase.groupDataSource(
1588
+ ListBase.getDataSource(dataSource, query),
1589
+ this.listBaseOption.fields, this.sortOrder);
1590
+ } else {
1591
+ this.curViewDS = ListBase.groupDataSource(dataSource, this.listBaseOption.fields, this.sortOrder);
1592
+ }
1593
+ } else if (dataSource && this.sortOrder !== 'None') {
1594
+ this.curViewDS = ListBase.getDataSource(dataSource, query);
1595
+ } else {
1596
+ this.curViewDS = dataSource;
1597
+ }
1598
+ }
1599
+
1600
+ private isInAnimation(): boolean {
1601
+ return this.curUL.classList.contains('.e-animate');
1602
+ }
1603
+
1604
+ private renderRemoteLists(e: Object, listViewComponent: ListView): void {
1605
+ if (this.isDestroyed) { return; }
1606
+ this.localData = (e as ResultData).result;
1607
+ listViewComponent.removeElement(listViewComponent.contentContainer);
1608
+ this.renderList();
1609
+ this.trigger('actionComplete', e);
1610
+ }
1611
+
1612
+ private triggerActionFailure(e: Object): void {
1613
+ if (this.isDestroyed) { return; }
1614
+ this.trigger('actionFailure', e);
1615
+ }
1616
+
1617
+ private setLocalData(): void {
1618
+ this.trigger('actionBegin');
1619
+ // eslint-disable-next-line
1620
+ const listViewComponent: ListView = this;
1621
+ if (this.dataSource instanceof DataManager) {
1622
+ if ((this.dataSource as DataManager).ready) {
1623
+ (this.dataSource as DataManager).ready.then((e: Object) => {
1624
+ /* eslint-disable */
1625
+ this.isOffline = (this.dataSource as DataManager).dataSource.offline;
1626
+ if (this.dataSource instanceof DataManager && this.isOffline) {
1627
+ this.renderRemoteLists(e, listViewComponent);
1628
+ }
1629
+ }).catch((e: Object) => {
1630
+ this.triggerActionFailure(e);
1631
+ });
1632
+ } else {
1633
+ (this.dataSource as DataManager).executeQuery(this.getQuery()).then((e: object) => {
1634
+ this.renderRemoteLists(e, listViewComponent);
1635
+ }).catch((e: Object) => {
1636
+ this.triggerActionFailure(e);
1637
+ });
1638
+ }
1639
+ } else if (!this.dataSource || !(<DataSource[]>this.dataSource).length) {
1640
+ const ul: HTMLElement = <HTMLElement>this.element.querySelector('ul');
1641
+ if (ul) {
1642
+ remove(ul);
1643
+ this.setProperties({ dataSource: ListBase.createJsonFromElement(ul) }, true);
1644
+ this.localData = <DataSource[]>this.dataSource;
1645
+ this.renderList();
1646
+ this.trigger('actionComplete', { data: this.localData });
1647
+ }
1648
+ } else {
1649
+ this.localData = <DataSource[]>this.dataSource;
1650
+ this.renderList();
1651
+ this.trigger('actionComplete', { data: this.localData });
1652
+ }
1653
+ }
1654
+
1655
+ private reRender(): void {
1656
+ this.removeElement(this.headerEle);
1657
+ this.removeElement(this.ulElement);
1658
+ this.removeElement(this.contentContainer);
1659
+ // eslint-disable-next-line
1660
+ if ((this as any).isReact) {
1661
+ this.clearTemplate();
1662
+ }
1663
+ if (Object.keys(window).indexOf('ejsInterop') === -1) {
1664
+ this.element.innerHTML = '';
1665
+ }
1666
+ this.headerEle = this.ulElement = this.liCollection = undefined;
1667
+ this.header();
1668
+ this.setLocalData();
1669
+ }
1670
+
1671
+ private resetCurrentList(): void {
1672
+ this.setViewDataSource(this.curViewDS as DataSource[]);
1673
+ this.contentContainer.innerHTML = '';
1674
+ this.createList();
1675
+ this.renderIntoDom(this.curUL);
1676
+ }
1677
+
1678
+ private setAttributes(liElements: HTMLElement[]): void {
1679
+ for (let i: number = 0; i < liElements.length; i++) {
1680
+ const element: HTMLElement = liElements[i];
1681
+ if (element.classList.contains('e-list-item')) {
1682
+ element.setAttribute('id', this.element.id + '_' + element.getAttribute('data-uid'));
1683
+ element.setAttribute('tabindex', '-1');
1684
+ }
1685
+ }
1686
+ }
1687
+
1688
+ private createList(): void {
1689
+ this.currentLiElements = [];
1690
+ this.isNestedList = false;
1691
+ this.ulElement = this.curUL = ListBase.createList(
1692
+ this.createElement, this.curViewDS as DataSource[], this.listBaseOption, null, this);
1693
+ this.liCollection = <HTMLElement[] & NodeListOf<Element>>this.curUL.querySelectorAll('.' + classNames.listItem);
1694
+ this.setAttributes(this.liCollection);
1695
+ }
1696
+
1697
+ private exceptionEvent(e: object): void {
1698
+ this.trigger('actionFailure', e);
1699
+ }
1700
+ private UpdateCurrentUL(): void {
1701
+ this.ulElement = this.curUL = this.element.querySelector('.' + classNames.parentItem);
1702
+ if (this.curUL) {
1703
+ // eslint-disable-next-line
1704
+ (this as any).liCollection = this.curUL.querySelectorAll('.' + classNames.listItem);
1705
+ }
1706
+ }
1707
+
1708
+ private renderSubList(li: Element): void {
1709
+ this.liElement = li;
1710
+ const uID: string = li.getAttribute('data-uid');
1711
+ if (li.classList.contains(classNames.hasChild) && uID) {
1712
+ const ul: Element = closest(li.parentNode, '.' + classNames.parentItem);
1713
+ let ele: Element = this.element.querySelector('[pid=\'' + uID + '\']');
1714
+ this.curDSLevel.push(uID);
1715
+ this.setViewDataSource(this.getSubDS());
1716
+ if (!ele) {
1717
+ const data: DataSource[] = this.curViewDS as DataSource[];
1718
+ ele = ListBase.createListFromJson(this.createElement, data, this.listBaseOption, this.curDSLevel.length, null, this);
1719
+ // eslint-disable-next-line
1720
+ if ((this as any).isReact) {
1721
+ this.renderReactTemplates();
1722
+ }
1723
+ const lists: HTMLElement[] = <HTMLElement[] & NodeListOf<Element>>ele.querySelectorAll('.' + classNames.listItem);
1724
+ this.setAttributes(lists);
1725
+ ele.setAttribute('pID', <string>uID);
1726
+ (ele as HTMLElement).style.display = 'none';
1727
+ this.renderIntoDom(ele);
1728
+ }
1729
+ this.switchView(<HTMLElement>ul, <HTMLElement>ele);
1730
+ this.liCollection = <HTMLElement[] & NodeListOf<Element>>this.curUL.querySelectorAll('.' + classNames.listItem);
1731
+ if (this.selectedItems) {
1732
+ const fieldData: DataSource = <DataSource>
1733
+ getFieldValues(<DataSource | string>this.selectedItems.data, this.listBaseOption.fields);
1734
+
1735
+ // eslint-disable-next-line
1736
+ this.header((fieldData[this.listBaseOption.fields.text]) as any, true, 'header');
1737
+ }
1738
+ this.selectedLI = undefined;
1739
+ }
1740
+ }
1741
+
1742
+ private renderIntoDom(ele: Element): void {
1743
+ this.contentContainer.appendChild(ele);
1744
+ }
1745
+
1746
+ private renderList(data?: DataSource[]): void {
1747
+ this.setViewDataSource(data);
1748
+ if (this.enableVirtualization) {
1749
+ if (Object.keys(this.dataSource).length) {
1750
+ if ((this.template || this.groupTemplate) && !this.virtualizationModule.isNgTemplate()) {
1751
+ if ((this as any).isReact || (this as any).isVue) {
1752
+ if (typeof this.template == "string") {
1753
+ this.listBaseOption.template = null;
1754
+ }
1755
+ if (typeof this.groupTemplate == "string") {
1756
+ this.listBaseOption.groupTemplate = null;
1757
+ }
1758
+ } else {
1759
+ this.listBaseOption.template = null;
1760
+ this.listBaseOption.groupTemplate = null;
1761
+ }
1762
+ this.listBaseOption.itemCreated = this.virtualizationModule.createUIItem.bind(this.virtualizationModule);
1763
+ }
1764
+ }
1765
+ this.virtualizationModule.uiVirtualization();
1766
+ } else {
1767
+ this.createList();
1768
+ this.contentContainer = this.createElement('div', { className: classNames.container });
1769
+ this.element.appendChild(this.contentContainer);
1770
+ this.renderIntoDom(this.ulElement);
1771
+ // eslint-disable-next-line
1772
+ if ((this as any).isReact) {
1773
+ this.renderReactTemplates();
1774
+ }
1775
+ }
1776
+ }
1777
+
1778
+ private getElementUID(obj: Fields | HTMLElement | Element): Fields {
1779
+ let fields: DataSource = {};
1780
+ if (obj instanceof Element) {
1781
+ // eslint-disable-next-line
1782
+ fields[this.fields.id] = obj.getAttribute('data-uid') as any;
1783
+ } else {
1784
+ fields = <DataSource>obj;
1785
+ }
1786
+ return fields;
1787
+ }
1788
+
1789
+ /**
1790
+ * Initializes the ListView component rendering.
1791
+ */
1792
+
1793
+ public render(): void {
1794
+ this.element.classList.add(classNames.root);
1795
+ attributes(this.element, { role: 'list', tabindex: '0' });
1796
+ this.setCSSClass();
1797
+ this.setEnableRTL();
1798
+ this.setEnable();
1799
+ this.setSize();
1800
+ this.wireEvents();
1801
+ this.header();
1802
+ this.setLocalData();
1803
+ this.setHTMLAttribute();
1804
+ this.rippleFn = rippleEffect(this.element, {
1805
+ selector: '.' + classNames.listItem
1806
+ });
1807
+ this.renderComplete();
1808
+ }
1809
+
1810
+ /**
1811
+ * It is used to destroy the ListView component.
1812
+ */
1813
+
1814
+ public destroy(): void {
1815
+ // eslint-disable-next-line
1816
+ if ((this as any).isReact) {
1817
+ this.clearTemplate();
1818
+ }
1819
+ this.unWireEvents();
1820
+ const classAr: string[] = [classNames.root, classNames.disable, 'e-rtl',
1821
+ 'e-has-header', 'e-lib'].concat(this.cssClass.split(' ').filter((css: string) => css));
1822
+ removeClass([this.element], classAr);
1823
+ this.element.removeAttribute('role');
1824
+ this.element.removeAttribute('tabindex');
1825
+ this.curUL = this.ulElement = this.liCollection = this.headerEle = undefined;
1826
+ this.element.innerHTML = '';
1827
+ this.contentContainer = null;
1828
+ this.selectedItems = null;
1829
+ this.selectedLI = null;
1830
+ this.liElement = null;
1831
+ this.targetElement = null;
1832
+ this.currentLiElements = null;
1833
+ this.virtualCheckBox = null;
1834
+ super.destroy();
1835
+ }
1836
+
1837
+ /**
1838
+ * Switches back from the navigated sub list item.
1839
+ */
1840
+
1841
+ public back(): void {
1842
+ const pID: string = this.curDSLevel[this.curDSLevel.length - 1];
1843
+ if (pID === undefined || this.isInAnimation()) { return; }
1844
+ this.curDSLevel.pop();
1845
+ this.setViewDataSource(this.getSubDS());
1846
+ let toUL: HTMLElement = <HTMLElement>this.element.querySelector('[data-uid=\'' + pID + '\']');
1847
+ const fromUL: HTMLElement = this.curUL;
1848
+ if (!toUL) {
1849
+ this.createList();
1850
+ this.renderIntoDom(this.ulElement);
1851
+ toUL = this.curUL;
1852
+ } else {
1853
+ toUL = toUL.parentElement;
1854
+ }
1855
+ const fieldData: DataSource = <DataSource>getFieldValues(this.curDSJSON, this.listBaseOption.fields);
1856
+ // eslint-disable-next-line
1857
+ const text: string = fieldData[this.fields.text] as any;
1858
+ this.switchView(fromUL, toUL, true);
1859
+ this.removeFocus();
1860
+ const li: HTMLElement = <HTMLElement>this.element.querySelector('[data-uid=\'' + pID + '\']');
1861
+ li.classList.remove(classNames.disable);
1862
+ li.classList.add(classNames.focused);
1863
+ if(!(this.showCheckBox && li.querySelector('.' + classNames.checkboxIcon).classList.contains(classNames.checked))){
1864
+ li.classList.remove(classNames.selected);
1865
+ }
1866
+ this.liCollection = <HTMLElement[] & NodeListOf<Element>>this.curUL.querySelectorAll('.' + classNames.listItem);
1867
+ if (this.enableHtmlSanitizer) {
1868
+ this.setProperties({ headerTitle: SanitizeHtmlHelper.sanitize(this.headerTitle) }, true);
1869
+ }
1870
+ this.header((this.curDSLevel.length ? text : this.headerTitle), (this.curDSLevel.length ? true : false), 'header');
1871
+ }
1872
+
1873
+ /**
1874
+ * Selects the list item from the ListView by passing the elements or field object.
1875
+ *
1876
+ * @param {Fields | HTMLElement | Element} item - We can pass element Object or Fields as Object with ID and Text fields.
1877
+ */
1878
+
1879
+ public selectItem(item: Fields | HTMLElement | Element): void {
1880
+ if (this.enableVirtualization) {
1881
+ this.virtualizationModule.selectItem(item);
1882
+ } else if (this.showCheckBox) {
1883
+ this.setCheckboxLI(this.getLiFromObjOrElement(item));
1884
+ } else {
1885
+ if (isNullOrUndefined(item) === true) {
1886
+ this.removeSelect();
1887
+ }
1888
+ else {
1889
+ this.setSelectLI(this.getLiFromObjOrElement(item));
1890
+ }
1891
+
1892
+ }
1893
+ }
1894
+
1895
+ private getLiFromObjOrElement(obj: Fields | HTMLElement | Element): HTMLElement {
1896
+ let li: Element;
1897
+ const dataSource: DataSource[] | string[] | number[] | DataManager = this.dataSource instanceof DataManager ?
1898
+ this.localData : this.dataSource;
1899
+ if (!isNullOrUndefined(obj)) {
1900
+ if (typeof (dataSource as string[])[0] === 'string' || typeof (dataSource as number[])[0] === 'number') {
1901
+ if (obj instanceof Element) {
1902
+ const uid: string = (obj as HTMLElement).getAttribute('data-uid').toString();
1903
+ for (let i: number = 0; i < this.liCollection.length; i++) {
1904
+ if (this.liCollection[i].getAttribute('data-uid').toString() === uid) {
1905
+ li = this.liCollection[i] as HTMLElement;
1906
+ break;
1907
+ }
1908
+ }
1909
+ } else {
1910
+ Array.prototype.some.call(this.curUL.querySelectorAll('.' + classNames.listItem), (item: HTMLElement) => {
1911
+ if (item.innerText.trim() === obj.toString()) {
1912
+ li = item;
1913
+ return true;
1914
+ } else {
1915
+ return false;
1916
+ }
1917
+ });
1918
+ }
1919
+ } else {
1920
+ const resultJSON: DataSource[] | DataSource = this.getItemData(obj);
1921
+ const fieldData: DataSource =
1922
+ <DataSource>getFieldValues(resultJSON, this.listBaseOption.fields);
1923
+ if (resultJSON) {
1924
+ li = this.element.querySelector('[data-uid="'
1925
+ + (<DataSource>fieldData)[this.fields.id] + '"]');
1926
+ if (!this.enableVirtualization && isNullOrUndefined(li)) {
1927
+ const curLi: NodeListOf<Element> = this.element.querySelectorAll('.' + classNames.listItem);
1928
+ for (let i: number = 0; i < curLi.length; i++) {
1929
+ // eslint-disable-next-line
1930
+ if ((curLi[i] as HTMLElement).innerText.trim() === fieldData[this.fields.text] as any) {
1931
+ li = curLi[i];
1932
+ }
1933
+ }
1934
+ }
1935
+ }
1936
+ }
1937
+ }
1938
+ return <HTMLElement>li;
1939
+ }
1940
+
1941
+ /**
1942
+ * Selects multiple list items from the ListView.
1943
+ *
1944
+ * @param {Fields[] | HTMLElement[] | Element[]} item - We can pass array of
1945
+ * elements or array of fields Object with ID and Text fields.
1946
+ */
1947
+
1948
+ public selectMultipleItems(item: Fields[] | HTMLElement[] | Element[]): void {
1949
+ if (!isNullOrUndefined(item)) {
1950
+ for (let i: number = 0; i < item.length; i++) {
1951
+ if (!isNullOrUndefined(item[i])) {
1952
+ this.selectItem(item[i]);
1953
+ }
1954
+ }
1955
+ }
1956
+ }
1957
+
1958
+ private getParentId(): string[] {
1959
+ const parentId: string[] = [];
1960
+ if (this.isNestedList) {
1961
+ for (let i: number = this.curDSLevel.length - 1; i >= 0; i--) {
1962
+ parentId.push(this.curDSLevel[i]);
1963
+ }
1964
+ }
1965
+ return parentId;
1966
+ }
1967
+
1968
+ private updateSelectedId(): void {
1969
+ this.selectedId = [];
1970
+ const liCollection: any = this.curUL.getElementsByClassName(classNames.selected);
1971
+ for (let i: number = 0; i < liCollection.length; i++) {
1972
+ const tempData: DataSource = this.getItemData(liCollection[i] as HTMLElement);
1973
+ if (!isNullOrUndefined(tempData) && tempData[this.listBaseOption.fields.id]) {
1974
+ this.selectedId.push(tempData[this.listBaseOption.fields.id] as any)
1975
+ }
1976
+ }
1977
+ }
1978
+
1979
+ /**
1980
+ * Gets the details of the currently selected item from the list items.
1981
+ *
1982
+ */
1983
+
1984
+ public getSelectedItems(): SelectedItem | SelectedCollection | UISelectedItem | NestedListData {
1985
+ // eslint-disable-next-line
1986
+ let finalValue: any;
1987
+ let isCompleted: boolean = false;
1988
+ this.selectedId = [];
1989
+ const dataSource: DataSource[] | string[] | number[] | DataManager = this.dataSource instanceof DataManager ?
1990
+ this.localData : this.dataSource;
1991
+ if (this.enableVirtualization && !isCompleted) {
1992
+ finalValue = this.virtualizationModule.getSelectedItems();
1993
+ isCompleted = true;
1994
+ } else if (this.showCheckBox && !isCompleted) {
1995
+ // eslint-disable-next-line
1996
+ const liCollection: any = this.curUL.getElementsByClassName(classNames.selected);
1997
+ const liTextCollection: string[] = []; const liDataCollection: DataSource[] = []; this.selectedId = [];
1998
+ const dataParent: DataAndParent[] = [];
1999
+ for (let i: number = 0; i < liCollection.length; i++) {
2000
+ if (typeof (dataSource as string[])[0] === 'string' || typeof (dataSource as number[])[0] === 'number') {
2001
+ liTextCollection.push((liCollection[i] as HTMLElement).innerText.trim());
2002
+ } else {
2003
+ const tempData: DataSource = this.getItemData(liCollection[i] as HTMLElement);
2004
+ const fieldData: DataSource =
2005
+ <DataSource>getFieldValues(tempData, this.listBaseOption.fields);
2006
+ if (this.isNestedList) {
2007
+ dataParent.push({ data: tempData, parentId: this.getParentId() });
2008
+ } else {
2009
+ liDataCollection.push(tempData);
2010
+ }
2011
+ if (fieldData) {
2012
+ // eslint-disable-next-line
2013
+ liTextCollection.push(fieldData[this.listBaseOption.fields.text] as any);
2014
+ // eslint-disable-next-line
2015
+ this.selectedId.push(fieldData[this.listBaseOption.fields.id] as any);
2016
+ } else {
2017
+ liTextCollection.push(undefined);
2018
+ this.selectedId.push(undefined);
2019
+ }
2020
+ }
2021
+ }
2022
+ if ((typeof (dataSource as string[])[0] === 'string'
2023
+ || typeof (dataSource as number[])[0] === 'number')
2024
+ && !isCompleted) {
2025
+ finalValue = { item: liCollection, data: dataSource as string[] | number[], text: liTextCollection };
2026
+ isCompleted = true;
2027
+ }
2028
+ if (this.isNestedList && !isCompleted) {
2029
+ finalValue = { item: liCollection, data: dataParent, text: liTextCollection };
2030
+ isCompleted = true;
2031
+ } else if (!isCompleted) {
2032
+ finalValue = { item: liCollection, data: liDataCollection, text: liTextCollection };
2033
+ isCompleted = true;
2034
+ }
2035
+ } else if (!isCompleted) {
2036
+ const liElement: Element = this.element.getElementsByClassName(classNames.selected)[0];
2037
+ const fieldData: DataSource =
2038
+ <DataSource>getFieldValues(this.getItemData(liElement), this.listBaseOption.fields);
2039
+ if ((typeof (dataSource as string[])[0] === 'string'
2040
+ || typeof (dataSource as number[])[0] === 'number')
2041
+ && !isCompleted) {
2042
+ finalValue = (!isNullOrUndefined(liElement)) ? {
2043
+ item: liElement, data: dataSource as string[] | number[],
2044
+ text: (liElement as HTMLElement).innerText.trim()
2045
+ } : undefined;
2046
+ isCompleted = true;
2047
+ } else if (!isCompleted) {
2048
+ if (isNullOrUndefined(fieldData) || isNullOrUndefined(liElement)) {
2049
+ finalValue = undefined;
2050
+ isCompleted = true;
2051
+ } else {
2052
+ // eslint-disable-next-line
2053
+ this.selectedId.push(fieldData[this.listBaseOption.fields.id] as any);
2054
+ finalValue = {
2055
+ text: fieldData[this.listBaseOption.fields.text], item: liElement,
2056
+ data: this.getItemData(liElement)
2057
+ };
2058
+ isCompleted = true;
2059
+ }
2060
+ }
2061
+ }
2062
+ return finalValue;
2063
+ }
2064
+
2065
+ /**
2066
+ * Finds out an item details from the current list.
2067
+ *
2068
+ * @param {Fields | HTMLElement | Element} item - We can pass element Object or Fields as Object with ID and Text fields.
2069
+ */
2070
+
2071
+ public findItem(item: Fields | HTMLElement | Element): SelectedItem {
2072
+ return <SelectedItem & DataSource>this.getItemData(item);
2073
+ }
2074
+
2075
+ /**
2076
+ * Enables the disabled list items by passing the Id and text fields.
2077
+ *
2078
+ * @param {Fields | HTMLElement | Element} item - We can pass element Object or Fields as Object with ID and Text fields.
2079
+ */
2080
+
2081
+ public enableItem(item: Fields | HTMLElement | Element): void {
2082
+ this.setItemState(item, true);
2083
+ if (this.enableVirtualization) {
2084
+ this.virtualizationModule.enableItem(item);
2085
+ }
2086
+ }
2087
+
2088
+ /**
2089
+ * Disables the list items by passing the Id and text fields.
2090
+ *
2091
+ * @param {Fields | HTMLElement | Element} item - We can pass element Object or Fields as Object with ID and Text fields.
2092
+ */
2093
+
2094
+ public disableItem(item: Fields | HTMLElement | Element): void {
2095
+ this.setItemState(item, false);
2096
+ if (this.enableVirtualization) {
2097
+ this.virtualizationModule.disableItem(item);
2098
+ }
2099
+ }
2100
+
2101
+ //A function that used to set state of the list item like enable, disable.
2102
+ private setItemState(item: Fields | HTMLElement | Element, isEnable: boolean): void {
2103
+ const resultJSON: DataSource = this.getItemData(item);
2104
+ const fieldData: DataSource = <DataSource>getFieldValues(resultJSON, this.listBaseOption.fields);
2105
+ if (resultJSON) {
2106
+ const li: Element = this.element.querySelector('[data-uid="' + fieldData[this.fields.id] + '"]');
2107
+ if (isEnable) {
2108
+ if (li) { li.classList.remove(classNames.disable); }
2109
+ delete resultJSON[this.fields.enabled];
2110
+ } else if (!isEnable) {
2111
+ if (li) { li.classList.add(classNames.disable); }
2112
+ // eslint-disable-next-line
2113
+ resultJSON[this.fields.enabled] = false as any;
2114
+ }
2115
+ }
2116
+ }
2117
+
2118
+ /**
2119
+ * Shows the hide list item from the ListView.
2120
+ *
2121
+ * @param {Fields | HTMLElement | Element} item - We can pass element Object or Fields as Object with ID and Text fields.
2122
+ */
2123
+
2124
+ public showItem(item: Fields | HTMLElement | Element): void {
2125
+ this.showHideItem(item, false, '');
2126
+ if (this.enableVirtualization) {
2127
+ this.virtualizationModule.showItem(item);
2128
+ }
2129
+ }
2130
+
2131
+ /**
2132
+ * Hides an list item from the ListView.
2133
+ *
2134
+ * @param {Fields | HTMLElement | Element} item - We can pass element Object or Fields as Object with ID and Text fields.
2135
+ */
2136
+
2137
+ public hideItem(item: Fields | HTMLElement | Element): void {
2138
+ this.showHideItem(item, true, 'none');
2139
+ if (this.enableVirtualization) {
2140
+ this.virtualizationModule.hideItem(item);
2141
+ }
2142
+ }
2143
+
2144
+ private showHideItem(obj: Fields | HTMLElement | Element, isHide: boolean, display: string): void {
2145
+ const resultJSON: DataSource = this.getItemData(obj);
2146
+ const fieldData: DataSource = <DataSource>getFieldValues(resultJSON, this.listBaseOption.fields);
2147
+ if (resultJSON) {
2148
+ const li: HTMLElement = <HTMLElement>this.element.querySelector('[data-uid="' + fieldData[this.fields.id] + '"]');
2149
+ if (li) { li.style.display = display; }
2150
+ if (isHide) {
2151
+ // eslint-disable-next-line
2152
+ resultJSON[this.fields.isVisible] = false as any;
2153
+ } else {
2154
+ delete resultJSON[this.fields.isVisible];
2155
+ }
2156
+ }
2157
+ }
2158
+
2159
+ /**
2160
+ * Adds the new list item(s) to the current ListView.
2161
+ * To add a new list item(s) in the ListView, we need to pass the `data` as an array of items that need
2162
+ * to be added and `fields` as the target item to which we need to add the given item(s) as its children.
2163
+ * For example fields: { text: 'Name', tooltip: 'Name', id:'id'}
2164
+ *
2165
+ * @param {{Object}[]} data - JSON Array Data that need to add.
2166
+ *
2167
+ * @param {Fields} fields - Target item to add the given data as its children (can be null).
2168
+ *
2169
+ */
2170
+
2171
+ public addItem(data: { [key: string]: Object }[], fields: Fields = undefined): void {
2172
+ const dataSource: DataSource[] = this.dataSource instanceof DataManager
2173
+ ? this.localData : this.dataSource as DataSource[];
2174
+ this.addItemInternally(data, fields, dataSource);
2175
+ }
2176
+
2177
+ private addItemInternally(data: DataSource[], fields: Fields, dataSource: DataSource[]): void {
2178
+ if (data instanceof Array) {
2179
+ if (this.enableVirtualization) {
2180
+ this.virtualizationModule.addItem(data, fields, dataSource);
2181
+ } else {
2182
+ const ds: DataSource = <DataSource>this.findItemFromDS(
2183
+ <DataSource[]>dataSource, <DataSource>fields);
2184
+ let child: DataSource[];
2185
+ if (ds) {
2186
+ const fieldData: DataSource = <DataSource>getFieldValues(ds, this.listBaseOption.fields);
2187
+ child = <DataSource[]>fieldData[this.fields.child];
2188
+ if (!child) {
2189
+ child = [];
2190
+ }
2191
+ child = child.concat(data);
2192
+ }
2193
+ // check for whether target is nested level or top level in list
2194
+ if (ds instanceof Array) {
2195
+ for (let i: number = 0; i < data.length; i++) {
2196
+ dataSource.push(data[i]);
2197
+ this.setViewDataSource(dataSource);
2198
+ // since it is top level target, get the content container's first child
2199
+ // as it is always the top level UL
2200
+ const targetUL: HTMLElement = this.contentContainer
2201
+ ? <HTMLElement>this.contentContainer.children[0]
2202
+ : null;
2203
+ // check for whether the list was previously empty or not, if it is
2204
+ // proceed to call initial render
2205
+ if (this.contentContainer && targetUL) {
2206
+ this.addItemIntoDom(data[i], targetUL, this.curViewDS as DataSource[]);
2207
+ } else {
2208
+ this.reRender();
2209
+ }
2210
+ }
2211
+ this.liCollection = <HTMLElement[] & NodeListOf<Element>>this.curUL.querySelectorAll('.' + classNames.listItem);
2212
+ } else {
2213
+ // proceed as target item is in nested level, only if it is a valid target ds
2214
+ if (ds) {
2215
+ ds[this.fields.child] = child;
2216
+ this.addItemInNestedList(ds, data);
2217
+ }
2218
+ }
2219
+ }
2220
+ }
2221
+ }
2222
+
2223
+ private addItemInNestedList(targetItemData: DataSource, itemQueue: DataSource[]): void {
2224
+ // eslint-disable-next-line
2225
+ const targetItemId: string = targetItemData[this.fields.id] as any;
2226
+ const targetChildDS: DataSource[] = targetItemData[this.fields.child] as DataSource[];
2227
+ const isAlreadyRenderedUL: HTMLElement | null = this.element.querySelector('[pid=\'' + targetItemId + '\']');
2228
+ const targetLi: HTMLElement | null = this.element.querySelector('[data-uid=\'' + targetItemId + '\']');
2229
+ const targetUL: HTMLElement | null = isAlreadyRenderedUL
2230
+ ? isAlreadyRenderedUL
2231
+ : targetLi
2232
+ ? <HTMLElement>closest(targetLi, 'ul')
2233
+ : null;
2234
+ const targetDS: DataSource[] = isAlreadyRenderedUL ? targetChildDS : [targetItemData];
2235
+ const isTargetEmptyChild: boolean = targetLi ? !targetLi.classList.contains(classNames.hasChild) : false;
2236
+ let isRefreshTemplateNeeded: boolean = false;
2237
+ // if li element is already rendered, that element needs to be refreshed so that
2238
+ // it becomes child viewable due to new child items are added now
2239
+ if (isTargetEmptyChild) {
2240
+ const targetRefreshedElement: HTMLElement[] = ListBase.createListItemFromJson(
2241
+ this.createElement, targetDS, this.listBaseOption, null, null, this);
2242
+ this.setAttributes(targetRefreshedElement);
2243
+ targetUL.insertBefore(targetRefreshedElement[0], targetLi);
2244
+ detach(targetLi);
2245
+ isRefreshTemplateNeeded = true;
2246
+ }
2247
+ // if it is already rendered element, we need to create and append new elements
2248
+ if (isAlreadyRenderedUL && itemQueue) {
2249
+ for (let i: number = 0; i < itemQueue.length; i++) {
2250
+ targetDS.push(itemQueue[i]);
2251
+ this.addItemIntoDom(itemQueue[i], targetUL, targetDS);
2252
+ }
2253
+ isRefreshTemplateNeeded = true;
2254
+ }
2255
+ }
2256
+
2257
+ private addItemIntoDom(currentItem: DataSource, targetUL: HTMLElement, curViewDS: DataSource[]): void {
2258
+ const index: number = curViewDS.indexOf(currentItem);
2259
+ this.addListItem(currentItem, index, targetUL, curViewDS);
2260
+ const curItemDS: DataSource = curViewDS[index - 1];
2261
+ if (curItemDS && curItemDS.isHeader && (curItemDS.items as DataSource[]).length === 1) {
2262
+ this.addListItem(curItemDS, (index - 1), targetUL, curViewDS);
2263
+ }
2264
+ }
2265
+
2266
+ private addListItem(dataSource: DataSource, index: number, ulElement: HTMLElement, curViewDS: DataSource[]): void {
2267
+ let target: HTMLElement = this.getLiFromObjOrElement((curViewDS as DataSource[])[index + 1]) ||
2268
+ this.getLiFromObjOrElement((curViewDS as DataSource[])[index + 2]) || null;
2269
+ const li: HTMLElement[] = ListBase.createListItemFromJson(this.createElement, [dataSource], this.listBaseOption, null, null, this);
2270
+ this.setAttributes(li);
2271
+ // eslint-disable-next-line
2272
+ if (this.template && (this as any).isReact) {
2273
+ this.renderReactTemplates();
2274
+ }
2275
+ if (this.fields.groupBy && curViewDS[index + 1] && curViewDS[index + 1].isHeader) {
2276
+ let targetEle: HTMLElement = this.getLiFromObjOrElement(curViewDS[index - 1]);
2277
+ if (targetEle) {
2278
+ target = targetEle.nextElementSibling as HTMLElement;
2279
+ }
2280
+ }
2281
+ ulElement.insertBefore(li[0], target);
2282
+ }
2283
+
2284
+ /**
2285
+ * Removes the list item from the data source based on a passed
2286
+ * element like fields: { text: 'Name', tooltip: 'Name', id:'id'}
2287
+ *
2288
+ * @param {Fields | HTMLElement | Element} item - We can pass element Object or Fields as Object with ID and Text fields.
2289
+ */
2290
+
2291
+ public removeItem(item: Fields | HTMLElement | Element): void {
2292
+ const listDataSource: DataSource[] = this.dataSource instanceof DataManager
2293
+ ? this.localData : this.dataSource as DataSource[];
2294
+ if (this.enableVirtualization) {
2295
+ this.virtualizationModule.removeItem(item);
2296
+ } else {
2297
+ this.removeItemFromList(item, listDataSource);
2298
+ }
2299
+ }
2300
+
2301
+ private removeItemFromList(obj: Fields | Element | HTMLElement, listDataSource: DataSource[]): void {
2302
+ const curViewDS: DataSource[] = this.curViewDS as DataSource[];
2303
+ const fields: Fields = obj instanceof Element ? this.getElementUID(obj) : obj;
2304
+ let dataSource: DataSource[] | DataSource;
2305
+ // eslint-disable-next-line prefer-const
2306
+ dataSource = this.findItemFromDS(listDataSource, <DataSource>fields, true);
2307
+ if (dataSource) {
2308
+ let data: DataSource | DataSource[];
2309
+ // eslint-disable-next-line prefer-const
2310
+ data = this.findItemFromDS(dataSource as DataSource[], <DataSource>fields);
2311
+ const index: number = curViewDS.indexOf(data as DataSource);
2312
+ const li: HTMLElement = this.getLiFromObjOrElement(obj);
2313
+ let groupLi: HTMLElement;
2314
+ this.validateNestedView(li);
2315
+ if (this.fields.groupBy && this.curViewDS[index - 1] &&
2316
+ curViewDS[index - 1].isHeader &&
2317
+ (curViewDS[index - 1].items as DataSource[]).length === 1) {
2318
+ if (li && (li.previousElementSibling as HTMLElement).classList.contains(classNames.groupListItem) &&
2319
+ (isNullOrUndefined(li.nextElementSibling) || (li.nextElementSibling &&
2320
+ (li.nextElementSibling as HTMLElement).classList.contains(classNames.groupListItem)))) {
2321
+ groupLi = li.previousElementSibling as HTMLElement;
2322
+ }
2323
+ }
2324
+ if (li) {
2325
+ detach(li);
2326
+ }
2327
+ if (groupLi) {
2328
+ detach(groupLi);
2329
+ }
2330
+ // eslint-disable-next-line
2331
+ const foundData: any = ((<DataSource[]>dataSource).length - 1) <= 0
2332
+ ? this.findParent(
2333
+ this.localData,
2334
+ this.fields.id,
2335
+ // eslint-disable-next-line
2336
+ (value: string) => value === (data as DataSource)[this.fields.id] as any,
2337
+ null) : null;
2338
+ const dsIndex: number = (dataSource as DataSource[]).indexOf(data as DataSource);
2339
+ (dataSource as DataSource[]).splice(dsIndex, 1);
2340
+ this.setViewDataSource(listDataSource);
2341
+ if (foundData
2342
+ && foundData.parent
2343
+ && Array.isArray(foundData.parent[this.fields.child])
2344
+ && foundData.parent[this.fields.child].length <= 0) {
2345
+ const parentLi: HTMLElement | null = this.getLiFromObjOrElement(foundData.parent);
2346
+ if (parentLi) {
2347
+ const li: HTMLElement[] = ListBase.createListItemFromJson(
2348
+ this.createElement,
2349
+ [foundData.parent],
2350
+ this.listBaseOption,
2351
+ null,
2352
+ null,
2353
+ this);
2354
+ this.setAttributes(li);
2355
+ parentLi.parentElement.insertBefore(li[0], parentLi);
2356
+ parentLi.parentElement.removeChild(parentLi);
2357
+ }
2358
+ }
2359
+ if (dataSource.length <= 0) {
2360
+ this.back();
2361
+ }
2362
+ this.liCollection = Array.prototype.slice.call(this.element.querySelectorAll('.' + classNames.listItem));
2363
+ }
2364
+ }
2365
+
2366
+ // validate before removing an element whether the current view is inside target element's child view
2367
+ private validateNestedView(li: HTMLElement | null): void {
2368
+ const liID: string = li ? li.getAttribute('data-uid').toString().toLowerCase() : null;
2369
+ if (liID && this.curDSLevel && this.curDSLevel.length > 0) {
2370
+ while (this.curDSLevel.some((id: string) => id.toString().toLowerCase() === liID)) {
2371
+ this.back();
2372
+ }
2373
+ }
2374
+ }
2375
+
2376
+ /**
2377
+ * Removes multiple items from the ListView by passing the array of elements or array of field objects.
2378
+ *
2379
+ * @param {Fields[] | HTMLElement[] | Element[]} item - We can pass array of elements or array of field Object with ID and Text fields.
2380
+ */
2381
+
2382
+ public removeMultipleItems(item: HTMLElement[] | Element[] | Fields[]): void {
2383
+ if (item.length) {
2384
+ for (let i: number = 0; i < item.length; i++) {
2385
+ this.removeItem(item[i]);
2386
+ }
2387
+ }
2388
+ }
2389
+
2390
+ // eslint-disable-next-line
2391
+ private findParent(dataSource: any, id: string, callback: Function, parent: object): object {
2392
+ // eslint-disable-next-line no-prototype-builtins
2393
+ if (dataSource.hasOwnProperty(id) && callback(dataSource[id]) === true) {
2394
+ return extend({}, dataSource);
2395
+ }
2396
+
2397
+ for (let i: number = 0; i < Object.keys(dataSource).length; i++) {
2398
+ if (dataSource[Object.keys(dataSource)[i]]
2399
+ && typeof dataSource[Object.keys(dataSource)[i]] === 'object') {
2400
+ // eslint-disable-next-line
2401
+ const result: any = this.findParent(dataSource[Object.keys(dataSource)[i]], id, callback, dataSource);
2402
+ if (result != null) {
2403
+ if (!result.parent) { result.parent = parent; }
2404
+ return result;
2405
+ }
2406
+ }
2407
+ }
2408
+ return null;
2409
+ }
2410
+
2411
+ // Module Required function
2412
+ protected getModuleName(): string {
2413
+ return 'listview';
2414
+ }
2415
+
2416
+ public requiredModules(): ModuleDeclaration[] {
2417
+ const modules: ModuleDeclaration[] = [];
2418
+ if (this.enableVirtualization) {
2419
+ modules.push({ args: [this], member: 'virtualization' });
2420
+ }
2421
+
2422
+ return modules;
2423
+ }
2424
+
2425
+
2426
+ /**
2427
+ * Get the properties to be maintained in the persisted state.
2428
+ */
2429
+
2430
+ protected getPersistData(): string {
2431
+ return this.addOnPersist(['cssClass', 'enableRtl', 'htmlAttributes',
2432
+ 'enable', 'fields', 'animation', 'headerTitle',
2433
+ 'sortOrder', 'showIcon', 'height', 'width', 'showCheckBox', 'checkBoxPosition','selectedId']);
2434
+ }
2435
+
2436
+ }
2437
+
2438
+ interface ResultData {
2439
+ result: { [key: string]: object }[];
2440
+ }
2441
+
2442
+ /** @hidden */
2443
+ export interface ClassNames {
2444
+ root: string;
2445
+ hover: string;
2446
+ focused: string;
2447
+ selected: string;
2448
+ parentItem: string;
2449
+ listItem: string;
2450
+ hasChild: string;
2451
+ view: string;
2452
+ header: string;
2453
+ text: string;
2454
+ headerText: string;
2455
+ headerTemplateText: string;
2456
+ listItemText: string;
2457
+ listIcon: string;
2458
+ textContent: string;
2459
+ groupListItem: string;
2460
+ disable: string;
2461
+ container: string;
2462
+ backIcon: string;
2463
+ backButton: string;
2464
+ icon: string;
2465
+ checkboxWrapper: string;
2466
+ checkbox: string;
2467
+ checked: string;
2468
+ checkboxIcon: string;
2469
+ checklist: string;
2470
+ checkboxRight: string;
2471
+ checkboxLeft: string;
2472
+ listviewCheckbox: string;
2473
+ itemCheckList: string;
2474
+ virtualElementContainer: string;
2475
+ }
2476
+
2477
+ /**
2478
+ * An interface that holds list selected item.
2479
+ */
2480
+ export interface ListSelectedItem {
2481
+ /**
2482
+ * Specifies the selected item dataSource collection.
2483
+ *
2484
+ * @isGenericType true
2485
+ */
2486
+ data?: object[];
2487
+ /**
2488
+ * Specifies the selected item text collection.
2489
+ */
2490
+ text?: string[];
2491
+ /**
2492
+ * Specifies index of the selected element.
2493
+ * Available only in virtualization.
2494
+ */
2495
+ index?: number[];
2496
+ /**
2497
+ * Specifies the hierarchical parent id collection of the current view.
2498
+ * Available only in nested list with checkbox enabled.
2499
+ */
2500
+ parentId?: string[];
2501
+ }
2502
+
2503
+ /**
2504
+ * An interface that holds selected item.
2505
+ */
2506
+ export interface SelectedItem {
2507
+ /**
2508
+ * It denotes the selected item text.
2509
+ */
2510
+ text: string;
2511
+
2512
+ /**
2513
+ * It denotes the selected item list element.
2514
+ *
2515
+ */
2516
+ item: HTMLElement | Element;
2517
+
2518
+ /**
2519
+ * It denotes the selected item dataSource JSON object.
2520
+ *
2521
+ * @isGenericType true
2522
+ */
2523
+ data: { [key: string]: Object } | string[] | number[];
2524
+
2525
+ }
2526
+
2527
+ /**
2528
+ * An interface that holds selected collection.
2529
+ */
2530
+ export interface SelectedCollection {
2531
+ /**
2532
+ * It denotes the selected item text data or collection.
2533
+ */
2534
+ text: string | string[];
2535
+
2536
+ /**
2537
+ * It denotes the selected item list element or element collection.
2538
+ */
2539
+ item: HTMLElement | Element[] | NodeList;
2540
+
2541
+ /**
2542
+ * It denotes the selected item dataSource JSON object or object collection.
2543
+ *
2544
+ * @isGenericType true
2545
+ */
2546
+ data: { [key: string]: object } | { [key: string]: object }[] | string[] | number[];
2547
+ }
2548
+
2549
+ /**
2550
+ * An interface that holds UI selected item.
2551
+ */
2552
+ export interface UISelectedItem {
2553
+ /**
2554
+ * It denotes the selected item text data or collection.
2555
+ */
2556
+ text: string | number | string[] | number[];
2557
+
2558
+ /**
2559
+ * It denotes the selected item list element or element collection.
2560
+ */
2561
+ item?: HTMLElement | Element[] | NodeList;
2562
+
2563
+ /**
2564
+ * It denotes the selected item dataSource JSON object or object collection.
2565
+ *
2566
+ * @isGenericType true
2567
+ */
2568
+ data: { [key: string]: object } | { [key: string]: object }[] | string | number | string[] | number[];
2569
+ /**
2570
+ * It is used to denote the index of the selected element.
2571
+ */
2572
+ index?: number | number[];
2573
+ /**
2574
+ * It is used to check whether the element is checked or not.
2575
+ */
2576
+ isChecked?: boolean;
2577
+ }
2578
+
2579
+ /**
2580
+ * An interface that holds details of data and parent.
2581
+ */
2582
+ export interface DataAndParent {
2583
+ /**
2584
+ * It denotes the selected item dataSource JSON object or object collection.
2585
+ *
2586
+ * @isGenericType true
2587
+ */
2588
+ data: { [key: string]: object } | { [key: string]: object }[] | string[];
2589
+ /**
2590
+ * It denotes the selected item's parent id;
2591
+ */
2592
+ parentId: string[];
2593
+ }
2594
+
2595
+ /**
2596
+ * An interface that holds nested list data.
2597
+ */
2598
+ export interface NestedListData {
2599
+ /**
2600
+ * It denotes the selected item text data or collection.
2601
+ */
2602
+ text: string | string[];
2603
+ /**
2604
+ * It denotes the selected item list element or element collection.
2605
+ */
2606
+ item: HTMLElement | Element[] | NodeList;
2607
+ /**
2608
+ * It denotes the selected item dataSource JSON object with it's parent id.
2609
+ */
2610
+ data: DataAndParent[];
2611
+ }
2612
+
2613
+ /**
2614
+ * An interface that holds selected event arguments
2615
+ */
2616
+ export interface SelectEventArgs extends BaseEventArgs, SelectedItem {
2617
+ /**
2618
+ * Specifies that event has triggered by user interaction.
2619
+ */
2620
+ isInteracted: boolean;
2621
+ /**
2622
+ * Specifies that event argument when event raised by other event.
2623
+ */
2624
+ event: MouseEvent | KeyboardEvent;
2625
+ /**
2626
+ * It is used to denote the index of the selected element.
2627
+ */
2628
+ index: number;
2629
+ /**
2630
+ * It is used to check whether the element is checked or not.
2631
+ */
2632
+ isChecked?: boolean;
2633
+ /**
2634
+ * Cancels the item selection if the value is true
2635
+ */
2636
+ cancel: boolean;
2637
+ }
2638
+
2639
+ /**
2640
+ * An interface that holds item created event arguments
2641
+ */
2642
+ export interface ItemCreatedArgs {
2643
+ curData: { [key: string]: object };
2644
+ dataSource: { [key: string]: object } | string[];
2645
+ fields: FieldsMapping;
2646
+ item: HTMLElement;
2647
+ options: ListBaseOptions;
2648
+ text: string;
2649
+ }
2650
+
2651
+ interface DataSource {
2652
+ [key: string]: object;
2653
+ }