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