• 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 { 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