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