• 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 - Reconstruct the class 'Vm' and make it more adaptable to framework.
21 * Copyright (c) 2021 Huawei Device Co., Ltd.
22 */
23
24/**
25 * @fileOverview
26 * ViewModel Constructor & definition
27 */
28
29import {
30  extend,
31  Log,
32  removeItem
33} from '../../utils/index';
34import {
35  initState,
36  initBases
37} from '../reactivity/state';
38import {
39  build, FragBlockInterface
40} from './compiler';
41import {
42  set,
43  del
44} from '../reactivity/observer';
45import {
46  watch,
47  initPropsToData
48} from './pageLife';
49import {
50  initEvents,
51  ExternalEvent,
52  Evt
53} from './events';
54import {
55  selector
56} from './selector';
57import Page from '../page/index';
58import Element from '../../vdom/Element';
59import {
60  ComputedInterface,
61  cssType,
62  Props,
63  VmOptions,
64  MediaStatusInterface,
65  ExternalBindingInterface
66} from './vmOptions';
67import {
68  hasOwn
69} from '../util/shared';
70
71/**
72 * VM constructor.
73 * @param {string} type - Type.
74 * @param {null | VmOptions} options - Component options.
75 * @param {Vm} parentVm   which contains __app.
76 * @param {Element | FragBlockInterface} parentEl - root element or frag block.
77 * @param {Object} mergedData - External data.
78 * @param {ExternalEvent} externalEvents - External events.
79 */
80export default class Vm {
81  private _$app: any;
82  private __methods: Record<string, (...args: unknown[]) => any>;
83  private __type: string;
84  private __css: cssType;
85  private __vmOptions: VmOptions;
86  private __parent: Vm;
87  private __realParent: Vm;
88  private __computed: ComputedInterface;
89  private __selector: object;
90  private __parentEl: Element | FragBlockInterface;
91  private __app: Page;
92  private __shareData: any;
93  private __data: any;
94  private __props: Props;
95  private __init: boolean;
96  private __valid: boolean;
97  private __visible: boolean;
98  private __ready: boolean;
99  private __rootEl: Element;
100  private __ids: Record<string, {vm: Vm, el: Element}>;
101  private __vmEvents: object;
102  private __childrenVms: Vm[];
103  private __externalBinding: ExternalBindingInterface;
104  private readonly __descriptor: string;
105  private __isHide: boolean;
106  private __mediaStatus: Partial<MediaStatusInterface<string, boolean>>;
107  private _$refs: Record<string, Element>;
108  private __slotContext: { content: Record<string, any>, parentVm: Vm };
109  private __counterMapping = new Map();
110
111  constructor(
112    type: string,
113    options: null | VmOptions,
114    parentVm: Vm | any,
115    parentEl: Element | FragBlockInterface,
116    mergedData: object,
117    externalEvents: ExternalEvent
118  ) {
119    this._$app = global.aceapp;
120    this.__parent = parentVm.__realParent ? parentVm.__realParent : parentVm;
121    this.__app = parentVm.__app;
122    parentVm.__childrenVms && parentVm.__childrenVms.push(this);
123
124    if (!options && this.__app.customComponentMap) {
125      options = this.__app.customComponentMap[type];
126    }
127    const data = options.data || {};
128    const shareData = options.shareData || {};
129    this.__vmOptions = options;
130    this.__computed = options.computed;
131    this.__css = options.style;
132    this.__selector = selector(this.__css);
133    this.__ids = {};
134    this._$refs = {};
135    this.__vmEvents = {};
136    this.__childrenVms = [];
137    this.__type = type;
138    this.__valid = true;
139    this.__props = [];
140    this.__methods = {};
141
142    // Bind events and lifecycles.
143    initEvents(this, externalEvents);
144
145    Log.debug(
146      `'_innerInit' lifecycle in Vm(${this.__type}) and mergedData = ${JSON.stringify(mergedData)}.`
147    );
148    this.$emit('hook:_innerInit');
149    this.__data = (typeof data === 'function' ? data.apply(this) : data) || {};
150    this.__shareData = (typeof shareData === 'function' ? shareData.apply(this) : shareData) || {};
151    this.__descriptor = options._descriptor;
152    if (global.aceapp && global.aceapp.i18n && global.aceapp.i18n.extend) {
153      global.aceapp.i18n.extend(this);
154    }
155    if (global.aceapp && global.aceapp.dpi && global.aceapp.dpi.extend) {
156      global.aceapp.dpi.extend(this);
157    }
158
159    // MergedData means extras params.
160    if (mergedData) {
161      if (hasOwn(mergedData, 'paramsData') && hasOwn(mergedData, 'dontOverwrite') && mergedData['dontOverwrite'] === false) {
162        dataAccessControl(this, mergedData['paramsData'], this.__app.options && this.__app.options.appCreate);
163        extend(this._data, mergedData['paramsData']);
164      } else {
165        dataAccessControl(this, mergedData, this.__app.options && this.__app.options.appCreate);
166        extend(this._data, mergedData);
167      }
168    }
169
170    initPropsToData(this);
171    initState(this);
172    initBases(this);
173    Log.debug(`"onInit" lifecycle in Vm(${this.__type})`);
174
175    if (mergedData && hasOwn(mergedData, 'paramsData') && hasOwn(mergedData, 'dontOverwrite')) {
176      if (mergedData['dontOverwrite'] === false) {
177        this.$emit('hook:onInit');
178      } else {
179        this.$emitDirect('hook:onInit', mergedData['paramsData']);
180      }
181    } else {
182      this.$emit('hook:onInit');
183    }
184
185    if (!this.__app.doc) {
186      return;
187    }
188    this.__mediaStatus = {};
189    this.__mediaStatus.orientation = this.__app.options.orientation;
190    this.__mediaStatus.width = this.__app.options.width;
191    this.__mediaStatus.height = this.__app.options.height;
192    this.__mediaStatus.resolution = this.__app.options.resolution;
193    this.__mediaStatus['device-type'] = this.__app.options['device-type'];
194    this.__mediaStatus['aspect-ratio'] = this.__app.options['aspect-ratio'];
195    this.__mediaStatus['device-width'] = this.__app.options['device-width'];
196    this.__mediaStatus['device-height'] = this.__app.options['device-height'];
197    this.__mediaStatus['round-screen'] = this.__app.options['round-screen'];
198    this.__mediaStatus['dark-mode'] = this.__app.options['dark-mode'];
199
200    // If there is no parentElement, specify the documentElement.
201    this.__parentEl = parentEl || this.__app.doc.documentElement;
202    build(this);
203  }
204
205  /**
206   * Get the element by id.
207   * @param {string | number} [id] - Element id.
208   * @return {Element} Element object. if get null, return root element.
209   */
210  public $element(id?: string | number): Element {
211    if (id) {
212      if (typeof id !== 'string' && typeof id !== 'number') {
213        Log.warn(`Invalid parameter type: The type of 'id' should be string or number, not ${typeof id}.`);
214        return;
215      }
216      const info: any = this._ids[id];
217      if (info) {
218        return info.el;
219      }
220    } else {
221      return this.__rootEl;
222    }
223  }
224
225  /**
226   * Get the vm by id.
227   * @param {string} id - Vm id.
228   * @return {Vm} Vm object.
229   */
230  public $vm(id: string): Vm {
231    const info = this._ids[id];
232    if (info) {
233      return info.vm;
234    }
235  }
236
237  /**
238   * Get parent Vm of current.
239   */
240  public $parent(): Vm {
241    return this._parent;
242  }
243
244  /**
245   * Get child Vm of current.
246   */
247  public $child(id: string): Vm {
248    if (typeof id !== 'string') {
249      Log.warn(`Invalid parameter type: The type of 'id' should be string, not ${typeof id}.`);
250      return;
251    }
252    return this.$vm(id);
253  }
254
255  /**
256   * Get root element of current.
257   */
258  public $rootElement(): Element {
259    return this.__rootEl;
260  }
261
262  /**
263   * Get root Vm of current.
264   */
265  public $root(): Vm {
266    return getRoot(this);
267  }
268
269  /**
270   * Execution Method.
271   * @param {string} type - Type.
272   * @param {Object} [detail] - May needed for Evt.
273   * @param {*} args - Arg list.
274   * @return {*}
275   */
276  public $emit(type: string, detail?: object, ...args: any[]): any[] {
277    if (typeof type !== 'string') {
278      Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`);
279      return;
280    }
281    const events = this.__vmEvents;
282    const handlerList = events[type];
283    if (handlerList) {
284      const results = [];
285      const evt = new Evt(type, detail);
286      handlerList.forEach((handler) => {
287        results.push(handler.call(this, evt, ...args));
288      });
289      return results;
290    }
291  }
292
293  /**
294   * Execution Method directly.
295   * @param {string} type - Type.
296   * @param {*} args - Arg list.
297   * @return {*}
298   */
299  public $emitDirect(type: string, ...args: any[]): any[] {
300    if (typeof type !== 'string') {
301      Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`);
302      return;
303    }
304    const events = this.__vmEvents;
305    const handlerList = events[type];
306    if (handlerList) {
307      const results = [];
308      handlerList.forEach((handler) => {
309        results.push(handler.call(this, ...args));
310      });
311      return results;
312    }
313  }
314
315  /**
316   * Dispatch events, passing upwards along the parent.
317   * @param {string} type - Type.
318   * @param {Object} [detail] - May needed for Evt.
319   */
320  public $dispatch(type: string, detail?: object): void {
321    if (typeof type !== 'string') {
322      Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`);
323      return;
324    }
325    const evt = new Evt(type, detail);
326    this.$emit(type, evt);
327    if (!evt.hasStopped() && this.__parent && this.__parent.$dispatch) {
328      this.__parent.$dispatch(type, evt);
329    }
330  }
331
332  /**
333   * Broadcast event, which is passed down the subclass.
334   * @param {string} type - Type.
335   * @param {Object} [detail] - May be needed for Evt.
336   */
337  public $broadcast(type: string, detail?: object): void {
338    if (typeof type !== 'string') {
339      Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`);
340      return;
341    }
342    const evt = new Evt(type, detail);
343    this.$emit(type, evt);
344    if (!evt.hasStopped() && this.__childrenVms) {
345      this.__childrenVms.forEach((subVm) => {
346        subVm.$broadcast(type, evt);
347      });
348    }
349  }
350
351  /**
352   * Add the event listener.
353   * @param {string} type - Type.
354   * @param {Function} handler - To add.
355   */
356  public $on(type: string, handler: Function): void {
357    if (typeof type !== 'string') {
358      Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`);
359      return;
360    }
361    if (typeof handler !== 'function') {
362      Log.warn(`Invalid parameter type: The type of 'handler' should be function, not ${typeof handler}.`);
363      return;
364    }
365    const events = this.__vmEvents;
366    const handlerList = events[type] || [];
367    handlerList.push(handler);
368    events[type] = handlerList;
369    if (type === 'hook:onReady' && this.__ready) {
370      this.$emit('hook:onReady');
371    }
372  }
373
374  /**
375   * Remove the event listener.
376   * @param {string} type - Type.
377   * @param {Function} handler - To remove.
378   */
379  public $off(type: string, handler: Function): void {
380    if (typeof type !== 'string') {
381      Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`);
382      return;
383    }
384    if (typeof handler !== 'function') {
385      Log.warn(`Invalid parameter type: The type of 'handler' should be function, not ${typeof handler}.`);
386      return;
387    }
388    const events = this.__vmEvents;
389    if (!handler) {
390      delete events[type];
391      return;
392    }
393    const handlerList = events[type];
394    if (!handlerList) {
395      return;
396    }
397    removeItem(handlerList, handler);
398  }
399
400  /**
401   * Execution element.fireEvent Method.
402   * @param {string} type - Type.
403   * @param {Object} data - needed for Evt.
404   * @param {string} id - Element id.
405   */
406  public $emitElement(type: string, data: object, id: string): void {
407    if (typeof type !== 'string') {
408      Log.warn(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`);
409      return;
410    }
411    if (typeof id !== 'string') {
412      Log.warn(`Invalid parameter type: The type of 'id' should be string, not ${typeof id}.`);
413      return;
414    }
415    const info = this._ids[id];
416    if (info) {
417      const element = info.el;
418      const evt = new Evt(type, data);
419      element.fireEvent(type, evt, false);
420    } else {
421      Log.warn('The id is invalid, id = ' + id);
422    }
423  }
424
425  /**
426   * Watch a calc function and callback if the calc value changes.
427   * @param {string} data - Data that needed.
428   * @param {Function | string} callback - Callback function.
429   */
430  public $watch(data: string, callback: ((...args: any) => any) | string): void {
431    if (typeof data !== 'string') {
432      Log.warn(`Invalid parameter type: The type of 'data' should be string, not ${typeof data}.`);
433      return;
434    }
435    if (typeof callback !== 'function' && typeof callback !== 'string') {
436      Log.warn(`Invalid parameter type: The type of 'callback' should be function or string, not ${typeof callback}.`);
437      return;
438    }
439    watch(this, data, callback);
440  }
441
442  /**
443   * Set a property on an object.
444   * @param {string} key - Get value by key.
445   * @param {*} value - Property
446   */
447  public $set(key: string, value: any): void {
448    if (typeof key !== 'string') {
449      Log.warn(`Invalid parameter type: The type of 'key' should be string, not ${typeof key}.`);
450      return;
451    }
452    if (key.indexOf('.') !== -1) {
453      _proxySet(this._data, key, value);
454    }
455    set(this._data, key, value);
456  }
457
458  /**
459   * Delete a property and trigger change.
460   * @param {string} key - Get by key.
461   */
462  public $delete(key: string): void {
463    if (typeof key !== 'string') {
464      Log.warn(`Invalid parameter type: The type of 'key' should be string, not ${typeof key}.`);
465      return;
466    }
467    del(this._data, key);
468  }
469
470  /**
471   * Delete Vm object.
472   */
473  public destroy(): void {
474    Log.debug(`[JS Framework] "onDestroy" lifecycle in Vm(${this.__type})`);
475    this.$emit('hook:onDestroy');
476    this.$emit('hook:onDetached');
477    fireNodeDetached(this.__rootEl);
478    this.__valid = false;
479
480    delete this.__app;
481    delete this.__computed;
482    delete this.__css;
483    delete this.__data;
484    delete this.__ids;
485    delete this.__vmOptions;
486    delete this.__parent;
487    delete this.__parentEl;
488    delete this.__rootEl;
489    delete this._$refs;
490
491    // Destroy child vms recursively.
492    if (this.__childrenVms) {
493      let vmCount: number = this.__childrenVms.length;
494      while (vmCount--) {
495        this.destroy.call(this.__childrenVms[vmCount], this.__childrenVms[vmCount]);
496      }
497      delete this.__childrenVms;
498    }
499    delete this.__type;
500    delete this.__vmEvents;
501  }
502
503  /**
504   * $t function.
505   * @param {string} key - Key.
506   * @return {string} - Key.
507   */
508  public $t(key: string): string {
509    return key;
510  }
511
512  /**
513   * $tc function.
514   * @param {string} key - Key.
515   * @return {string} - Key.
516   */
517  public $tc(key: string): string {
518    return key;
519  }
520
521  /**
522   * $r function.
523   * @param {string} key - Key.
524   * @return {string} - Key.
525   */
526  public $r(key: string): string {
527    return key;
528  }
529
530  /**
531   * $app function.
532   * @return {*} - aceapp.
533   */
534  public get $app(): any {
535    return this._$app;
536  }
537
538  /**
539   * Methods of this Vm.
540   * @type {Object}
541   * @readonly
542   */
543  public get _methods() {
544    return this.__methods;
545  }
546
547  public $getCounterMapping(key: string): number {
548    return this.__counterMapping.get(key);
549  }
550
551  public $setCounterMapping(key: string, value: number) {
552    this.__counterMapping.set(key, value);
553  }
554
555  public get _counterMapping() {
556    return this.__counterMapping;
557  }
558
559  /**
560   * Type of this Vm.
561   * @type {string}
562   * @readonly
563   */
564  public get _type() {
565    return this.__type;
566  }
567
568  public set _type(newType: string) {
569    this.__type = newType;
570  }
571
572  /**
573   * Css of this Vm.
574   * @type {[key: string]: any}
575   * @readonly
576   */
577  public get _css() {
578    return this.__css;
579  }
580
581  /**
582   * Options of this Vm.
583   * @type {VmOptions}
584   */
585  public get _vmOptions() {
586    return this.__vmOptions;
587  }
588
589  public set _vmOptions(newOptions: VmOptions) {
590    this.__vmOptions = newOptions;
591  }
592
593  /**
594   * Parent of this Vm.
595   * @type {Vm}
596   * @readonly
597   */
598  public get _parent() {
599    return this.__parent;
600  }
601
602  /**
603   * RealParent of this Vm.
604   * @type {Vm}
605   */
606  public get _realParent() {
607    return this.__realParent;
608  }
609
610  public set _realParent(realParent: Vm) {
611    this.__realParent = realParent;
612  }
613
614  /**
615   * Computed of this Vm.
616   * @type {ComputedInterface}
617   */
618  public get computed() {
619    return this.__computed;
620  }
621
622  public set computed(newComputed: ComputedInterface) {
623    this.__computed = newComputed;
624  }
625
626  /**
627   * Selector of this Vm.
628   * @type {Object}
629   * @readonly
630   */
631  public get _selector() {
632    return this.__selector;
633  }
634
635  /**
636   * ParentEl of this Vm.
637   * @type {FragBlockInterface | Element}
638   */
639  public get _parentEl() {
640    return this.__parentEl;
641  }
642
643  public set _parentEl(newParentEl: FragBlockInterface | Element) {
644    this.__parentEl = newParentEl;
645  }
646
647  /**
648   * App of this Vm.
649   * @type {Page}
650   */
651  public get _app() {
652    return this.__app;
653  }
654
655  public set _app(newApp: Page) {
656    this.__app = newApp;
657  }
658
659  /**
660   * ShareData of this Vm.
661   * @type {*}
662   */
663  public get _shareData() {
664    return this.__shareData;
665  }
666
667  public set _shareData(newShareData: object) {
668    this.__shareData = newShareData;
669  }
670
671  /**
672   * Data of this Vm.
673   * @type {*}
674   */
675  public get _data() {
676    return this.__data;
677  }
678
679  public set _data(newData: any) {
680    this.__data = newData;
681  }
682
683  /**
684   * Props of this Vm.
685   * @type {Props}
686   * @readonly
687   */
688  public get _props() {
689    return this.__props;
690  }
691
692  /**
693   * Init of this Vm.
694   * @type {boolean}
695   */
696  public get _init() {
697    return this.__init;
698  }
699
700  public set _init(newInit: boolean) {
701    this.__init = newInit;
702  }
703
704  /**
705   * Valid of this Vm.
706   * @type {boolean}
707   * @readonly
708   */
709  public get _valid() {
710    return this.__valid;
711  }
712
713  /**
714   * Visible of this Vm.
715   * @type {boolean}
716   */
717  public get _visible() {
718    return this.__visible;
719  }
720
721  public set _visible(newVisible) {
722    this.__visible = newVisible;
723  }
724
725  /**
726   * Ready of this Vm.
727   * @type {boolean}
728   */
729  public get _ready() {
730    return this.__ready;
731  }
732
733  public set _ready(newReady: boolean) {
734    this.__ready = newReady;
735  }
736
737  /**
738   * RootEl of this Vm.
739   * @type {Element}
740   */
741  public get _rootEl() {
742    return this.__rootEl;
743  }
744
745  public set _rootEl(newRootEl: Element) {
746    this.__rootEl = newRootEl;
747  }
748
749  /**
750   * Ids of this Vm.
751   * @type {{[key: string]: { vm: Vm, el: Element}}}
752   * @readonly
753   */
754  public get _ids() {
755    return this.__ids;
756  }
757
758  /**
759   * VmEvents of this Vm.
760   * @type {Object}
761   * @readonly
762   */
763  public get _vmEvents() {
764    return this.__vmEvents;
765  }
766
767  /**
768   * children of vm.
769   * @return {Array} - children of Vm.
770   */
771  public get _childrenVms() {
772    return this.__childrenVms;
773  }
774
775  /**
776   * ExternalBinding of this Vm.
777   * @type {ExternalBinding}
778   */
779  public get _externalBinding() {
780    return this.__externalBinding;
781  }
782
783  public set _externalBinding(newExternalBinding: ExternalBindingInterface) {
784    this.__externalBinding = newExternalBinding;
785  }
786
787  /**
788   * Descriptor of this Vm.
789   * @type {string}
790   * @readonly
791   */
792  public get _descriptor() {
793    return this.__descriptor;
794  }
795
796  /**
797   * IsHide of this Vm.
798   * @type {boolean}
799   */
800  public get _isHide() {
801    return this.__isHide;
802  }
803
804  public set _isHide(newIsHide: boolean) {
805    this.__isHide = newIsHide;
806  }
807
808  /**
809   * MediaStatus of this Vm.
810   * @type {MediaStatusInterface<string, boolean>}
811   */
812  public get _mediaStatus() {
813    return this.__mediaStatus;
814  }
815
816  public set _mediaStatus(newMediaStatus: Partial<MediaStatusInterface<string, boolean>>) {
817    this.__mediaStatus = newMediaStatus;
818  }
819
820  /**
821   * $refs of this Vm.
822   * @type {[key: string]: Element}
823   * @readonly
824   */
825  public get $refs() {
826    return this._$refs;
827  }
828
829  /**
830   * slotContext of this Vm.
831   * @type { content: Record<string, any>, parentVm: Vm }
832   */
833  public get _slotContext() {
834    return this.__slotContext;
835  }
836
837  public set _slotContext(newMSoltContext: { content: Record<string, any>, parentVm: Vm }) {
838    this.__slotContext = newMSoltContext;
839  }
840}
841
842/**
843 * Set proxy.
844 * @param {Object} data - Data that needed.
845 * @param {string} key - Get prop by key.
846 * @param {*} value - Property.
847 */
848function _proxySet(data: object, key: string, value: any): void {
849  let tempObj = data;
850  const keys = key.split('.');
851  const len = keys.length;
852  for (let i = 0; i < len; i++) {
853    const prop = keys[i];
854    if (i === len - 1) {
855      set(tempObj, prop, value);
856      tempObj = null;
857      break;
858    }
859    if (tempObj[prop] === null || typeof tempObj[prop] !== 'object' && typeof tempObj[prop] !== 'function') {
860      Log.warn(`Force define property '${prop}' of '${JSON.stringify(tempObj)}' with value '{}', `
861        + `old value is '${tempObj[prop]}'.`);
862      set(tempObj, prop, {});
863    }
864    tempObj = tempObj[prop];
865  }
866}
867
868/**
869 * Control data access.
870 * @param {Vm} vm - Vm object.
871 * @param {Object} mergedData - Merged data.
872 * @param {boolean} external - If has external data.
873 */
874function dataAccessControl(vm: any, mergedData: object, external: boolean): void {
875  if (vm._descriptor && Object.keys(vm._descriptor).length !== 0) {
876    const keys = Object.keys(mergedData);
877    keys.forEach(key => {
878      const desc = vm._descriptor[key];
879      if (!desc || desc.access === 'private' || external && desc.access === 'protected') {
880        Log.error(`(${key}) can not modify`);
881        delete mergedData[key];
882      }
883    });
884  }
885}
886
887/**
888 * Get root Vm.
889 * @param {Vm} vm - Vm object.
890 * @return {Vm} Root vm.
891 */
892function getRoot(vm: any): Vm {
893  const parent = vm._parent;
894  if (!parent) {
895    return vm;
896  }
897  if (parent.__rootVm) {
898    return vm;
899  }
900  return getRoot(parent);
901}
902
903/**
904 * order node and fire detached event.
905 * @param {Element} el - Element object.
906 */
907function fireNodeDetached(el: Element) {
908  if (!el) {
909    return;
910  }
911  if (el.event && el.event['detached']) {
912    el.fireEvent('detached', {});
913  }
914  if (el.children && el.children.length !== 0) {
915    for (const child of el.children) {
916      fireNodeDetached(child as Element);
917    }
918  }
919}
920