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