• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { LitTableColumn } from './lit-table-column';
17import { LitProgressBar } from './../progress-bar/LitProgressBar';
18import { BaseElement, element } from '../BaseElement';
19import '../utils/Template';
20import { TableRowObject } from './TableRowObject';
21import {
22  addCopyEventListener,
23  addSelectAllBox,
24  createDownUpSvg,
25  exportData, fixed,
26  formatExportData,
27  formatName,
28  iconPadding,
29  iconWidth,
30  litPageTableHtml
31} from './LitTableHtml';
32
33@element('lit-page-table')
34export class LitPageTable extends BaseElement {
35  meauseRowElement: HTMLDivElement | undefined;
36  currentRecycleList: HTMLDivElement[] = [];
37  currentTreeDivList: HTMLDivElement[] = [];
38  public rememberScrollTop = false;
39  public getItemTextColor?: (data: any) => string;
40  public itemTextHandleMap: Map<string, (value: any) => string> = new Map<string, (value: any) => string>();
41  public exportLoading: boolean = false;
42  public exportTextHandleMap: Map<string, (value: any) => string> = new Map<string, (value: any) => string>();
43  public ds: Array<any> = [];
44  public recycleDs: Array<any> = [];
45  public tableColumns: NodeListOf<LitTableColumn> | undefined;
46  public tableElement: HTMLDivElement | null | undefined;
47  public columns: Array<Element> | null | undefined;
48  public exportProgress: LitProgressBar | null | undefined;
49  private gridTemplateColumns: Array<string> = [];
50  private st: HTMLSlotElement | null | undefined;
51  private theadElement: HTMLDivElement | null | undefined;
52  private tbodyElement: HTMLDivElement | undefined | null;
53  private treeElement: HTMLDivElement | undefined | null;
54  private previousDiv: HTMLDivElement | undefined | null;
55  private nextDiv: HTMLDivElement | undefined | null;
56  private currentPageDiv: HTMLDivElement | undefined | null;
57  private targetPageInput: HTMLInputElement | undefined | null;
58  private jumpDiv: HTMLDivElement | undefined | null;
59  private colCount: number = 0;
60  private isScrollXOutSide: boolean = false;
61  private currentPage: number = 0;
62  startSkip: number = 0;
63
64  static get observedAttributes() {
65    return [
66      'scroll-y',
67      'selectable',
68      'no-head',
69      'grid-line',
70      'defaultOrderColumn',
71      'hideDownload',
72      'loading',
73      'pagination',
74    ];
75  }
76
77  set loading(value: boolean) {
78    this.exportProgress!.loading = value;
79  }
80
81  get hideDownload() {
82    return this.hasAttribute('hideDownload');
83  }
84
85  set hideDownload(value) {
86    if (value) {
87      this.setAttribute('hideDownload', '');
88    } else {
89      this.removeAttribute('hideDownload');
90    }
91  }
92
93  get selectable() {
94    return this.hasAttribute('selectable');
95  }
96
97  set selectable(value) {
98    if (value) {
99      this.setAttribute('selectable', '');
100    } else {
101      this.removeAttribute('selectable');
102    }
103  }
104
105  get scrollY() {
106    return this.getAttribute('scroll-y') || 'auto';
107  }
108
109  set scrollY(value) {
110    this.setAttribute('scroll-y', value);
111  }
112  get recycleDataSource() {
113    return this.ds || [];
114  }
115
116  set recycleDataSource(value) {
117    this.isScrollXOutSide = this.tableElement!.scrollWidth > this.tableElement!.clientWidth;
118    this.ds = value;
119    this.toTop();
120    this.currentPage = 0;
121    this.pagination = value.length > 0 && Array.isArray(value[0]);
122    if (this.pagination) {
123      this.currentPageDiv!.textContent = `第 ${this.currentPage + 1} 页,共 ${this.ds.length} 页`;
124      this.targetPageInput!.value = '';
125    }
126    if (this.hasAttribute('tree')) {
127      this.recycleDs = this.meauseTreeRowElement(value);
128    } else {
129      this.recycleDs = this.meauseAllRowHeight(this.pagination ? value[0] : value);
130    }
131  }
132
133  set pagination(value: boolean) {
134    if (value) {
135      this.setAttribute('pagination', '');
136    } else {
137      this.removeAttribute('pagination');
138    }
139  }
140
141  get pagination() {
142    return this.hasAttribute('pagination');
143  }
144
145  initElements(): void {
146    this.tableElement = this.shadowRoot?.querySelector('.table');
147    this.st = this.shadowRoot?.querySelector('#slot');
148    this.theadElement = this.shadowRoot?.querySelector('.thead');
149    this.exportProgress = this.shadowRoot?.querySelector('#export_progress_bar');
150    this.treeElement = this.shadowRoot?.querySelector('.tree');
151    this.tbodyElement = this.shadowRoot?.querySelector('.body');
152    this.tableColumns = this.querySelectorAll<LitTableColumn>('lit-table-column');
153    this.previousDiv = this.shadowRoot?.querySelector<HTMLDivElement>('#previousPage');
154    this.nextDiv = this.shadowRoot?.querySelector<HTMLDivElement>('#nextPage');
155    this.currentPageDiv = this.shadowRoot?.querySelector<HTMLDivElement>('#currentPage');
156    this.targetPageInput = this.shadowRoot?.querySelector<HTMLInputElement>('#targetPage');
157    this.jumpDiv = this.shadowRoot?.querySelector<HTMLDivElement>('#jumpPage');
158    this.initPageEventListener();
159  }
160
161  initPageEventListener(): void {
162    this.previousDiv!.onclick = () => {
163      if (this.currentPage > 0) {
164        this.currentPage = Math.max(this.currentPage - 1, 0);
165        this.showCurrentPageData();
166      }
167    };
168    this.nextDiv!.onclick = () => {
169      if (this.currentPage < this.ds.length - 1) {
170        this.currentPage = Math.min(this.currentPage + 1, this.ds.length - 1);
171        this.showCurrentPageData();
172      }
173    };
174    this.jumpDiv!.onclick = () => {
175      let value = this.targetPageInput!.value;
176      let reg = /^[0-9]*$/;
177      if (value.length > 0 && reg.test(value)) {
178        let target = parseInt(value);
179        if (target < 1) {
180          target = 1;
181        }
182        if (target > this.ds.length) {
183          target = this.ds.length;
184        }
185        this.targetPageInput!.value = `${target}`;
186        if (this.currentPage !== target - 1) {
187          this.currentPage = target - 1;
188          this.showCurrentPageData();
189        }
190      } else {
191        this.targetPageInput!.value = '';
192      }
193    };
194  }
195
196  toTop() {
197    if (this.rememberScrollTop) {
198      this.tableElement!.scrollTop = 0;
199      this.tableElement!.scrollLeft = 0;
200    } else {
201      this.tableElement!.scrollTop = 0;
202      this.tableElement!.scrollLeft = 0;
203    }
204  }
205
206  showCurrentPageData() {
207    this.toTop();
208    this.currentPageDiv!.textContent = `第 ${(this.currentPage || 0) + 1} 页,共 ${this.ds.length} 页`;
209    if (this.hasAttribute('tree')) {
210      this.recycleDs = this.meauseTreeRowElement(this.ds[this.currentPage]);
211    } else {
212      this.recycleDs = this.meauseAllRowHeight(this.ds[this.currentPage]);
213    }
214  }
215
216  initHtml(): string {
217    return litPageTableHtml;
218  }
219
220  dataExportInit(): void {
221    let exportDiv = this.shadowRoot!.querySelector<HTMLDivElement>('.export');
222    exportDiv &&
223      (exportDiv.onclick = (): void => {
224        this.exportData();
225      });
226  }
227
228  exportData(): void {
229    exportData(this);
230  }
231
232  formatExportData(dataSource: any[]): any[] {
233    return formatExportData(dataSource, this);
234  }
235
236  //当 custom element首次被插入文档DOM时,被调用。
237  connectedCallback(): void {
238    this.dataExportInit();
239    addCopyEventListener(this);
240    this.colCount = this.tableColumns!.length;
241    this.st?.addEventListener('slotchange', () => {
242      this.theadElement!.innerHTML = '';
243      setTimeout(() => {
244        this.columns = this.st!.assignedElements();
245        let rowElement = document.createElement('div');
246        rowElement.classList.add('th');
247        this.gridTemplateColumns = [];
248        let pageArea: Array<any> = [];
249        addSelectAllBox(rowElement, this);
250        this.resolvingArea(this.columns, 0, 0, pageArea, rowElement);
251        pageArea.forEach((rows, j, array) => {
252          for (let i = 0; i < this.colCount; i++) {
253            if (!rows[i]) rows[i] = array[j - 1][i];
254          }
255        });
256        if (this.selectable) {
257          let s = pageArea.map((a) => '"_checkbox_ ' + a.map((aa: any) => aa.t).join(' ') + '"').join(' ');
258          rowElement.style.gridTemplateColumns = '60px ' + this.gridTemplateColumns.join(' ');
259          rowElement.style.gridTemplateRows = `repeat(${pageArea.length},1fr)`;
260          rowElement.style.gridTemplateAreas = s;
261        } else {
262          let s = pageArea.map((a) => '"' + a.map((aa: any) => aa.t).join(' ') + '"').join(' ');
263          rowElement.style.gridTemplateColumns = this.gridTemplateColumns.join(' ');
264          rowElement.style.gridTemplateRows = `repeat(${pageArea.length},1fr)`;
265          rowElement.style.gridTemplateAreas = s;
266        }
267        this.theadElement!.innerHTML = '';
268        this.theadElement!.append(rowElement);
269        this.treeElement!.style.top = this.theadElement?.clientHeight + 'px';
270      });
271    });
272    this.shadowRoot!.addEventListener('load', function (event) {});
273    this.tableElement!.addEventListener('mouseout', (ev) => this.mouseOut());
274  }
275
276  resolvingArea(columns: any, x: any, y: any, area: Array<any>, rowElement: HTMLDivElement) {
277    columns.forEach((a: any, i: any) => {
278      if (!area[y]) area[y] = [];
279      let key = a.getAttribute('key') || a.getAttribute('title');
280      if (a.tagName === 'LIT-TABLE-GROUP') {
281        let childList = [...a.children].filter((a) => a.tagName !== 'TEMPLATE');
282        let len = a.querySelectorAll('lit-table-column').length;
283        if (childList.length > 0) {
284          this.resolvingArea(childList, x, y + 1, area, rowElement);
285        }
286        for (let j = 0; j < len; j++) {
287          area[y][x] = { x, y, t: key };
288          x++;
289        }
290        let head = document.createElement('div');
291        head.classList.add('td');
292        head.style.justifyContent = a.getAttribute('align');
293        head.style.borderBottom = '1px solid #f0f0f0';
294        head.style.gridArea = key;
295        head.innerText = a.title;
296        if (a.hasAttribute('fixed')) {
297          fixed(head, a.getAttribute('fixed'), '#42b983');
298        }
299        rowElement.append(head);
300      } else if (a.tagName === 'LIT-TABLE-COLUMN') {
301        area[y][x] = { x, y, t: key };
302        x++;
303        let h: any = document.createElement('div');
304        h.classList.add('td');
305        if (i > 0) {
306          let resizeDiv: HTMLDivElement = document.createElement('div');
307          resizeDiv.classList.add('resize');
308          h.appendChild(resizeDiv);
309          this.resizeEventHandler(rowElement, resizeDiv, i);
310        }
311        this.resolvingAreaColumnOrder(a, i, key, h);
312        h.style.justifyContent = a.getAttribute('align');
313        this.gridTemplateColumns.push(a.getAttribute('width') || '1fr');
314        h.style.gridArea = key;
315        let titleLabel = document.createElement('label');
316        titleLabel.textContent = a.title;
317        h.appendChild(titleLabel);
318        if (a.hasAttribute('fixed')) {
319          fixed(h, a.getAttribute('fixed'), '#42b983');
320        }
321        rowElement.append(h);
322      }
323    });
324  };
325
326  resolvingAreaColumnOrder(column: any, index: number, key: string,head: any): void {
327    if (column.hasAttribute('order')) {
328      (head as any).sortType = 0;
329      head.classList.add('td-order');
330      head.style.position = 'relative';
331      let { upSvg, downSvg } = createDownUpSvg(index, head);
332      head.onclick = () => {
333        if (this.isResize || this.resizeColumnIndex !== -1) {
334          return;
335        }
336        this?.shadowRoot?.querySelectorAll('.td-order svg').forEach((it: any) => {
337          it.setAttribute('fill', 'let(--dark-color1,#212121)');
338          it.sortType = 0;
339          it.style.display = 'none';
340        });
341        if (head.sortType == undefined || head.sortType == null) {
342          head.sortType = 0;
343        } else if (head.sortType === 2) {
344          head.sortType = 0;
345        } else {
346          head.sortType += 1;
347        }
348        upSvg.setAttribute('fill', 'let(--dark-color1,#212121)');
349        downSvg.setAttribute('fill', 'let(--dark-color1,#212121)');
350        upSvg.style.display = head.sortType === 1 ? 'block' : 'none';
351        downSvg.style.display = head.sortType === 2 ? 'block' : 'none';
352        switch (head.sortType) {
353          case 1:
354            this.theadElement!.setAttribute('sort', '');
355            break;
356          case 2:
357            break;
358          default:
359            this.theadElement!.removeAttribute('sort');
360            break;
361        }
362        this.dispatchEvent(
363          new CustomEvent('column-click', {
364            detail: {
365              sort: head.sortType,
366              key: key,
367            },
368            composed: true,
369          })
370        );
371      };
372    }
373  }
374
375  private isResize: boolean = false;
376  private resizeColumnIndex: number = -1;
377  private resizeDownX: number = 0;
378  private columnMinWidth: number = 50;
379  private beforeResizeWidth: number = 0;
380
381  resizeMouseMoveEventHandler(header: HTMLDivElement) {
382    header.addEventListener('mousemove', (event) => {
383      if (this.isResize) {
384        header.style.cursor = 'col-resize';
385        let width = event.clientX - this.resizeDownX;
386        let prePageWidth = Math.max(this.beforeResizeWidth + width, this.columnMinWidth);
387        for (let i = 0; i < header.childNodes.length; i++) {
388          let node = header.childNodes.item(i) as HTMLDivElement;
389          this.gridTemplateColumns[i] = `${node.clientWidth}px`;
390        }
391        this.gridTemplateColumns[this.resizeColumnIndex - 1] = `${prePageWidth}px`;
392        header.style.gridTemplateColumns = this.gridTemplateColumns.join(' ');
393        let preNode = header.childNodes.item(this.resizeColumnIndex - 1) as HTMLDivElement;
394        preNode.style.width = `${prePageWidth}px`;
395        this.shadowRoot!.querySelectorAll<HTMLDivElement>('.tr').forEach((tr) => {
396          tr.style.gridTemplateColumns = this.gridTemplateColumns.join(' ');
397        });
398        event.preventDefault();
399        event.stopPropagation();
400      } else {
401        header.style.cursor = 'pointer';
402      }
403    });
404  }
405
406  resizeEventHandler(header: HTMLDivElement, element: HTMLDivElement, index: number): void {
407    this.resizeMouseMoveEventHandler(header);
408    header.addEventListener('mouseup', (event) => {
409      this.resizeDownX = 0;
410      this.isResize = false;
411      header.style.cursor = 'pointer';
412      setTimeout(() => {
413        this.resizeColumnIndex = -1;
414      }, 100);
415      event.stopPropagation();
416      event.preventDefault();
417    });
418    header.addEventListener('mouseleave', (event) => {
419      event.stopPropagation();
420      event.preventDefault();
421      this.isResize = false;
422      this.resizeDownX = 0;
423      this.resizeColumnIndex = -1;
424      header.style.cursor = 'pointer';
425    });
426    element.addEventListener('mousedown', (event) => {
427      this.resizeColumnIndex = index;
428      this.isResize = true;
429      this.resizeDownX = event.clientX;
430      let pre = header.childNodes.item(this.resizeColumnIndex - 1) as HTMLDivElement;
431      this.beforeResizeWidth = pre.clientWidth;
432      event.stopPropagation();
433    });
434    element.addEventListener('click', (event) => {
435      event.stopPropagation();
436    });
437  }
438
439  // Is called when the custom element is removed from the document DOM.
440  disconnectedCallback(): void {}
441
442  // It is called when the custom element is moved to a new document.
443  adoptedCallback(): void {}
444
445  // It is called when a custom element adds, deletes, or modifies its own properties.
446  attributeChangedCallback(name: string, oldValue: string, newValue: string): void {}
447
448  meauseElementHeight(rowData: any): number {
449    return 27;
450  }
451
452  meauseTreeElementHeight(rowData: any, depth: number): number {
453    return 27;
454  }
455
456  meauseAllRowHeight(list: any[]): TableRowObject[] {
457    this.tbodyElement!.innerHTML = '';
458    this.meauseRowElement = undefined;
459    this.startSkip = 0;
460    let head = this.shadowRoot!.querySelector('.th');
461    this.tbodyElement && (this.tbodyElement.style.width = head?.clientWidth + 'px');
462    this.currentRecycleList = [];
463    let headHeight = 0;
464    let totalHeight = headHeight;
465    let visibleObjects: TableRowObject[] = [];
466    let itemHandler = (rowData: any, index: number) => {
467      let height = this.meauseElementHeight(rowData);
468      let tableRowObject = new TableRowObject();
469      tableRowObject.height = height;
470      tableRowObject.top = totalHeight;
471      tableRowObject.data = rowData;
472      tableRowObject.rowIndex = index;
473      if (
474        Math.max(totalHeight, this.tableElement!.scrollTop + headHeight) <=
475        Math.min(totalHeight + height, this.tableElement!.scrollTop + this.tableElement!.clientHeight + headHeight)
476      ) {
477        let newTableElement = this.createNewTableElement(tableRowObject);
478        newTableElement.style.transform = `translateY(${totalHeight}px)`;
479        this.tbodyElement?.append(newTableElement);
480        this.currentRecycleList.push(newTableElement);
481      }
482      totalHeight += height;
483      visibleObjects.push(tableRowObject);
484    };
485    let realIndex = 0;
486    list.forEach((item, index) => {
487      if (Array.isArray(item)) {
488        item.forEach((rowData, childIndex) => {
489          itemHandler(rowData, realIndex);
490          realIndex++;
491        });
492      } else {
493        itemHandler(item, index);
494      }
495    });
496    this.tbodyElement && (this.tbodyElement.style.height = totalHeight + (this.isScrollXOutSide ? 0 : 0) + 'px');
497    this.addOnScrollListener(visibleObjects);
498    return visibleObjects;
499  }
500
501  addOnScrollListener(visibleObjList: TableRowObject[]): void {
502    this.tableElement &&
503    (this.tableElement.onscroll = (event) => {
504      let tblScrollTop = this.tableElement!.scrollTop;
505      let skip = 0;
506      for (let i = 0; i < visibleObjList.length; i++) {
507        if (
508          visibleObjList[i].top <= tblScrollTop &&
509          visibleObjList[i].top + visibleObjList[i].height >= tblScrollTop
510        ) {
511          skip = i;
512          break;
513        }
514      }
515      let reduce = this.currentRecycleList.map((item) => item.clientHeight).reduce((a, b) => a + b, 0);
516      if (reduce == 0) {
517        return;
518      }
519      while (
520        reduce <= this.tableElement!.clientHeight &&
521        this.currentRecycleList.length + skip < visibleObjList.length
522        ) {
523        let newTableElement = this.createNewTableElement(visibleObjList[skip]);
524        this.tbodyElement?.append(newTableElement);
525        this.currentRecycleList.push(newTableElement);
526        reduce += newTableElement.clientHeight;
527      }
528      this.startSkip = skip;
529      for (let i = 0; i < this.currentRecycleList.length; i++) {
530        this.freshCurrentLine(this.currentRecycleList[i], visibleObjList[i + skip]);
531      }
532    });
533  }
534
535  measureReset(): void {
536    this.meauseRowElement = undefined;
537    this.tbodyElement!.innerHTML = '';
538    this.treeElement!.innerHTML = '';
539    this.currentRecycleList = [];
540    this.currentTreeDivList = [];
541  }
542
543  meauseTreeRowElement(list: any[]): TableRowObject[] {
544    this.measureReset();
545    let headHeight = this.theadElement?.clientHeight || 0;
546    let totalHeight = 0;
547    let visibleObjects: TableRowObject[] = [];
548    let resetAllHeight = (list: any[], depth: number, parentNode?: TableRowObject) => {
549      list.forEach((item) => {
550        let tableRowObject = new TableRowObject();
551        tableRowObject.depth = depth;
552        tableRowObject.data = item;
553        tableRowObject.top = totalHeight;
554        tableRowObject.height = this.meauseTreeElementHeight(tableRowObject, depth);
555        if (parentNode !== undefined) {
556          parentNode.children.push(tableRowObject);
557        }
558        let maxHeight = Math.max(totalHeight, this.tableElement!.scrollTop);
559        let minHeight = Math.min(
560          totalHeight + tableRowObject.height,
561          this.tableElement!.scrollTop + this.tableElement!.clientHeight - headHeight
562        );
563        if (maxHeight <= minHeight) {
564          let newTableElement = this.createNewTreeTableElement(tableRowObject);
565          newTableElement.style.transform = `translateY(${totalHeight}px)`;
566          this.tbodyElement?.append(newTableElement);
567          if (this.treeElement?.lastChild) {
568            (this.treeElement?.lastChild as HTMLElement).style.height = tableRowObject.height + 'px';
569          }
570          this.currentRecycleList.push(newTableElement);
571        }
572        totalHeight += tableRowObject.height;
573        visibleObjects.push(tableRowObject);
574        if (item.hasNext) {
575          if (item.parents != undefined && item.parents.length > 0 && item.status) {
576            resetAllHeight(item.parents, depth + 1, tableRowObject);
577          } else if (item.children != undefined && item.children.length > 0 && item.status) {
578            resetAllHeight(item.children, depth + 1, tableRowObject);
579          }
580        } else {
581          if (item.children !== undefined && item.children.length > 0) {
582            resetAllHeight(item.children, depth + 1, tableRowObject);
583          }
584        }
585      });
586    };
587    resetAllHeight(list, 0);
588    this.tbodyElement && (this.tbodyElement.style.height = totalHeight + 'px');
589    this.treeElement!.style.height = this.tableElement!.clientHeight - this.theadElement!.clientHeight + 'px';
590    this.addTreeRowScrollListener();
591    return visibleObjects;
592  }
593
594  addTreeRowScrollListener(): void {
595    this.tableElement &&
596    (this.tableElement.onscroll = (event) => {
597      let visibleObjs = this.recycleDs.filter((item) => {
598        return !item.rowHidden;
599      });
600      let top = this.tableElement!.scrollTop;
601      this.treeElement!.style.transform = `translateY(${top}px)`;
602      let skip = 0;
603      for (let index = 0; index < visibleObjs.length; index++) {
604        if (visibleObjs[index].top <= top && visibleObjs[index].top + visibleObjs[index].height >= top) {
605          skip = index;
606          break;
607        }
608      }
609      let reduce = this.currentRecycleList.map((item) => item.clientHeight).reduce((a, b) => a + b, 0);
610      if (reduce == 0) {
611        return;
612      }
613      while (reduce <= this.tableElement!.clientHeight) {
614        let newTableElement = this.createNewTreeTableElement(visibleObjs[skip]);
615        this.tbodyElement?.append(newTableElement);
616        if (this.treeElement?.lastChild) {
617          (this.treeElement?.lastChild as HTMLElement).style.height = visibleObjs[skip].height + 'px';
618        }
619        this.currentRecycleList.push(newTableElement);
620        reduce += newTableElement.clientHeight;
621      }
622      for (let i = 0; i < this.currentRecycleList.length; i++) {
623        this.freshCurrentLine(
624          this.currentRecycleList[i],
625          visibleObjs[i + skip],
626          this.treeElement?.children[i] as HTMLElement
627        );
628      }
629    });
630  }
631
632  createNewTreeTableElement(rowData: TableRowObject): any {
633    let newTableElement = document.createElement('div');
634    newTableElement.classList.add('tr');
635    let treeTop = 0;
636    if (this.treeElement!.children?.length > 0) {
637      let transX = Number((this.treeElement?.lastChild as HTMLElement).style.transform.replace(/[^0-9]/gi, ''));
638      treeTop += transX + rowData.height;
639    }
640    this?.columns?.forEach((column: any, index) => {
641      let dataIndex = column.getAttribute('data-index') || '1';
642      let td: any;
643      if (index === 0) {
644        td = this.firstElementTdHandler(newTableElement, dataIndex, rowData, column);
645      } else {
646        td = this.otherElementHandler(dataIndex, rowData, column);
647        newTableElement.append(td);
648      }
649    });
650    let lastChild = this.treeElement?.lastChild as HTMLElement;
651    if (lastChild) {
652      lastChild.style.transform = `translateY(${treeTop}px)`;
653    }
654    (newTableElement as any).data = rowData.data;
655    newTableElement.style.gridTemplateColumns = this.gridTemplateColumns.join(' ');
656    newTableElement.style.position = 'absolute';
657    newTableElement.style.top = '0px';
658    newTableElement.style.left = '0px';
659    newTableElement.style.cursor = 'pointer';
660    this.setHighLight(rowData.data.isSearch, newTableElement);
661    this.addRowElementEvent(newTableElement, rowData);
662    return newTableElement;
663  }
664
665  addRowElementEvent(newTableElement: HTMLDivElement, rowData: any): void {
666    newTableElement.onmouseenter = () => {
667      if ((newTableElement as any).data.isSelected) return;
668      let indexOf = this.currentRecycleList.indexOf(newTableElement);
669      this.currentTreeDivList.forEach((row) => {
670        row.classList.remove('mouse-in');
671      });
672      if (indexOf >= 0 && indexOf < this.treeElement!.children.length) {
673        this.setMouseIn(true, [this.treeElement?.children[indexOf] as HTMLElement]);
674      }
675    };
676    newTableElement.onmouseleave = () => {
677      if ((newTableElement as any).data.isSelected) return;
678      let indexOf = this.currentRecycleList.indexOf(newTableElement);
679      if (indexOf >= 0 && indexOf < this.treeElement!.children.length) {
680        this.setMouseIn(false, [this.treeElement?.children[indexOf] as HTMLElement]);
681      }
682    };
683    newTableElement.onclick = (e) => {
684      let indexOf = this.currentRecycleList.indexOf(newTableElement);
685      this.dispatchRowClickEvent(rowData, [this.treeElement?.children[indexOf] as HTMLElement, newTableElement]);
686    };
687  }
688
689  firstElementTdHandler(newTableElement: HTMLDivElement, dataIndex: string, rowData: any, column: any) {
690    let td: any;
691    let text = formatName(dataIndex, rowData.data[dataIndex], this);
692    if (column.template) {
693      td = column.template.render(rowData.data).content.cloneNode(true);
694      td.template = column.template;
695      td.title = text;
696    } else {
697      td = document.createElement('div');
698      td.innerHTML = text;
699      td.dataIndex = dataIndex;
700      td.title = text;
701    }
702    if (rowData.data.children && rowData.data.children.length > 0 && !rowData.data.hasNext) {
703      let btn = this.createExpandBtn(rowData);
704      td.insertBefore(btn, td.firstChild);
705    }
706    if (rowData.data.hasNext) {
707      td.title = rowData.data.objectName;
708      let btn = this.createBtn(rowData);
709      td.insertBefore(btn, td.firstChild);
710    }
711    td.style.paddingLeft = rowData.depth * iconWidth + 'px';
712    if (!rowData.data.children || rowData.data.children.length === 0) {
713      td.style.paddingLeft = iconWidth * rowData.depth + iconWidth + iconPadding * 2 + 'px';
714    }
715    (td as any).data = rowData.data;
716    td.classList.add('tree-first-body');
717    td.style.position = 'absolute';
718    td.style.top = '0px';
719    td.style.left = '0px';
720    this.addFirstElementEvent(td, newTableElement, rowData);
721    this.setHighLight(rowData.data.isSearch, td);
722    this.treeElement!.style.width = column.getAttribute('width');
723    this.treeElement?.append(td);
724    this.currentTreeDivList.push(td);
725    return td;
726  }
727
728  addFirstElementEvent(td: HTMLDivElement, tr: HTMLDivElement, rowData: any): void {
729    td.onmouseenter = () => {
730      let indexOf = this.currentTreeDivList.indexOf(td);
731      this.currentRecycleList.forEach((row) => {
732        row.classList.remove('mouse-in');
733      });
734      if (indexOf >= 0 && indexOf < this.currentRecycleList.length && td.innerHTML != '') {
735        this.setMouseIn(true, [td]);
736      }
737    };
738    td.onmouseleave = () => {
739      let indexOf = this.currentTreeDivList.indexOf(td);
740      if (indexOf >= 0 && indexOf < this.currentRecycleList.length) {
741        this.setMouseIn(false, [td]);
742      }
743    };
744    td.onclick = () => {
745      let indexOf = this.currentTreeDivList.indexOf(td);
746      this.dispatchRowClickEvent(rowData, [td, tr]);
747    };
748  }
749
750  otherElementHandler(dataIndex: string, rowData: any, column: any) {
751    let text = formatName(dataIndex, rowData.data[dataIndex], this);
752    let td: any = document.createElement('div');
753    td = document.createElement('div');
754    td.classList.add('td');
755    td.style.overflow = 'hidden';
756    td.style.textOverflow = 'ellipsis';
757    td.style.whiteSpace = 'nowrap';
758    td.title = text;
759    td.dataIndex = dataIndex;
760    td.style.justifyContent = column.getAttribute('align') || 'flex-start';
761    if (column.template) {
762      td.appendChild(column.template.render(rowData.data).content.cloneNode(true));
763      td.template = column.template;
764    } else {
765      td.innerHTML = text;
766    }
767    return td;
768  }
769
770  createBtn(row: any): any {
771    let btn: any = document.createElement('lit-icon');
772    btn.classList.add('tree-icon');
773    if (row.data.expanded) {
774      btn.name = 'plus-square';
775    } else {
776      btn.name = 'minus-square';
777    }
778    btn.addEventListener('click', (e: any) => {
779      row.data.status = false;
780      const resetNodeHidden = (hidden: boolean, rowData: any) => {
781        if (hidden) {
782          rowData.children.forEach((child: any) => {
783            child.rowHidden = false;
784          });
785        } else {
786          rowData.children.forEach((child: any) => {
787            child.rowHidden = true;
788            resetNodeHidden(hidden, child);
789          });
790        }
791      };
792
793      if (row.data.expanded) {
794        row.data.status = true;
795        this.dispatchRowClickEventIcon(row, [btn]);
796        row.data.expanded = false;
797        resetNodeHidden(true, row);
798      } else {
799        row.data.expanded = true;
800        row.data.status = false;
801        resetNodeHidden(false, row);
802      }
803      this.reMeauseHeight();
804      e.stopPropagation();
805    });
806    return btn;
807  }
808
809  createExpandBtn(row: any): any {
810    let btn: any = document.createElement('lit-icon');
811    btn.classList.add('tree-icon');
812    // @ts-ignore
813    if (row.expanded) {
814      btn.name = 'minus-square';
815    } else {
816      btn.name = 'plus-square';
817    }
818    btn.onclick = (e: Event) => {
819      const resetNodeHidden = (hidden: boolean, rowData: any) => {
820        if (rowData.children.length > 0) {
821          if (hidden) {
822            rowData.children.forEach((child: any) => {
823              child.rowHidden = true;
824              resetNodeHidden(hidden, child);
825            });
826          } else {
827            rowData.children.forEach((child: any) => {
828              child.rowHidden = !rowData.expanded;
829              if (rowData.expanded) {
830                resetNodeHidden(hidden, child);
831              }
832            });
833          }
834        }
835      };
836
837      if (row.expanded) {
838        row.expanded = false;
839        resetNodeHidden(true, row);
840      } else {
841        row.expanded = true;
842        resetNodeHidden(false, row);
843      }
844      this.reMeauseHeight();
845      e.stopPropagation();
846    };
847    return btn;
848  }
849
850  getVisibleObjs() {
851    let totalH = 0;
852    this.recycleDs.forEach((it) => {
853      if (!it.rowHidden) {
854        it.top = totalH;
855        totalH += it.height;
856      }
857    });
858    this.tbodyElement && (this.tbodyElement.style.height = totalH + (this.isScrollXOutSide ? 0 : 0) + 'px');
859    this.treeElement!.style.height = this.tableElement!.clientHeight - this.theadElement!.clientHeight + 'px';
860    let visibleObjs = this.recycleDs.filter((item) => {
861      return !item.rowHidden;
862    });
863    let top = this.tableElement!.scrollTop;
864    let skip = 0;
865    for (let i = 0; i < visibleObjs.length; i++) {
866      if (visibleObjs[i].top <= top && visibleObjs[i].top + visibleObjs[i].height >= top) {
867        skip = i;
868        break;
869      }
870    }
871    let reduce = this.currentRecycleList.map((item) => item.clientHeight).reduce((a, b) => a + b, 0);
872    return { visibleObjs, skip, reduce };
873  }
874
875  reMeauseHeight(): void {
876    if (this.currentRecycleList.length === 0) {
877      return;
878    }
879    let { visibleObjs, skip, reduce } = this.getVisibleObjs();
880    if (reduce === 0) {
881      return;
882    }
883    while (reduce <= this.tableElement!.clientHeight) {
884      let rowElement;
885      if (this.hasAttribute('tree')) {
886        rowElement = this.createNewTreeTableElement(visibleObjs[skip]);
887      } else {
888        rowElement = this.createNewTableElement(visibleObjs[skip]);
889      }
890      this.tbodyElement?.append(rowElement);
891      if (this.hasAttribute('tree')) {
892        if (this.treeElement?.lastChild) {
893          (this.treeElement?.lastChild as HTMLElement).style.height = visibleObjs[skip].height + 'px';
894        }
895      }
896      this.currentRecycleList.push(rowElement);
897      reduce += rowElement.clientHeight;
898    }
899    for (let i = 0; i < this.currentRecycleList.length; i++) {
900      if (this.hasAttribute('tree')) {
901        this.freshCurrentLine(
902          this.currentRecycleList[i],
903          visibleObjs[i + skip],
904          this.treeElement?.children[i] as HTMLElement
905        );
906      } else {
907        this.freshCurrentLine(this.currentRecycleList[i], visibleObjs[i + skip]);
908      }
909    }
910  }
911
912  createNewTableElement(rowData: any): any {
913    let rowElement = document.createElement('div');
914    rowElement.classList.add('tr');
915    this?.columns?.forEach((column: any) => {
916      let dataIndex = column.getAttribute('data-index') || '1';
917      let td: any;
918      td = document.createElement('div');
919      td.classList.add('td');
920      td.style.overflow = 'scroll hidden';
921      td.style.textOverflow = 'ellipsis';
922      td.style.whiteSpace = 'nowrap';
923      td.dataIndex = dataIndex;
924      td.style.justifyContent = column.getAttribute('align') || 'flex-start';
925      let text = formatName(dataIndex, rowData.data[dataIndex], this);
926      td.title = text;
927      if (column.template) {
928        td.appendChild(column.template.render(rowData.data).content.cloneNode(true));
929        td.template = column.template;
930      } else {
931        td.innerHTML = text;
932      }
933      rowElement.append(td);
934    });
935    rowElement.onclick = () => {
936      this.dispatchRowClickEvent(rowData, [rowElement]);
937    };
938    rowElement.onmouseover = () => {
939      this.dispatchRowHoverEvent(rowData, [rowElement]);
940    };
941    if (rowData.data.isSelected != undefined) {
942      this.setSelectedRow(rowData.data.isSelected, [rowElement]);
943    }
944    (rowElement as any).data = rowData.data;
945    rowElement.style.cursor = 'pointer';
946    rowElement.style.gridTemplateColumns = this.gridTemplateColumns.join(' ');
947    rowElement.style.position = 'absolute';
948    rowElement.style.top = '0px';
949    rowElement.style.left = '0px';
950    if (this.getItemTextColor) {
951      rowElement.style.color = this.getItemTextColor(rowData.data);
952    }
953    return rowElement;
954  }
955
956  freshCurrentLine(element: HTMLElement, rowObject: TableRowObject, firstElement?: HTMLElement) {
957    if (!rowObject) {
958      if (firstElement) {
959        firstElement.style.display = 'none';
960      }
961      element.style.display = 'none';
962      return;
963    }
964    let childIndex = -1;
965    this.setHighLight(rowObject.data.isSearch, element);
966    element.childNodes.forEach((child) => {
967      if (child.nodeType != 1) return;
968      childIndex++;
969      let idx = firstElement !== undefined ? childIndex + 1 : childIndex;
970      this.freshLineFirstElementHandler(firstElement, rowObject, childIndex);
971      let dataIndex = this.columns![idx].getAttribute('data-index') || '1';
972      let text = formatName(dataIndex, rowObject.data[dataIndex], this);
973      if ((this.columns![idx] as any).template) {
974        (child as HTMLElement).innerHTML = '';
975        (child as HTMLElement).appendChild(
976          (this.columns![idx] as any).template.render(rowObject.data).content.cloneNode(true)
977        );
978        (child as HTMLElement).title = text;
979      } else {
980        (child as HTMLElement).innerHTML = text;
981        (child as HTMLElement).title = text;
982      }
983    });
984    this.freshLineStyleAndEvents(element, rowObject, firstElement);
985  }
986
987  freshLineFirstElementHandler(rowFirstElement: any, rowObject: TableRowObject, childIndex: number): void {
988    if (rowFirstElement !== undefined && childIndex === 0) {
989      this.setHighLight(rowObject.data.isSearch, rowFirstElement);
990      (rowFirstElement as any).data = rowObject.data;
991      if ((this.columns![0] as any).template) {
992        rowFirstElement.innerHTML = (this.columns![0] as any).template
993          .render(rowObject.data)
994          .content.cloneNode(true).innerHTML;
995      } else {
996        let dataIndex = this.columns![0].getAttribute('data-index') || '1';
997        let text = formatName(dataIndex, rowObject.data[dataIndex], this);
998        rowFirstElement.innerHTML = text;
999        rowFirstElement.title = text;
1000      }
1001      if (rowObject.children && rowObject.children.length > 0 && !rowObject.data.hasNext) {
1002        let btn = this.createExpandBtn(rowObject);
1003        rowFirstElement.insertBefore(btn, rowFirstElement.firstChild);
1004      }
1005      rowFirstElement.style.paddingLeft = iconWidth * rowObject.depth + 'px';
1006      if (!rowObject.children || rowObject.children.length === 0) {
1007        rowFirstElement.style.paddingLeft = iconWidth * rowObject.depth + iconWidth + iconPadding * 2 + 'px';
1008      }
1009      if (rowObject.data.hasNext) {
1010        let btn = this.createBtn(rowObject);
1011        rowFirstElement.title = rowObject.data.objectName;
1012        rowFirstElement.insertBefore(btn, rowFirstElement.firstChild);
1013        rowFirstElement.style.paddingLeft = iconWidth * rowObject.depth + 'px';
1014      }
1015      rowFirstElement.onclick = () => {
1016        this.dispatchRowClickEvent(rowObject, [rowFirstElement, element]);
1017      };
1018      rowFirstElement.style.transform = `translateY(${rowObject.top - this.tableElement!.scrollTop}px)`;
1019      if (rowObject.data.isSelected !== undefined) {
1020        this.setSelectedRow(rowObject.data.isSelected, [rowFirstElement]);
1021      } else {
1022        this.setSelectedRow(false, [rowFirstElement]);
1023      }
1024    }
1025  }
1026
1027  freshLineStyleAndEvents(element: HTMLElement, rowData: TableRowObject, firstElement?: HTMLElement): void {
1028    if (element.style.display === 'none') {
1029      element.style.display = 'grid';
1030    }
1031    element.style.transform = `translateY(${rowData.top}px)`;
1032    if (firstElement && firstElement.style.display === 'none') {
1033      firstElement.style.display = 'flex';
1034    }
1035    element.onclick = (e) => {
1036      if (firstElement !== undefined) {
1037        this.dispatchRowClickEvent(rowData, [firstElement, element]);
1038      } else {
1039        this.dispatchRowClickEvent(rowData, [element]);
1040      }
1041    };
1042    element.onmouseenter = () => {
1043      this.dispatchRowHoverEvent(rowData, [element]);
1044    };
1045    (element as any).data = rowData.data;
1046    if (rowData.data.isSelected !== undefined) {
1047      this.setSelectedRow(rowData.data.isSelected, [element]);
1048    } else {
1049      this.setSelectedRow(false, [element]);
1050    }
1051    if (rowData.data.isHover !== undefined) {
1052      this.setMouseIn(rowData.data.isHover, [element]);
1053    } else {
1054      this.setMouseIn(false, [element]);
1055    }
1056    if (this.getItemTextColor) {
1057      element.style.color = this.getItemTextColor((element as any).data);
1058    }
1059  }
1060
1061  setSelectedRow(isSelected: boolean, rows: any[]): void {
1062    if (isSelected) {
1063      rows.forEach((row) => {
1064        if (row.classList.contains('mouse-in')) {
1065          row.classList.remove('mouse-in');
1066        }
1067        row.classList.add('mouse-select');
1068      });
1069    } else {
1070      rows.forEach((row) => {
1071        row.classList.remove('mouse-select');
1072      });
1073    }
1074  }
1075
1076  setMouseIn(isMouseIn: boolean, rows: any[]): void {
1077    if (isMouseIn) {
1078      rows.forEach((row) => {
1079        row.classList.add('mouse-in');
1080      });
1081    } else {
1082      rows.forEach((row) => {
1083        row.classList.remove('mouse-in');
1084      });
1085    }
1086  }
1087
1088  scrollToData(data: any): void {
1089    if (this.recycleDs.length > 0) {
1090      let filter = this.recycleDs.filter((item) => {
1091        return item.data == data;
1092      });
1093      if (filter.length > 0) {
1094        this.tableElement!.scrollTop = filter[0].top;
1095      }
1096      this.setCurrentSelection(data);
1097    }
1098  }
1099
1100  expandList(datasource: any[]): void {
1101    let source = this.recycleDs.filter((item) => {
1102      return datasource.indexOf(item.data) != -1;
1103    });
1104    if (source.length > 0) {
1105      source.forEach((item) => {
1106        item.expanded = true;
1107        item.rowHidden = false;
1108      });
1109    }
1110    this.reMeauseHeight();
1111  }
1112
1113  clearAllSelection(rowObjectData: any = undefined): void {
1114    this.recycleDs.forEach((item) => {
1115      if (rowObjectData || (item.data != rowObjectData && item.data.isSelected)) {
1116        item.data.isSelected = false;
1117      }
1118    });
1119    this.setSelectedRow(false, this.currentTreeDivList);
1120    this.setSelectedRow(false, this.currentRecycleList);
1121  }
1122
1123  clearAllHover(rowObjectData: any): void {
1124    this.recycleDs.forEach((item) => {
1125      if (item.data != rowObjectData && item.data.isHover) {
1126        item.data.isHover = false;
1127      }
1128    });
1129    this.setMouseIn(false, this.currentTreeDivList);
1130    this.setMouseIn(false, this.currentRecycleList);
1131  }
1132
1133  mouseOut(): void {
1134    this.recycleDs.forEach((item) => (item.data.isHover = false));
1135    this.setMouseIn(false, this.currentTreeDivList);
1136    this.setMouseIn(false, this.currentRecycleList);
1137    this.dispatchEvent(
1138      new CustomEvent('row-hover', {
1139        detail: {
1140          data: undefined,
1141        },
1142        composed: true,
1143      })
1144    );
1145  }
1146
1147  setCurrentSelection(data: any): void {
1148    if (data.isSelected != undefined) {
1149      this.currentTreeDivList.forEach((item) => {
1150        if ((item as any).data == data) {
1151          this.setSelectedRow(data.isSelected, [item]);
1152        }
1153      });
1154      this.currentRecycleList.forEach((item) => {
1155        if ((item as any).data == data) {
1156          this.setSelectedRow(data.isSelected, [item]);
1157        }
1158      });
1159    }
1160  }
1161
1162  setCurrentHover(data: any): void {
1163    this.setMouseIn(false, this.currentTreeDivList);
1164    this.setMouseIn(false, this.currentRecycleList);
1165    if (data.isHover != undefined) {
1166      this.currentTreeDivList.forEach((item) => {
1167        if ((item as any).data == data) {
1168          this.setMouseIn(data.isHover, [item]);
1169        }
1170      });
1171      this.currentRecycleList.forEach((item) => {
1172        if ((item as any).data == data) {
1173          this.setMouseIn(data.isHover, [item]);
1174        }
1175      });
1176    }
1177  }
1178
1179  dispatchRowClickEventIcon(rowObject: any, elements: any[]) {
1180    this.dispatchEvent(
1181      new CustomEvent('icon-click', {
1182        detail: {
1183          ...rowObject.data,
1184          data: rowObject.data,
1185          callBack: (isSelected: boolean) => {
1186            //是否爲单选
1187            if (isSelected) {
1188              this.clearAllSelection(rowObject.data);
1189            }
1190            this.setSelectedRow(rowObject.data.isSelected, elements);
1191          },
1192        },
1193        composed: true,
1194      })
1195    );
1196  }
1197
1198  dispatchRowClickEvent(rowObject: any, elements: any[]): void {
1199    this.dispatchEvent(
1200      new CustomEvent('row-click', {
1201        detail: {
1202          ...rowObject.data,
1203          data: rowObject.data,
1204          callBack: (isSelected: boolean): void => {
1205            //是否爲单选
1206            if (isSelected) {
1207              this.clearAllSelection(rowObject.data);
1208            }
1209            this.setSelectedRow(rowObject.data.isSelected, elements);
1210          },
1211        },
1212        composed: true,
1213      })
1214    );
1215  }
1216
1217  dispatchRowHoverEvent(rowObject: any, elements: any[]): void {
1218    this.dispatchEvent(
1219      new CustomEvent('row-hover', {
1220        detail: {
1221          data: rowObject.data,
1222          callBack: (): void => {
1223            this.clearAllHover(rowObject.data);
1224            this.setMouseIn(rowObject.data.isHover, elements);
1225          },
1226        },
1227        composed: true,
1228      })
1229    );
1230  }
1231  setHighLight(isSearch: boolean, element: any): void {
1232    if (isSearch) {
1233      element.setAttribute('high-light', '');
1234    } else {
1235      element.removeAttribute('high-light');
1236    }
1237  }
1238}
1239
1240if (!customElements.get('lit-page-table')) {
1241  customElements.define('lit-page-table', LitPageTable);
1242}
1243