• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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