1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19/* 20 * 2021.01.08 - Move element's method from operation.js to Element class. 21 * Copyright (c) 2021 Huawei Device Co., Ltd. 22 */ 23 24import { Log } from '../utils/index'; 25import Node from './Node'; 26import NativeElementClassFactory from './NativeElementClassFactory'; 27import Document from './Document'; 28import { TaskCenter } from '../main/manage/event/TaskCenter'; 29import { FragBlockInterface } from '../main/model/compiler'; 30import Vm from '../main/model'; 31import { CSS_INHERITANCE } from '../main/app/bundle'; 32 33/** 34 * Element is a basic class to describe a tree node in vdom. 35 * @extends Node 36 */ 37class Element extends Node { 38 private _style: any; 39 private _classStyle: any; 40 private _event: any; 41 private _idStyle: any; 42 private _tagStyle: any; 43 private _id: string | null; 44 private _classList: any[]; 45 private _block: FragBlockInterface; 46 private _vm: Vm; 47 private _isCustomComponent: boolean; 48 private _inheritedStyle: object; 49 50 protected _children: Node[]; 51 protected _pureChildren: Element[]; 52 protected _role: string; 53 protected _attr: any; 54 protected _dataSet: any; 55 56 constructor(type = 'div', props:any = {}, isExtended: boolean = false) { 57 super(); 58 const NativeElementClass = NativeElementClassFactory.nativeElementClassMap.get(type); 59 if (NativeElementClass && !isExtended) { 60 return new NativeElementClass(props); 61 } 62 63 this._nodeType = Node.NodeType.Element; 64 this._type = type; 65 this._attr = props.attr || {}; 66 this._style = props.style || {}; 67 this._classStyle = props.classStyle || {}; 68 this._event = {}; 69 this._idStyle = {}; 70 this._tagStyle = {}; 71 this._id = null; 72 this._classList = []; 73 this._children = []; 74 this._pureChildren = []; 75 this._isCustomComponent = false; 76 this._inheritedStyle = {}; 77 this._dataSet = {}; 78 } 79 80 /** 81 * inherit sytle from parent 82 * @type {Object} 83 */ 84 public set inheritedStyle(inheritedStyle: object) { 85 this._inheritedStyle = inheritedStyle; 86 } 87 88 public get inheritedStyle() { 89 return this._inheritedStyle; 90 } 91 92 /** 93 * Children array except comment node. 94 * @type {Node[]} 95 */ 96 public set pureChildren(newPureChildren: Element[]) { 97 this._pureChildren = newPureChildren; 98 } 99 100 public get pureChildren() { 101 return this._pureChildren; 102 } 103 104 /** 105 * event of element 106 * @type {Node[]} 107 */ 108 public get event() { 109 return this._event; 110 } 111 112 /** 113 * Children array. 114 * @type {Node[]} 115 */ 116 public set children(newChildren: Node[]) { 117 this._children = newChildren; 118 } 119 120 public get children() { 121 return this._children; 122 } 123 124 /** 125 * View model of this Element. 126 * @type {Vm} 127 */ 128 public get vm() { 129 return this._vm; 130 } 131 132 public set vm(newVm: Vm) { 133 this._vm = newVm; 134 } 135 136 /** 137 * Style object of this Element, which keys is style name, and values is style values. 138 * @type {JSON} 139 */ 140 public get classStyle() { 141 return this._classStyle; 142 } 143 144 /** 145 * Block in this Element. 146 * @type {FragBlock} 147 */ 148 public get block() { 149 return this._block; 150 } 151 152 public set block(newBlock: FragBlockInterface) { 153 this._block = newBlock; 154 } 155 156 /** 157 * Role of this element. 158 * @type {string} 159 */ 160 public set role(role: string) { 161 this._role = role; 162 } 163 164 public get role() { 165 return this._role; 166 } 167 168 /** 169 * ID of this element. 170 * @type {string} 171 */ 172 public get id() { 173 return this._id; 174 } 175 176 public set id(value) { 177 this._id = value; 178 } 179 180 /** 181 * Class list of this element. 182 * @type {string[]} 183 */ 184 public get classList() { 185 return this._classList; 186 } 187 188 public set classList(value: string[]) { 189 this._classList = value.slice(0); 190 } 191 192 /** 193 * Attributes object of this Element. 194 * @type {Object} 195 */ 196 public set attr(attr: any) { 197 this._attr = attr; 198 } 199 200 public get attr() { 201 return this._attr; 202 } 203 204 /** 205 * DataSet object of this Element. 206 * @type {Object} 207 */ 208 public set dataSet(dataSet: any) { 209 this._dataSet = dataSet; 210 } 211 212 public get dataSet() { 213 return this._dataSet; 214 } 215 216 /** 217 * Flag of whether the element is the root of customeComponent. 218 * @param {bollean} 219 */ 220 public set isCustomComponent(isCustomComponent: boolean) { 221 this._isCustomComponent = isCustomComponent; 222 } 223 224 public get isCustomComponent() { 225 return this._isCustomComponent; 226 } 227 228 /** 229 * Style object of this Element. 230 * @type {Object} 231 */ 232 public set style(style: any) { 233 this._style = style; 234 } 235 236 public get style() { 237 return this._style; 238 } 239 240 /** 241 * Get TaskCenter instance by id. 242 * @param {string} id 243 * @return {TaskCenter} TaskCenter 244 */ 245 public getTaskCenter(id: string): TaskCenter | null { 246 const doc: Document = this._ownerDocument; 247 if (doc && doc.taskCenter) { 248 return doc.taskCenter; 249 } 250 return null; 251 } 252 253 /** 254 * Establish the connection between parent and child node. 255 * @param {Node} child - Target node. 256 */ 257 public linkChild(child: Node): void { 258 child.parentNode = this; 259 if (this._docId) { 260 child.docId = this._docId; 261 child.ownerDocument = this._ownerDocument; 262 child.ownerDocument.nodeMap[child.nodeId] = child; 263 child.depth = this._depth + 1; 264 } 265 if (child.nodeType === Node.NodeType.Element) { 266 const element = child as Element; 267 element.children.forEach((grandChild: Node) => { 268 element.linkChild(grandChild); 269 }); 270 } 271 } 272 273 /** 274 * Insert a node into list at the specified index. 275 * @param {Node} target - Target node. 276 * @param {number} newIndex - Target index. 277 * @param {Object} [options] - options of insert method. 278 * @param {boolean} [options.changeSibling=false] - If need to change sibling's index. 279 * @param {boolean} [options.isInPureChildren=false] - If in pure children array or children array. 280 * @return {number} New index of node. 281 */ 282 public insertIndex(target: Node, newIndex: number, { changeSibling = false, isInPureChildren = false }): number { 283 const list: Node[] = isInPureChildren ? this._pureChildren : this._children; 284 if (newIndex < 0) { 285 newIndex = 0; 286 } 287 const before: Node = list[newIndex - 1]; 288 const after: Node = list[newIndex]; 289 list.splice(newIndex, 0, target); 290 if (changeSibling) { 291 before && (before.nextSibling = target); 292 target.previousSibling = before; 293 target.nextSibling = after; 294 after && (after.previousSibling = target); 295 } 296 return newIndex; 297 } 298 299 /** 300 * Move the node to a new index in list. 301 * @param {Node} target - Target node. 302 * @param {number} newIndex - Target index. 303 * @param {Object} [options] - options of insert method. 304 * @param {boolean} [options.changeSibling=false] - If need to change sibling's index. 305 * @param {boolean} [options.isInPureChildren=false] - If in pure children array or children array. 306 * @return {number} New index of node. 307 */ 308 public moveIndex(target: Node, newIndex: number, { changeSibling = false, isInPureChildren = false }): number { 309 const list: Node[] = isInPureChildren ? this._pureChildren : this._children; 310 const index: number = list.indexOf(target); 311 if (index < 0) { 312 return -1; 313 } 314 if (changeSibling) { 315 const before: Node = list[index - 1]; 316 const after: Node = list[index + 1]; 317 before && (before.nextSibling = after); 318 after && (after.previousSibling = before); 319 } 320 list.splice(index, 1); 321 let newIndexAfter = newIndex; 322 if (index <= newIndex) { 323 newIndexAfter = newIndex - 1; 324 } 325 const beforeNew: Node = list[newIndexAfter - 1]; 326 const afterNew: Node = list[newIndexAfter]; 327 list.splice(newIndexAfter, 0, target); 328 if (changeSibling) { 329 if (beforeNew) { 330 beforeNew.nextSibling = target; 331 } 332 target.previousSibling = beforeNew; 333 target.nextSibling = afterNew; 334 if (afterNew) { 335 afterNew.previousSibling = target; 336 } 337 } 338 if (index === newIndexAfter) { 339 return -1; 340 } 341 return newIndex; 342 } 343 344 /** 345 * Remove the node from list. 346 * @param {Node} target - Target node. 347 * @param {Object} [options] - options of insert method. 348 * @param {boolean} [options.changeSibling=false] - If need to change sibling's index. 349 * @param {boolean} [options.isInPureChildren=false] - If in pure children array or children array. 350 */ 351 public removeIndex(target, { changeSibling = false, isInPureChildren = false}): void { 352 const list: Node[] = isInPureChildren ? this._pureChildren : this._children; 353 const index: number = list.indexOf(target); 354 if (index < 0) { 355 return; 356 } 357 if (changeSibling) { 358 const before: Node = list[index - 1]; 359 const after: Node = list[index + 1]; 360 before && (before.nextSibling = after); 361 after && (after.previousSibling = before); 362 } 363 list.splice(index, 1); 364 } 365 366 /** 367 * Get the next sibling element. 368 * @param {Node} node - Target node. 369 * @return {Node} Next node of target node. 370 */ 371 public nextElement(node: Node): Element { 372 while (node) { 373 if (node.nodeType === Node.NodeType.Element) { 374 return node as Element; 375 } 376 node = node.nextSibling; 377 } 378 } 379 380 /** 381 * Get the previous sibling element. 382 * @param {Node} node - Target node. 383 * @return {Node} Previous node of target node. 384 */ 385 public previousElement(node: Node): Element { 386 while (node) { 387 if (node.nodeType === Node.NodeType.Element) { 388 return node as Element; 389 } 390 node = node.previousSibling; 391 } 392 } 393 394 /** 395 * Append a child node. 396 * @param {Node} node - Target node. 397 * @return {number} the signal sent by native 398 */ 399 public appendChild(node: Node): void { 400 if (node.parentNode && node.parentNode !== this) { 401 return; 402 } 403 404 if (!node.parentNode) { 405 this.linkChild(node as Element); 406 this.insertIndex(node, this.children.length, { changeSibling: true }); 407 if (this.docId) { 408 this.registerNode(node); 409 } 410 if (node.nodeType === Node.NodeType.Element) { 411 const element = node as Element; 412 this.insertIndex(element, this.pureChildren.length, { isInPureChildren: true }); 413 this.inheritStyle(node, true); 414 const taskCenter = this.getTaskCenter(this.docId); 415 if (taskCenter) { 416 return taskCenter.send( 417 'dom', 418 { action: 'addElement' }, 419 [this.ref, element.toJSON(), -1] 420 ); 421 } 422 } 423 } else { 424 this.moveIndex(node, this.children.length, { changeSibling: true }); 425 if (node.nodeType === Node.NodeType.Element) { 426 const index = this.moveIndex(node, this.pureChildren.length, { isInPureChildren: true }); 427 const taskCenter = this.getTaskCenter(this.docId); 428 if (taskCenter && index >= 0) { 429 return taskCenter.send( 430 'dom', 431 { action: 'moveElement' }, 432 [node.ref, this.ref, index] 433 ); 434 } 435 } 436 } 437 } 438 439 /** 440 * Insert a node before specified node. 441 * @param {Node} node - Target node. 442 * @param {Node} before - The node next to the target position. 443 * @return {number} the signal sent by native 444 */ 445 public insertBefore(node: Node, before: Node): void { 446 if (node.parentNode && node.parentNode !== this) { 447 return; 448 } 449 if (node === before || node.nextSibling && node.nextSibling === before) { 450 return; 451 } 452 // If before is not exist, return. 453 if (this.children.indexOf(before) < 0) { 454 return; 455 } 456 if (!node.parentNode) { 457 this.linkChild(node as Element); 458 this.insertIndex(node, this.children.indexOf(before), { changeSibling: true }); 459 if (this.docId) { 460 this.registerNode(node); 461 } 462 if (node.nodeType === Node.NodeType.Element) { 463 const element = node as Element; 464 const pureBefore = this.nextElement(before); 465 const index = this.insertIndex( 466 element, 467 pureBefore 468 ? this.pureChildren.indexOf(pureBefore) 469 : this.pureChildren.length, 470 { isInPureChildren: true } 471 ); 472 this.inheritStyle(node); 473 const taskCenter = this.getTaskCenter(this.docId); 474 if (taskCenter) { 475 return taskCenter.send( 476 'dom', 477 { action: 'addElement' }, 478 [this.ref, element.toJSON(), index] 479 ); 480 } 481 } 482 } else { 483 this.moveIndex(node, this.children.indexOf(before), { changeSibling: true }); 484 if (node.nodeType === Node.NodeType.Element) { 485 const pureBefore = this.nextElement(before); 486 const index = this.moveIndex( 487 node, 488 pureBefore 489 ? this.pureChildren.indexOf(pureBefore) 490 : this.pureChildren.length, 491 { isInPureChildren: true} 492 ); 493 const taskCenter = this.getTaskCenter(this.docId); 494 if (taskCenter && index >= 0) { 495 return taskCenter.send( 496 'dom', 497 { action: 'moveElement' }, 498 [node.ref, this.ref, index] 499 ); 500 } 501 } 502 } 503 } 504 505 /** 506 * Insert a node after specified node. 507 * @param {Node} node - Target node. 508 * @param {Node} after - The node in front of the target position. 509 * @return {number} the signal sent by native 510 */ 511 public insertAfter(node: Node, after: Node) { 512 if (node.parentNode && node.parentNode !== this) { 513 return; 514 } 515 if (node === after || node.previousSibling && node.previousSibling === after) { 516 return; 517 } 518 if (!node.parentNode) { 519 this.linkChild(node as Element); 520 this.insertIndex(node, this.children.indexOf(after) + 1, { changeSibling: true }); 521 522 if (this.docId) { 523 this.registerNode(node); 524 } 525 if (node.nodeType === Node.NodeType.Element) { 526 const element = node as Element; 527 const index = this.insertIndex( 528 element, 529 this.pureChildren.indexOf(this.previousElement(after)) + 1, 530 { isInPureChildren: true } 531 ); 532 this.inheritStyle(node); 533 const taskCenter = this.getTaskCenter(this.docId); 534 if (taskCenter) { 535 return taskCenter.send( 536 'dom', 537 { action: 'addElement' }, 538 [this.ref, element.toJSON(), index] 539 ); 540 } 541 } 542 } else { 543 this.moveIndex(node, this.children.indexOf(after) + 1, { changeSibling: true}); 544 if (node.nodeType === Node.NodeType.Element) { 545 const index = this.moveIndex( 546 node, 547 this.pureChildren.indexOf(this.previousElement(after)) + 1, 548 { isInPureChildren: true } 549 ); 550 const taskCenter = this.getTaskCenter(this.docId); 551 if (taskCenter && index >= 0) { 552 return taskCenter.send( 553 'dom', 554 { action: 'moveElement' }, 555 [node.ref, this.ref, index] 556 ); 557 } 558 } 559 } 560 } 561 562 /** 563 * Remove a child node, and decide whether it should be destroyed. 564 * @param {Node} node - Target node. 565 * @param {boolean} [preserved=false] - If need to keep the target node. 566 */ 567 public removeChild(node: Node, preserved: boolean = false): void { 568 if (node.parentNode) { 569 this.removeIndex(node, { changeSibling: true }); 570 if (node.nodeType === Node.NodeType.Element) { 571 this.removeIndex(node, { isInPureChildren: true}); 572 const taskCenter = this.getTaskCenter(this.docId); 573 if (taskCenter) { 574 taskCenter.send( 575 'dom', 576 { action: 'removeElement' }, 577 [node.ref] 578 ); 579 } 580 } 581 } 582 if (!preserved) { 583 node.destroy(); 584 } 585 } 586 587 /** 588 * Clear all child nodes. 589 */ 590 public clear(): void { 591 const taskCenter: TaskCenter = this.getTaskCenter(this._docId); 592 if (taskCenter) { 593 this._pureChildren.forEach(child => { 594 taskCenter.send('dom', { action: 'removeElement' }, [child.ref]); 595 }); 596 } 597 this._children.forEach(node => { 598 node.destroy(); 599 }); 600 this._children.length = 0; 601 this._pureChildren.length = 0; 602 } 603 604 /** 605 * Set dataSet for an element. 606 * @param {string} key - dataSet name. 607 * @param {string} value - dataSet value. 608 */ 609 public setData(key: string, value: string): void { 610 this.dataSet[key] = value; 611 } 612 613 /** 614 * Set an attribute, and decide whether the task should be send to native. 615 * @param {string} key - Arribute name. 616 * @param {string | number} value - Arribute value. 617 * @param {boolean} [silent=false] - If use silent mode. 618 */ 619 public setAttr(key: string, value: string | number, silent: boolean = false): void { 620 if (this.attr[key] === value && silent !== false) { 621 return; 622 } 623 this.attr[key] = value; 624 const taskCenter = this.getTaskCenter(this.docId); 625 if (!silent && taskCenter) { 626 const result = {}; 627 result[key] = value; 628 taskCenter.send('dom', { action: 'updateAttrs' }, [this.ref, result]); 629 } 630 } 631 632 /** 633 * Set a style property, and decide whether the task should be send to native. 634 * @param {string} key - Style name. 635 * @param {string | number} value - Style value. 636 * @param {boolean} [silent=false] - If use silent mode. 637 */ 638 public setStyle(key: string, value: string | number, silent: boolean = false): void { 639 if (this.style[key] === value && silent !== false) { 640 return; 641 } 642 this.style[key] = value; 643 const taskCenter = this.getTaskCenter(this.docId); 644 if (!silent && taskCenter) { 645 const result = {}; 646 result[key] = value; 647 taskCenter.send('dom', { action: 'updateStyle' }, [this.ref, this.toStyle()]); 648 if (CSS_INHERITANCE.includes(key)) { 649 this.broadcastStyle(); 650 } 651 } 652 } 653 654 /** 655 * Set style properties from class. 656 * @param {object} classStyle - Style properties. 657 */ 658 public setClassStyle(classStyle: any): void { 659 let canUpdate: boolean = false; 660 const taskCenter = this.getTaskCenter(this.docId); 661 Object.keys(classStyle).forEach(key => { 662 if (CSS_INHERITANCE.includes(key) && taskCenter) { 663 if (!this.isSameStyle(this.classStyle[key], classStyle[key], key)) { 664 canUpdate = true; 665 } 666 } 667 }); 668 for (const key in this._classStyle) { 669 this._classStyle[key] = ''; 670 } 671 672 Object.assign(this._classStyle, classStyle); 673 if (taskCenter) { 674 taskCenter.send('dom', { action: 'updateStyle' }, [this.ref, this.toStyle()]); 675 if (canUpdate) { 676 this.broadcastStyle(); 677 } 678 } 679 } 680 681 /** 682 * Set style properties from class. 683 * @param {object} classStyle - Style properties. 684 */ 685 public setCustomFlag(): void { 686 this._isCustomComponent = true; 687 } 688 689 /** 690 * Set IdStyle properties from class. 691 * @param {string} key - Style name. 692 * @param {string|number} value - Style value. 693 * @param {boolean} [silent=false] - If use silent mode. 694 */ 695 public setIdStyle(key: string, value: string | number, silent: boolean = false): void { 696 if (this._idStyle[key] === value && silent !== false) { 697 return; 698 } 699 // if inline style has define return 700 if (this.style[key]) { 701 return; 702 } 703 this._idStyle[key] = value; 704 const taskCenter = this.getTaskCenter(this.docId); 705 if (!silent && taskCenter) { 706 const result = {}; 707 result[key] = value; 708 taskCenter.send('dom', { action: 'updateStyle' }, [this.ref, result]); 709 if (CSS_INHERITANCE.includes(key)) { 710 this.broadcastStyle(); 711 } 712 } 713 } 714 715 /** 716 * Set TagStyle properties from class. 717 * @param {string} key - Style name. 718 * @param {string|number} value - Style value. 719 * @param {boolean} [silent=false] - If use silent mode. 720 */ 721 public setTagStyle(key: string, value: string | number, silent: boolean = false): void { 722 if (this._tagStyle[key] === value && silent !== false) { 723 return; 724 } 725 // If inline id class style has define return. 726 if (this.style[key] || this._idStyle[key] || this._classStyle[key]) { 727 return; 728 } 729 this._tagStyle[key] = value; 730 const taskCenter = this.getTaskCenter(this.docId); 731 if (!silent && taskCenter) { 732 const result = {}; 733 result[key] = value; 734 taskCenter.send('dom', { action: 'updateStyle' }, [this.ref, result]); 735 } 736 } 737 738 /** 739 * Add an event handler. 740 * @param {string} type - Event name. 741 * @param {Function} handler - Event handler. 742 * @param {Object} [params] - Event parameters. 743 */ 744 public addEvent(type: string, handler?: Function, params?: any): void { 745 if (!this._event) { 746 this._event = {}; 747 } 748 if (!this._event[type]) { 749 this._event[type] = { handler, params }; 750 const taskCenter = this.getTaskCenter(this.docId); 751 if (taskCenter) { 752 taskCenter.send( 753 'dom', 754 { action: 'addEvent' }, 755 [this.ref, type] 756 ); 757 } 758 } 759 } 760 761 /** 762 * Remove an event handler. 763 * @param {string} type - Event name 764 */ 765 public removeEvent(type: string): void { 766 if (this._event && this._event[type]) { 767 delete this._event[type]; 768 const taskCenter = this.getTaskCenter(this.docId); 769 if (taskCenter) { 770 taskCenter.send( 771 'dom', 772 { action: 'removeEvent' }, 773 [this.ref, type] 774 ); 775 } 776 } 777 } 778 779 /** 780 * Fire an event manually. 781 * @param {string} type - Event name. 782 * @param {function} event - Event handler. 783 * @param {boolean} isBubble - Whether or not event bubble 784 * @param {boolean} [options] - Event options 785 * @return {*} anything returned by handler function. 786 */ 787 public fireEvent(type: string, event: any, isBubble?: boolean, options?: any) { 788 Log.debug(`Element#fireEvent, type = ${type}, event = ${event}, isBubble = ${isBubble}, options = ${options}.`); 789 const BUBBLE_EVENTS = [ 790 'mouse', 'click', 'longpress', 'touchstart', 791 'touchmove', 'touchend', 'panstart', 'panmove', 792 'panend', 'horizontalpan', 'verticalpan', 'swipe' 793 ]; 794 let result = null; 795 let isStopPropagation = false; 796 const eventDesc = this._event[type]; 797 if (eventDesc && event) { 798 const handler = eventDesc.handler; 799 event.stopPropagation = () => { 800 isStopPropagation = true; 801 }; 802 if (options && options.params) { 803 result = handler.call(this, event, ...options.params); 804 } else { 805 result = handler.call(this, event); 806 } 807 } 808 809 if (!isStopPropagation && isBubble && BUBBLE_EVENTS.indexOf(type) !== -1) { 810 if (this._parentNode) { 811 const parentNode = this._parentNode as Element; 812 event.currentTarget = parentNode; 813 parentNode.fireEvent(type, event, isBubble); // no options 814 } 815 } 816 817 return result; 818 } 819 820 /** 821 * Get all styles of current element. 822 * @return {object} style 823 */ 824 public toStyle(): any { 825 // Selector Specificity inline > #id > .class > tag > inheritance. 826 const style = Object.assign({}, this._inheritedStyle); 827 this.assignStyle(style, this._tagStyle); 828 this.assignStyle(style, this._classStyle); 829 this.assignStyle(style, this._idStyle); 830 this.assignStyle(style, this.style); 831 return style; 832 } 833 834 /** 835 * Assign style. 836 * @param {*} src - Source style object. 837 * @param {*} dest - Target style object. 838 */ 839 public assignStyle(src: any, dest: any): void { 840 if (dest) { 841 const keys = Object.keys(dest); 842 843 // Margin and padding style: the style should be empty in the first. 844 keys.sort(function(style1, style2) { 845 if (dest[style1] === '') { 846 return 1; 847 } else { 848 return -1; 849 } 850 }); 851 let i = keys.length; 852 while (i--) { 853 const key = keys[i]; 854 const val = dest[key]; 855 if (val) { 856 src[key] = val; 857 } else { 858 if ((val === '' || val === undefined) && src[key]) { 859 return; 860 } 861 src[key] = val; 862 } 863 } 864 } 865 } 866 867 /** 868 * Convert current element to JSON like object. 869 * @param {boolean} [ignoreChildren=false] - whether to ignore child nodes, default false 870 * @return {JSON} JSON object of this element. 871 */ 872 public toJSON(ignoreChildren = false): JSON { 873 const result: any = { 874 ref: this.ref, 875 type: this._type, 876 attr: this.attr, 877 style: this.toStyle(), 878 customComponent: this._isCustomComponent 879 }; 880 const event = []; 881 for (const type in this._event) { 882 const { params } = this._event[type]; 883 if (!params) { 884 event.push(type); 885 } else { 886 event.push({ type, params }); 887 } 888 } 889 if (event.length) { 890 result.event = event; 891 } 892 if (!ignoreChildren && this._pureChildren.length) { 893 result.children = this._pureChildren.map(child => child.toJSON()); 894 } 895 if (this._id) { 896 result.id = this._id; 897 } 898 return result; 899 } 900 901 /** 902 * Convert to HML element tag string. 903 * @override 904 * @return {string} hml of this element. 905 */ 906 public toString(): string { 907 const id = this._id !== null ? this._id : ''; 908 return '<' + this._type + 909 ' id =' + id + 910 ' attr=' + JSON.stringify(this.attr) + 911 ' style=' + JSON.stringify(this.toStyle()) + '>' + 912 this.pureChildren.map((child) => child.toString()).join('') + 913 '</' + this._type + '>'; 914 } 915 916 /** 917 * Destroy this element 918 */ 919 public destroy() { 920 Log.debug(`Element#destroy this._type = ${this._type}.`); 921 if (this._event && this._event['detached']) { 922 this.fireEvent('detached', {}); 923 } 924 this._attr = {}; 925 this._style = {}; 926 this._classStyle = {}; 927 this._event = {}; 928 this._idStyle = {}; 929 this._tagStyle = {}; 930 this._classList.length = 0; 931 932 if (this.destroyHook) { 933 this.destroyHook(); 934 this.destroyHook = null; 935 } 936 if (this._children) { 937 this._children.forEach((child: Node): void => { 938 child.destroy(); 939 }); 940 this._children.length = 0; 941 } 942 if (this._pureChildren) { 943 this._pureChildren.length = 0; 944 } 945 super.destroy(); 946 } 947 948 /** 949 * the judgement of whether the inherited style should update 950 * @param {string | Array} oldClassStyle 951 * @param {string | Array} newClassStyle 952 * @param {string} key 953 * @returns {boolean} 954 */ 955 isSameStyle(oldClassStyle: string | any[], newClassStyle: string | any[], key: string) { 956 if (key === 'fontFamily') { 957 if (oldClassStyle[0].fontFamily === newClassStyle[0].fontFamily) { 958 return true; 959 } 960 } else { 961 if (oldClassStyle === newClassStyle) { 962 return true; 963 } 964 } 965 return false; 966 } 967 968 /** 969 * iterate child node for updating inheritedstyle 970 */ 971 broadcastStyle() { 972 if (this.pureChildren) { 973 for (const child in this.pureChildren) { 974 this.pureChildren[child].setInheritedStyle(); 975 this.pureChildren[child].broadcastStyle(); 976 } 977 } 978 } 979 980 /** 981 * before update inherited style 982 * clear up the inherited style 983 */ 984 resetInheritedStyle() { 985 this.inheritedStyle = {}; 986 } 987 988 /** 989 * inherited style from parent 990 */ 991 public setInheritedStyle() { 992 this.resetInheritedStyle(); 993 const parentNode: Element = this.parentNode as Element; 994 parentNode.inheritStyle(this); 995 const taskCenter = this.getTaskCenter(this.docId); 996 if (taskCenter) { 997 taskCenter.send( 998 'dom', 999 { action: 'updateStyle' }, 1000 [this.ref, this.toStyle()] 1001 ); 1002 } 1003 } 1004 1005 /** 1006 * inherit style of parent. 1007 * @return {object} element 1008 */ 1009 public inheritStyle(node, isFirst = false) { 1010 // for first render, save time 1011 const allStyle = this.toStyle(); 1012 this.setChildStyle(allStyle, node._inheritedStyle); 1013 } 1014 1015 /** 1016 * set inherited style to child 1017 * @param {object} parentStyle 1018 * @param {object} childStyle 1019 * @param {object} node 1020 */ 1021 public setChildStyle(parentStyle, childStyle) { 1022 Object.keys(parentStyle).forEach(key => { 1023 if (CSS_INHERITANCE.includes(key)) { 1024 childStyle[key] = parentStyle[key]; 1025 } 1026 }); 1027 } 1028 1029 private registerNode(node) { 1030 const doc = this._ownerDocument; 1031 doc.nodeMap[node.nodeId] = node; 1032 } 1033} 1034 1035export default Element; 1036