• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 typeGenFunc_: RepeatTypeGenFunc<T>;
23    //
24    private mkRepeatItem_: (item: T, index?: number) =>__RepeatItemFactoryReturn<T>;
25    private onMoveHandler_?: OnMoveHandler;
26
27    private key2Item_ = new Map<string, __RepeatItemInfo<T>>();
28
29    /**/
30    constructor() {
31    }
32
33    /**/
34    public render(config: __RepeatConfig<T>, isInitialRender: boolean): void {
35        this.arr_ = config.arr;
36        this.itemGenFuncs_ = config.itemGenFuncs;
37        this.typeGenFunc_ = config.typeGenFunc;
38        this.keyGenFunction_ = config.keyGenFunc;
39        this.mkRepeatItem_ = config.mkRepeatItem;
40        this.onMoveHandler_ = config.onMoveHandler;
41
42        isInitialRender ? this.initialRender() : this.reRender();
43    }
44
45    private genKeys(): Map<string, __RepeatItemInfo<T>> {
46        const key2Item = new Map<string, __RepeatItemInfo<T>>();
47        this.arr_.forEach((item, index) => {
48            const key = this.keyGenFunction_(item, index);
49            key2Item.set(key, { key, index });
50        });
51        if (key2Item.size < this.arr_.length) {
52            stateMgmtConsole.warn('__RepeatImpl: Duplicates detected, fallback to index-based keyGen.');
53            // Causes all items to be re-rendered
54            this.keyGenFunction_ = __RepeatDefaultKeyGen.funcWithIndex;
55            return this.genKeys();
56        }
57        return key2Item;
58    }
59
60    private initialRender(): void {
61        //console.log('__RepeatImpl initialRender() 0')
62        this.key2Item_ = this.genKeys();
63
64        RepeatNative.startRender();
65
66        let index = 0;
67        this.key2Item_.forEach((itemInfo, key) => {
68            itemInfo.repeatItem = this.mkRepeatItem_(this.arr_[index], index);
69            this.initialRenderItem(key, itemInfo.repeatItem);
70            index++;
71        });
72        let removedChildElmtIds = new Array<number>();
73        // Fetch the removedChildElmtIds from C++ to unregister those elmtIds with UINodeRegisterProxy
74        RepeatNative.onMove(this.onMoveHandler_);
75        RepeatNative.finishRender(removedChildElmtIds);
76        UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds);
77        stateMgmtConsole.debug(`__RepeatImpl: initialRender elmtIds need unregister after repeat render: ${JSON.stringify(removedChildElmtIds)}`);
78    }
79
80    private reRender(): void {
81        const oldKey2Item: Map<string, __RepeatItemInfo<T>> = this.key2Item_;
82        this.key2Item_ = this.genKeys();
83
84        // identify array items that have been deleted
85        // these are candidates for re-use
86        const deletedKeysAndIndex = new Array<__RepeatItemInfo<T>>();
87        for (const [key, feInfo] of oldKey2Item) {
88            if (!this.key2Item_.has(key)) {
89                deletedKeysAndIndex.push(feInfo);
90            }
91        }
92
93        // C++: mv children_ aside to tempchildren_
94        RepeatNative.startRender();
95
96        let index = 0;
97        this.key2Item_.forEach((itemInfo, key) => {
98            const item = this.arr_[index];
99            let oldItemInfo = oldKey2Item.get(key);
100
101            if (oldItemInfo) {
102                // case #1 retained array item
103                // moved from oldIndex to index
104                const oldIndex = oldItemInfo.index;
105                itemInfo.repeatItem = oldItemInfo!.repeatItem!;
106                stateMgmtConsole.debug(`__RepeatImpl: retained: key ${key} ${oldIndex}->${index}`);
107                itemInfo.repeatItem.updateIndex(index);
108                // C++ mv from tempChildren[oldIndex] to end of children_
109                RepeatNative.moveChild(oldIndex);
110
111                // TBD moveChild() only when item types are same
112            } else if (deletedKeysAndIndex.length) {
113                // case #2:
114                // new array item, there is an deleted array items whose
115                // UINode children cab re-used
116                const oldItemInfo = deletedKeysAndIndex.pop();
117                const reuseKey = oldItemInfo!.key;
118                const oldKeyIndex = oldItemInfo!.index;
119                const oldRepeatItem = oldItemInfo!.repeatItem!;
120                itemInfo.repeatItem = oldRepeatItem;
121                stateMgmtConsole.debug(`__RepeatImpl: new: key ${key} reuse key ${reuseKey}  ${oldKeyIndex}->${index}`);
122
123                itemInfo.repeatItem.updateItem(item);
124                itemInfo.repeatItem.updateIndex(index);
125
126                // update key2item_ Map
127                this.key2Item_.set(key, itemInfo);
128
129                // TBD moveChild() only when item types are same
130                // C++ mv from tempChildren[oldIndex] to end of children_
131                RepeatNative.moveChild(oldKeyIndex);
132            } else {
133                // case #3:
134                // new array item, there are no deleted array items
135                // render new UINode children
136                itemInfo.repeatItem = this.mkRepeatItem_(item, index);
137                this.initialRenderItem(key, itemInfo.repeatItem);
138            }
139
140            index++;
141        });
142
143        // keep  this.id2item_. by removing all entries for remaining
144        // deleted items
145        deletedKeysAndIndex.forEach(delItem => {
146            this.key2Item_.delete(delItem!.key);
147        });
148
149        // Finish up for.each update
150        // C++  tempChildren.clear() , trigger re-layout
151        let removedChildElmtIds = new Array<number>();
152        // Fetch the removedChildElmtIds from C++ to unregister those elmtIds with UINodeRegisterProxy
153        RepeatNative.onMove(this.onMoveHandler_);
154        RepeatNative.finishRender(removedChildElmtIds);
155        UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds);
156        stateMgmtConsole.debug(`__RepeatImpl: reRender elmtIds need unregister after repeat render: ${JSON.stringify(removedChildElmtIds)}`);
157    }
158
159    private initialRenderItem(key: string, repeatItem: __RepeatItemFactoryReturn<T>): void {
160        //console.log('__RepeatImpl initialRenderItem()')
161        // render new UINode children
162        stateMgmtConsole.debug(`__RepeatImpl: new: key ${key} n/a->${repeatItem.index}`);
163
164        // C++: initial render will render to the end of children_
165        RepeatNative.createNewChildStart(key);
166
167        // execute the itemGen function
168        const itemType = this.typeGenFunc_(repeatItem.item, repeatItem.index) ?? '';
169        const itemFunc = this.itemGenFuncs_[itemType] ?? this.itemGenFuncs_[''];
170        itemFunc(repeatItem);
171
172        RepeatNative.createNewChildFinish(key);
173    }
174
175};
176