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 public $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.debug(`Invalid parameter type: The type of 'type' should be string, not ${typeof type}.`); 359 return; 360 } 361 if (typeof handler !== 'function') { 362 Log.debug(`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 * Methods of this Vm. 532 * @type {Object} 533 * @readonly 534 */ 535 public get _methods() { 536 return this.__methods; 537 } 538 539 public $getCounterMapping(key: string): number { 540 return this.__counterMapping.get(key); 541 } 542 543 public $setCounterMapping(key: string, value: number) { 544 this.__counterMapping.set(key, value); 545 } 546 547 public get _counterMapping() { 548 return this.__counterMapping; 549 } 550 551 /** 552 * Type of this Vm. 553 * @type {string} 554 * @readonly 555 */ 556 public get _type() { 557 return this.__type; 558 } 559 560 public set _type(newType: string) { 561 this.__type = newType; 562 } 563 564 /** 565 * Css of this Vm. 566 * @type {[key: string]: any} 567 * @readonly 568 */ 569 public get _css() { 570 return this.__css; 571 } 572 573 /** 574 * Options of this Vm. 575 * @type {VmOptions} 576 */ 577 public get _vmOptions() { 578 return this.__vmOptions; 579 } 580 581 public set _vmOptions(newOptions: VmOptions) { 582 this.__vmOptions = newOptions; 583 } 584 585 /** 586 * Parent of this Vm. 587 * @type {Vm} 588 * @readonly 589 */ 590 public get _parent() { 591 return this.__parent; 592 } 593 594 /** 595 * RealParent of this Vm. 596 * @type {Vm} 597 */ 598 public get _realParent() { 599 return this.__realParent; 600 } 601 602 public set _realParent(realParent: Vm) { 603 this.__realParent = realParent; 604 } 605 606 /** 607 * Computed of this Vm. 608 * @type {ComputedInterface} 609 */ 610 public get computed() { 611 return this.__computed; 612 } 613 614 public set computed(newComputed: ComputedInterface) { 615 this.__computed = newComputed; 616 } 617 618 /** 619 * Selector of this Vm. 620 * @type {Object} 621 * @readonly 622 */ 623 public get _selector() { 624 return this.__selector; 625 } 626 627 /** 628 * ParentEl of this Vm. 629 * @type {FragBlockInterface | Element} 630 */ 631 public get _parentEl() { 632 return this.__parentEl; 633 } 634 635 public set _parentEl(newParentEl: FragBlockInterface | Element) { 636 this.__parentEl = newParentEl; 637 } 638 639 /** 640 * App of this Vm. 641 * @type {Page} 642 */ 643 public get _app() { 644 return this.__app; 645 } 646 647 public set _app(newApp: Page) { 648 this.__app = newApp; 649 } 650 651 /** 652 * ShareData of this Vm. 653 * @type {*} 654 */ 655 public get _shareData() { 656 return this.__shareData; 657 } 658 659 public set _shareData(newShareData: object) { 660 this.__shareData = newShareData; 661 } 662 663 /** 664 * Data of this Vm. 665 * @type {*} 666 */ 667 public get _data() { 668 return this.__data; 669 } 670 671 public set _data(newData: any) { 672 this.__data = newData; 673 } 674 675 /** 676 * Props of this Vm. 677 * @type {Props} 678 * @readonly 679 */ 680 public get _props() { 681 return this.__props; 682 } 683 684 /** 685 * Init of this Vm. 686 * @type {boolean} 687 */ 688 public get _init() { 689 return this.__init; 690 } 691 692 public set _init(newInit: boolean) { 693 this.__init = newInit; 694 } 695 696 /** 697 * Valid of this Vm. 698 * @type {boolean} 699 * @readonly 700 */ 701 public get _valid() { 702 return this.__valid; 703 } 704 705 /** 706 * Visible of this Vm. 707 * @type {boolean} 708 */ 709 public get _visible() { 710 return this.__visible; 711 } 712 713 public set _visible(newVisible) { 714 this.__visible = newVisible; 715 } 716 717 /** 718 * Ready of this Vm. 719 * @type {boolean} 720 */ 721 public get _ready() { 722 return this.__ready; 723 } 724 725 public set _ready(newReady: boolean) { 726 this.__ready = newReady; 727 } 728 729 /** 730 * RootEl of this Vm. 731 * @type {Element} 732 */ 733 public get _rootEl() { 734 return this.__rootEl; 735 } 736 737 public set _rootEl(newRootEl: Element) { 738 this.__rootEl = newRootEl; 739 } 740 741 /** 742 * Ids of this Vm. 743 * @type {{[key: string]: { vm: Vm, el: Element}}} 744 * @readonly 745 */ 746 public get _ids() { 747 return this.__ids; 748 } 749 750 /** 751 * VmEvents of this Vm. 752 * @type {Object} 753 * @readonly 754 */ 755 public get _vmEvents() { 756 return this.__vmEvents; 757 } 758 759 /** 760 * children of vm. 761 * @return {Array} - children of Vm. 762 */ 763 public get _childrenVms() { 764 return this.__childrenVms; 765 } 766 767 /** 768 * ExternalBinding of this Vm. 769 * @type {ExternalBinding} 770 */ 771 public get _externalBinding() { 772 return this.__externalBinding; 773 } 774 775 public set _externalBinding(newExternalBinding: ExternalBindingInterface) { 776 this.__externalBinding = newExternalBinding; 777 } 778 779 /** 780 * Descriptor of this Vm. 781 * @type {string} 782 * @readonly 783 */ 784 public get _descriptor() { 785 return this.__descriptor; 786 } 787 788 /** 789 * IsHide of this Vm. 790 * @type {boolean} 791 */ 792 public get _isHide() { 793 return this.__isHide; 794 } 795 796 public set _isHide(newIsHide: boolean) { 797 this.__isHide = newIsHide; 798 } 799 800 /** 801 * MediaStatus of this Vm. 802 * @type {MediaStatusInterface<string, boolean>} 803 */ 804 public get _mediaStatus() { 805 return this.__mediaStatus; 806 } 807 808 public set _mediaStatus(newMediaStatus: Partial<MediaStatusInterface<string, boolean>>) { 809 this.__mediaStatus = newMediaStatus; 810 } 811 812 /** 813 * slotContext of this Vm. 814 * @type { content: Record<string, any>, parentVm: Vm } 815 */ 816 public get _slotContext() { 817 return this.__slotContext; 818 } 819 820 public set _slotContext(newMSoltContext: { content: Record<string, any>, parentVm: Vm }) { 821 this.__slotContext = newMSoltContext; 822 } 823} 824 825/** 826 * Set proxy. 827 * @param {Object} data - Data that needed. 828 * @param {string} key - Get prop by key. 829 * @param {*} value - Property. 830 */ 831function _proxySet(data: object, key: string, value: any): void { 832 let tempObj = data; 833 const keys = key.split('.'); 834 const len = keys.length; 835 for (let i = 0; i < len; i++) { 836 const prop = keys[i]; 837 if (i === len - 1) { 838 set(tempObj, prop, value); 839 tempObj = null; 840 break; 841 } 842 if (tempObj[prop] === null || typeof tempObj[prop] !== 'object' && typeof tempObj[prop] !== 'function') { 843 Log.warn(`Force define property '${prop}' of '${JSON.stringify(tempObj)}' with value '{}', ` 844 + `old value is '${tempObj[prop]}'.`); 845 set(tempObj, prop, {}); 846 } 847 tempObj = tempObj[prop]; 848 } 849} 850 851/** 852 * Control data access. 853 * @param {Vm} vm - Vm object. 854 * @param {Object} mergedData - Merged data. 855 * @param {boolean} external - If has external data. 856 */ 857function dataAccessControl(vm: any, mergedData: object, external: boolean): void { 858 if (vm._descriptor && Object.keys(vm._descriptor).length !== 0) { 859 const keys = Object.keys(mergedData); 860 keys.forEach(key => { 861 const desc = vm._descriptor[key]; 862 if (!desc || desc.access === 'private' || external && desc.access === 'protected') { 863 Log.error(`(${key}) can not modify`); 864 delete mergedData[key]; 865 } 866 }); 867 } 868} 869 870/** 871 * Get root Vm. 872 * @param {Vm} vm - Vm object. 873 * @return {Vm} Root vm. 874 */ 875function getRoot(vm: any): Vm { 876 const parent = vm._parent; 877 if (!parent) { 878 return vm; 879 } 880 if (parent.__rootVm) { 881 return vm; 882 } 883 return getRoot(parent); 884} 885 886/** 887 * order node and fire detached event. 888 * @param {Element} el - Element object. 889 */ 890function fireNodeDetached(el: Element) { 891 if (!el) { 892 return; 893 } 894 if (el.event && el.event['detached']) { 895 el.fireEvent('detached', {}); 896 } 897 if (el.children && el.children.length !== 0) { 898 for (const child of el.children) { 899 fireNodeDetached(child as Element); 900 } 901 } 902} 903