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