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