1/* 2 * Copyright (c) 2023-2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 * 15 * all definitions in this file are framework internal 16*/ 17 18class __RepeatImpl<T> { 19 private arr_: Array<T>; 20 private itemGenFuncs_: { [type: string]: RepeatItemGenFunc<T> }; 21 private keyGenFunction_?: RepeatKeyGenFunc<T>; 22 private ttypeGenFunc_: RepeatTTypeGenFunc<T>; 23 // 24 private mkRepeatItem_: (item: T, index?: number) =>__RepeatItemFactoryReturn<T>; 25 private onMoveHandler_?: OnMoveHandler; 26 private itemDragEventHandler?: ItemDragEventHandler; 27 28 private key2Item_ = new Map<string, __RepeatItemInfo<T>>(); 29 30 /**/ 31 constructor() { 32 } 33 34 /**/ 35 public render(config: __RepeatConfig<T>, isInitialRender: boolean): void { 36 this.arr_ = config.arr; 37 this.itemGenFuncs_ = config.itemGenFuncs; 38 this.ttypeGenFunc_ = config.ttypeGenFunc; 39 this.keyGenFunction_ = config.keyGenFunc; 40 this.mkRepeatItem_ = config.mkRepeatItem; 41 this.onMoveHandler_ = config.onMoveHandler; 42 this.itemDragEventHandler = config.itemDragEventHandler; 43 44 isInitialRender ? this.initialRender() : this.reRender(); 45 } 46 47 public getKey2Item(): Map<string, __RepeatItemInfo<T>> { 48 return this.key2Item_; 49 } 50 51 private genKeys(): Map<string, __RepeatItemInfo<T>> { 52 const key2Item = new Map<string, __RepeatItemInfo<T>>(); 53 this.arr_.forEach((item, index) => { 54 const key = this.keyGenFunction_(item, index); 55 key2Item.set(key, { key, index }); 56 }); 57 if (key2Item.size < this.arr_.length) { 58 stateMgmtConsole.warn('__RepeatImpl: Duplicates detected, fallback to index-based keyGen.'); 59 // Causes all items to be re-rendered 60 this.keyGenFunction_ = __RepeatDefaultKeyGen.funcWithIndex; 61 return this.genKeys(); 62 } 63 return key2Item; 64 } 65 66 private initialRender(): void { 67 //console.log('__RepeatImpl initialRender() 0') 68 this.key2Item_ = this.genKeys(); 69 70 RepeatNative.startRender(); 71 72 let index = 0; 73 this.key2Item_.forEach((itemInfo, key) => { 74 itemInfo.repeatItem = this.mkRepeatItem_(this.arr_[index], index); 75 this.initialRenderItem(key, itemInfo.repeatItem); 76 index++; 77 }); 78 let removedChildElmtIds = new Array<number>(); 79 // Fetch the removedChildElmtIds from C++ to unregister those elmtIds with UINodeRegisterProxy 80 RepeatNative.onMove(this.onMoveHandler_, this.itemDragEventHandler); 81 RepeatNative.finishRender(removedChildElmtIds); 82 UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds); 83 stateMgmtConsole.debug(`__RepeatImpl: initialRender elmtIds need unregister after repeat render: ${JSON.stringify(removedChildElmtIds)}`); 84 } 85 86 private reRender(): void { 87 const oldKey2Item: Map<string, __RepeatItemInfo<T>> = this.key2Item_; 88 this.key2Item_ = this.genKeys(); 89 90 // identify array items that have been deleted 91 // these are candidates for re-use 92 const deletedKeysAndIndex = new Array<__RepeatItemInfo<T>>(); 93 for (const [key, feInfo] of oldKey2Item) { 94 if (!this.key2Item_.has(key)) { 95 deletedKeysAndIndex.push(feInfo); 96 } 97 } 98 99 // C++: mv children_ aside to tempchildren_ 100 RepeatNative.startRender(); 101 102 let index = 0; 103 this.key2Item_.forEach((itemInfo, key) => { 104 const item = this.arr_[index]; 105 let oldItemInfo = oldKey2Item.get(key); 106 107 if (oldItemInfo) { 108 // case #1 retained array item 109 // moved from oldIndex to index 110 const oldIndex = oldItemInfo.index; 111 itemInfo.repeatItem = oldItemInfo!.repeatItem!; 112 stateMgmtConsole.debug(`__RepeatImpl: retained: key ${key} ${oldIndex}->${index}`); 113 itemInfo.repeatItem.updateIndex(index); 114 // C++ mv from tempChildren[oldIndex] to end of children_ 115 RepeatNative.moveChild(oldIndex); 116 117 // TBD moveChild() only when item types are same 118 } else if (deletedKeysAndIndex.length) { 119 // case #2: 120 // new array item, there is an deleted array items whose 121 // UINode children cab re-used 122 const oldItemInfo = deletedKeysAndIndex.pop(); 123 const reuseKey = oldItemInfo!.key; 124 const oldKeyIndex = oldItemInfo!.index; 125 const oldRepeatItem = oldItemInfo!.repeatItem!; 126 itemInfo.repeatItem = oldRepeatItem; 127 stateMgmtConsole.debug(`__RepeatImpl: new: key ${key} reuse key ${reuseKey} ${oldKeyIndex}->${index}`); 128 129 itemInfo.repeatItem.updateItem(item); 130 itemInfo.repeatItem.updateIndex(index); 131 132 // update key2item_ Map 133 this.key2Item_.set(key, itemInfo); 134 135 // TBD moveChild() only when item types are same 136 // C++ mv from tempChildren[oldIndex] to end of children_ 137 RepeatNative.moveChild(oldKeyIndex); 138 } else { 139 // case #3: 140 // new array item, there are no deleted array items 141 // render new UINode children 142 itemInfo.repeatItem = this.mkRepeatItem_(item, index); 143 this.initialRenderItem(key, itemInfo.repeatItem); 144 this.afterAddChild(); 145 } 146 147 index++; 148 }); 149 150 // keep this.id2item_. by removing all entries for remaining 151 // deleted items 152 deletedKeysAndIndex.forEach(delItem => { 153 if (delItem && delItem.repeatItem && ('aboutToBeDeleted' in delItem.repeatItem)) { 154 // delete repeatItem property 155 delItem.repeatItem.aboutToBeDeleted(); 156 } 157 this.key2Item_.delete(delItem!.key); 158 }); 159 160 // Finish up for.each update 161 // C++ tempChildren.clear() , trigger re-layout 162 let removedChildElmtIds = new Array<number>(); 163 // Fetch the removedChildElmtIds from C++ to unregister those elmtIds with UINodeRegisterProxy 164 RepeatNative.onMove(this.onMoveHandler_, this.itemDragEventHandler); 165 RepeatNative.finishRender(removedChildElmtIds); 166 UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds); 167 stateMgmtConsole.debug(`__RepeatImpl: reRender elmtIds need unregister after repeat render: ${JSON.stringify(removedChildElmtIds)}`); 168 } 169 170 private afterAddChild(): void { 171 if (this.onMoveHandler_ === undefined || this.onMoveHandler_ === null) { 172 return; 173 } 174 RepeatNative.afterAddChild(); 175 } 176 177 private initialRenderItem(key: string, repeatItem: __RepeatItemFactoryReturn<T>): void { 178 //console.log('__RepeatImpl initialRenderItem()') 179 // render new UINode children 180 stateMgmtConsole.debug(`__RepeatImpl: new: key ${key} n/a->${repeatItem.index}`); 181 182 // C++: initial render will render to the end of children_ 183 RepeatNative.createNewChildStart(key); 184 185 // execute the itemGen function 186 const itemType = this.ttypeGenFunc_?.(repeatItem.item, repeatItem.index) ?? RepeatEachFuncTtype; 187 const itemFunc = this.itemGenFuncs_[itemType] ?? this.itemGenFuncs_[RepeatEachFuncTtype]; 188 itemFunc(repeatItem); 189 190 RepeatNative.createNewChildFinish(key); 191 } 192 193}; 194