• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023-2025 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
18// declare add. functions beyond that framework needs internally
19interface __IRepeatItemInternal<T> {
20    // set new item value, used during Repeat.each update when
21    // - array item has been replaced with new value (LazyForEach onDataChanged)
22    // - on child reuse. reuse children to render newItemValue
23    updateItem: (newItemVal: T) => void;
24
25    // set new index value, used during  Repeat.each update when
26    // - order of item in array has changed  (LazyforEach onDataMoved)
27    // - on child reuse. reuse children to render newItemValue. index of
28    //   newItemValue is a new one
29    updateIndex: (newIndexValue: number) => void;
30
31    // un-register all repeat sub-components
32    aboutToBeDeleted?: () => void;
33}
34
35interface __RepeatItemFactoryReturn<T> extends RepeatItem<T>, __IRepeatItemInternal<T> { }
36
37// implementation for existing state observation system
38class __RepeatItemPU<T> implements RepeatItem<T>, __IRepeatItemInternal<T> {
39
40    // ObservedPropertyPU is the framework class that implements @State, @Provide
41    // and App/LocalStorage properties
42    private _observedItem: ObservedPropertyPU<T>;
43    private _observedIndex?: ObservedPropertyPU<number>;
44
45    constructor(owningView: ViewPU, initialItem: T, initialIndex?: number) {
46        this._observedItem = new ObservedPropertyPU<T>(initialItem, owningView, 'Repeat item');
47        if (initialIndex !== undefined) {
48            this._observedIndex = new ObservedPropertyPU<number>(initialIndex, owningView, 'Repeat index');
49        }
50    }
51
52    public get item(): T {
53        return this._observedItem.get();
54    }
55
56    public get index(): number | undefined {
57        return this._observedIndex?.get();
58    }
59
60    public updateItem(newItemValue: T): void {
61        this._observedItem.set(newItemValue);
62    }
63
64    public updateIndex(newIndex: number): void {
65        if (!this._observedIndex?.hasDependencies()) {
66            return;
67        }
68        if (this._observedIndex?.getUnmonitored() !== newIndex) {
69            this._observedIndex?.set(newIndex);
70        }
71    }
72
73    public aboutToBeDeleted(): void {
74        this._observedItem.aboutToBeDeleted();
75        this._observedIndex?.aboutToBeDeleted();
76    }
77}
78
79// Framework internal, deep observation
80// Using @ObservedV2_Internal instead of @ObservedV2 to avoid forcing V2 usage.
81@ObservedV2_Internal
82class __RepeatItemV2<T> implements RepeatItem<T>, __IRepeatItemInternal<T> {
83
84    constructor(initialItem: T, initialIndex?: number) {
85        this.item = initialItem;
86        this.index = initialIndex;
87    }
88    // Using @Trace_Internal instead of @Trace to avoid forcing V2 usage.
89    @Trace_Internal item: T;
90    @Trace_Internal index?: number;
91
92    public updateItem(newItemValue: T): void {
93        this.item = newItemValue;
94    }
95
96    public updateIndex(newIndex: number): void {
97        this.index = newIndex;
98    }
99
100    public hasBindingToIndex(): boolean {
101        return this[ObserveV2.SYMBOL_REFS]?.index?.size > 0;
102    }
103}
104
105// helper, framework internal
106interface __RepeatItemInfo<T> {
107    key: string;
108    // also repeatItem includes index
109    // we need separate index because repeatItem set set and updated later than index needs to be set.
110    index: number;
111    repeatItem?: __RepeatItemFactoryReturn<T>;
112}
113
114// helper
115class __RepeatDefaultKeyGen {
116    private static weakMap_ = new WeakMap<Object | Symbol, number>();
117    private static lastKey_ = 0;
118
119    // Return the same IDs for the same items
120    public static func<T>(item: T): string {
121        try {
122            return __RepeatDefaultKeyGen.funcImpl(item);
123        } catch (e) {
124            throw new Error(`Repeat(). Default key gen failed. Application Error!`);
125        }
126    }
127
128    // Return the same IDs for the same pairs <item, index>
129    public static funcWithIndex<T>(item: T, index: number) {
130        return `${index}__` + __RepeatDefaultKeyGen.func(item);
131    }
132
133    private static funcImpl<T>(item: T) {
134        // fast keygen logic can be used with objects/symbols only
135        if (typeof item !== 'object' && typeof item !== 'symbol') {
136            return JSON.stringify(item);
137        }
138        // generate a numeric key, store mappings in WeakMap
139        if (!this.weakMap_.has(item)) {
140            return this.weakMap_.set(item, ++this.lastKey_), `${this.lastKey_}`;
141        }
142        // use cached key
143        return `${this.weakMap_.get(item)}`;
144    }
145};
146
147// TBD comments
148interface __RepeatConfig<T> {
149    owningView_? : ViewV2;
150    arr?: Array<T>;
151    itemGenFuncs?: { [type: string]: RepeatItemGenFunc<T> };
152    keyGenFunc?: RepeatKeyGenFunc<T>;
153    keyGenFuncSpecified?: boolean;
154    ttypeGenFunc?: RepeatTTypeGenFunc<T>;
155    totalCountSpecified?: boolean;
156    totalCount?: number | (() => number);
157    onLazyLoading?: (index : number) => void,
158    templateOptions?: { [type: string]: RepeatTemplateImplOptions };
159    mkRepeatItem?: (item: T, index?: number) => __RepeatItemFactoryReturn<T>;
160    onMoveHandler?: OnMoveHandler;
161    itemDragEventHandler?: ItemDragEventHandler;
162    reusable?: boolean;
163};
164
165// should be empty string, don't change it
166const RepeatEachFuncTtype: string = '';
167
168// __Repeat implements ForEach with child re-use for both existing state observation
169// and deep observation , for non-virtual and virtual code paths (TODO)
170class __Repeat<T> implements RepeatAPI<T> {
171    private config: __RepeatConfig<T> = {};
172    private impl: __RepeatImpl<T> | __RepeatVirtualScrollImpl<T> | __RepeatVirtualScroll2Impl<T>;
173    private isVirtualScroll = false;
174
175    constructor(owningView: ViewV2 | ViewPU, arr: Array<T>) {
176        this.config.owningView_ = owningView instanceof ViewV2 ? owningView : undefined;
177        this.config.arr = arr ?? [];
178        this.config.itemGenFuncs = {};
179        this.config.keyGenFunc = __RepeatDefaultKeyGen.funcWithIndex;
180        this.config.keyGenFuncSpecified = false;
181        this.config.ttypeGenFunc = undefined;
182
183        this.config.totalCountSpecified = false;
184        this.config.totalCount = this.config.arr.length;
185        this.config.templateOptions = {};
186        this.config.reusable = true;
187
188        // to be used with ViewV2
189        const mkRepeatItemV2 = (item: T, index?: number): __RepeatItemFactoryReturn<T> =>
190            new __RepeatItemV2(item as T, index);
191
192        // to be used with ViewPU
193        const mkRepeatItemPU = (item: T, index?: number): __RepeatItemFactoryReturn<T> =>
194            new __RepeatItemPU(owningView as ViewPU, item, index);
195
196        const isViewV2 = (this.config.owningView_ instanceof ViewV2);
197        this.config.mkRepeatItem = isViewV2 ? mkRepeatItemV2: mkRepeatItemPU;
198    }
199
200    public each(itemGenFunc: RepeatItemGenFunc<T>): RepeatAPI<T> {
201        this.config.itemGenFuncs[RepeatEachFuncTtype] = itemGenFunc;
202        this.config.templateOptions[RepeatEachFuncTtype] = this.normTemplateOptions({});
203        return this;
204    }
205
206    public key(keyGenFunc: RepeatKeyGenFunc<T>): RepeatAPI<T> {
207        this.config.keyGenFunc = keyGenFunc;
208        this.config.keyGenFuncSpecified = true;
209        return this;
210    }
211
212    public virtualScroll(options? : {
213        totalCount?: number, onTotalCount?: () => number, onLazyLoading?: (index: number) => void, reusable?: boolean
214    }): RepeatAPI<T> {
215        // use array length by default
216        this.config.totalCount = this.config.arr?.length;
217        this.config.totalCountSpecified = false;
218
219        // options.totalCount must be 0 or a positive integer, or undefined
220        if (Number.isInteger(options?.totalCount) && (options?.totalCount as number) >= 0) {
221            this.config.totalCount = options?.totalCount;
222            this.config.totalCountSpecified = true;
223        }
224        // available since API 18
225        if (options?.onTotalCount) {
226            this.config.totalCount = options.onTotalCount;
227            this.config.totalCountSpecified = true;
228        }
229        if (options?.onTotalCount && options?.totalCount !== undefined) {
230            stateMgmtConsole.error(`Error: Both totalCount and onTotalCount() are defined`);
231        }
232
233        if (typeof options?.reusable === 'boolean') {
234            this.config.reusable = options.reusable;
235        } else if (options?.reusable === null) {
236            this.config.reusable = true;
237            stateMgmtConsole.warn(
238                `Repeat.reusable type should be boolean. Use default setting: reusable = true`);
239        } else {
240            this.config.reusable = true;
241        }
242        if (options?.onLazyLoading) {
243            this.config.onLazyLoading = options.onLazyLoading;
244        }
245
246        this.isVirtualScroll = true;
247        return this;
248    }
249
250    // function to decide which template to use, each template has an ttype
251    public templateId(ttypeGenFunc: RepeatTTypeGenFunc<T>): RepeatAPI<T> {
252        this.config.ttypeGenFunc = ttypeGenFunc;
253        return this;
254    }
255
256    // template: ttype + builder function to render specific type of data item
257    public template(ttype: string, itemGenFunc: RepeatItemGenFunc<T>,
258        options?: RepeatTemplateOptions): RepeatAPI<T>
259    {
260        this.config.itemGenFuncs[ttype] = itemGenFunc;
261        this.config.templateOptions[ttype] = this.normTemplateOptions(options);
262        return this;
263    }
264
265    public updateArr(arr: Array<T>): RepeatAPI<T> {
266        this.config.arr = arr ?? [];
267        return this;
268    }
269
270    public render(isInitialRender: boolean): void {
271        if (!this.config.itemGenFuncs?.[RepeatEachFuncTtype]) {
272            throw new Error(`__Repeat item builder function unspecified. Usage error`);
273        }
274        if (!this.isVirtualScroll) {
275            // Repeat
276            this.impl ??= new __RepeatImpl<T>();
277            this.impl.render(this.config, isInitialRender);
278            return;
279        }
280        // RepeatVirtualScroll v2
281        this.impl ??= new __RepeatVirtualScroll2Impl<T>();
282        this.impl.render(this.config, isInitialRender);
283    }
284
285    // drag and drop API
286    public onMove(handler: OnMoveHandler, eventHandler?: ItemDragEventHandler): RepeatAPI<T> {
287        this.config.onMoveHandler = handler;
288        this.config.itemDragEventHandler = eventHandler;
289        return this;
290    }
291
292    // un-register all repeat sub-components
293    public aboutToBeDeleted(): void {
294        if (this.impl instanceof __RepeatImpl) {
295            this.impl.getKey2Item().forEach((itemInfo: __RepeatItemInfo<T>) => {
296                if (itemInfo.repeatItem && itemInfo.repeatItem instanceof __RepeatItemPU) {
297                    itemInfo.repeatItem.aboutToBeDeleted();
298                }
299            });
300        }
301    }
302
303    // normalize template options
304    private normTemplateOptions(options: RepeatTemplateOptions): RepeatTemplateImplOptions {
305        const value = (options && Number.isInteger(options.cachedCount) && options.cachedCount >= 0)
306            ? {
307                cachedCount: Math.max(0, options.cachedCount),
308                cachedCountSpecified: true
309            }
310            : {
311                cachedCountSpecified: false
312            };
313            return value;
314    }
315}; // __Repeat<T>
316