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 20import { hasOwn } from '../../../utils/index'; 21 22/** 23 * This class provides action for page refresh. 24 */ 25export default class Differ { 26 private _id: string; 27 private _map: object[]; 28 private _hooks: Function[]; 29 private _hasTimer: boolean; 30 31 constructor(id: string) { 32 this._id = id; 33 this._map = []; 34 this._hooks = []; 35 } 36 37 /** 38 * Check whether the map is empty. 39 * @return {boolean} 40 */ 41 public isEmpty(): boolean { 42 return this._map.length === 0; 43 } 44 45 /** 46 * Id of the page. 47 * @type {string} 48 */ 49 public get id() { 50 return this._id; 51 } 52 53 /** 54 * Append action. 55 * @param {string} type 56 * @param {string} ref 57 * @param {Function} handler 58 */ 59 public append(type: string, ref: string, handler: Function): void { 60 // Ignore depth to speed up render. 61 const defaultDepth: number = 1; 62 if (!this._hasTimer) { 63 this._hasTimer = true; 64 65 // Use setTimeout instead of setTimeoutDiffer 66 // avoid invoking setTimeout after appDestroy 67 if (typeof setTimeout === "function") { 68 setTimeout(() => { 69 this._hasTimer = false; 70 this.flush(); 71 }, 0); 72 } 73 } 74 const map: object[] = this._map; 75 if (!map[defaultDepth]) { 76 map[defaultDepth] = {}; 77 } 78 const group: object = map[defaultDepth]; 79 if (!group[type]) { 80 group[type] = {}; 81 } 82 if (type === 'element') { 83 if (!group[type][ref]) { 84 group[type][ref] = []; 85 } 86 group[type][ref].push(handler); 87 } else { 88 group[type][ref] = handler; 89 } 90 } 91 92 /** 93 * Execute actions of differ. 94 */ 95 public flush(): void { 96 const map: object[] = this._map.slice(); 97 this._map.length = 0; 98 map.forEach((group) => { 99 callTypeList(group, 'element'); 100 callTypeMap(group, 'repeat'); 101 callTypeMap(group, 'shown'); 102 }); 103 104 const hooks: Function[] = this._hooks.slice(); 105 this._hooks.length = 0; 106 hooks.forEach((fn) => { 107 fn(); 108 }); 109 110 if (!this.isEmpty()) { 111 this.flush(); 112 } 113 } 114 then(fn) { 115 this._hooks.push(fn); 116 } 117} 118 119function callTypeMap(group: any, type: string): void { 120 const map: Function[] = group[type]; 121 for (const ref in map) { 122 map[ref](); 123 } 124} 125 126function callTypeList(group: any, type: string): void { 127 const map: any = group[type]; 128 for (const ref in map) { 129 if (hasOwn(map, ref)) { 130 const list: Function[] = map[ref]; 131 list.forEach((handler) => { 132 handler(); 133 }); 134 } 135 } 136} 137