• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022 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 *  * ViewPU - View for Partial Update
16 *
17* all definitions in this file are framework internal
18*/
19
20/**
21 * WeakRef
22 * ref to an Object that does not prevent the Object from getting GC'ed
23 * current version of tsc does not know about WeakRef
24 * but Ark runtime supports it
25 *
26 */
27declare class WeakRef<T extends Object> {
28  constructor(o: T);
29  deref(): T;
30}
31
32type ProvidedVarsMapPU = Map<string, ObservedPropertyAbstractPU<any>>;
33
34// denotes a missing elemntId, this is the case during initial render
35const UndefinedElmtId = -1;
36
37// function type of partial update function
38type UpdateFunc = (elmtId: number, isFirstRender: boolean) => void;
39
40// Nativeview
41// implemented in C++  for release
42// and in utest/view_native_mock.ts for testing
43abstract class ViewPU extends NativeViewPartialUpdate
44  implements IViewPropertiesChangeSubscriber {
45
46  // Array.sort() converts array items to string to compare them, sigh!
47  static readonly compareNumber = (a: number, b: number): number => {
48    return (a < b) ? -1 : (a > b) ? 1 : 0;
49  };
50
51  private id_: number;
52
53  private parent_: ViewPU = undefined;
54  private childrenWeakrefMap_ = new Map<number, WeakRef<ViewPU>>();
55
56  private watchedProps: Map<string, (propName: string) => void>
57    = new Map<string, (propName: string) => void>();
58
59  // @Provide'd variables by this class and its ancestors
60  protected providedVars_: ProvidedVarsMapPU;
61
62  // Set of dependent elmtIds that need partial update
63  // during next re-render
64  protected dirtDescendantElementIds_: Set<number>
65    = new Set<number>();
66
67  // registry of update functions
68  // the key is the elementId of the Component/Element that's the result of this function
69  protected updateFuncByElmtId: Map<number, UpdateFunc>
70    = new Map<number, UpdateFunc>();
71
72  // my LocalStorge instance, shared with ancestor Views.
73  // create a default instance on demand if none is initialized
74  protected localStoragebackStore_: LocalStorage = undefined;
75
76  protected get localStorage_() {
77    if (!this.localStoragebackStore_) {
78      stateMgmtConsole.info(`${this.constructor.name} is accessing LocalStorage without being provided an instance. Creating a default instance.`);
79      this.localStoragebackStore_ = new LocalStorage({ /* emty */ });
80    }
81    return this.localStoragebackStore_;
82  }
83
84  protected set localStorage_(instance: LocalStorage) {
85    if (!instance) {
86      // setting to undefined not allowed
87      return;
88    }
89    if (this.localStoragebackStore_) {
90      stateMgmtConsole.error(`${this.constructor.name} is setting LocalStorage instance twice`);
91    }
92    this.localStoragebackStore_ = instance;
93  }
94
95  /**
96   * Create a View
97   *
98   * 1. option: top level View, specify
99   *    - compilerAssignedUniqueChildId must specify
100   *    - parent=undefined
101   *    - localStorage  must provide if @LocalSTorageLink/Prop variables are used
102   *      in this View or descendant Views.
103   *
104   * 2. option: not a top level View
105   *    - compilerAssignedUniqueChildId must specify
106   *    - parent must specify
107   *    - localStorage do not specify, will inherit from parent View.
108   *
109  */
110  constructor(parent: ViewPU, localStorage: LocalStorage, elmtId : number = -1) {
111    super();
112    // if set use the elmtId also as the ViewPU object's subscribable id.
113    // these matching is requiremrnt for updateChildViewById(elmtId) being able to
114    // find the child ViewPU object by given elmtId
115    this.id_= elmtId == -1 ? SubscriberManager.MakeId() : elmtId;
116    this.providedVars_ = parent ? new Map(parent.providedVars_)
117      : new Map<string, ObservedPropertyAbstractPU<any>>();
118
119    this.localStoragebackStore_ = undefined;
120    if (parent) {
121      // this View is not a top-level View
122      stateMgmtConsole.debug(`${this.constructor.name} constructor: Using LocalStorage instance of the parent View.`);
123      this.setCardId(parent.getCardId());
124      this.localStorage_ = parent.localStorage_;
125      parent.addChild(this);
126    } else if (localStorage) {
127      this.localStorage_ = localStorage;
128      stateMgmtConsole.debug(`${this.constructor.name} constructor: Using LocalStorage instance provided via @Entry.`);
129    }
130
131    SubscriberManager.Add(this);
132    stateMgmtConsole.debug(`${this.constructor.name}(${this.id__()}): constructor done`);
133  }
134
135  // globally unique id, this is different from compilerAssignedUniqueChildId!
136  id__(): number {
137    return this.id_;
138  }
139
140  // inform the subscribed property
141  // that the View and thereby all properties
142  // are about to be deleted
143  abstract aboutToBeDeleted(): void;
144
145  // super class will call this function from
146  // its aboutToBeDeleted implementation
147  protected aboutToBeDeletedInternal(): void {
148    // When a custom component is deleted, need to notify the C++ side to clean the corresponding deletion cache Map,
149    // because after the deletion, can no longer clean the RemoveIds cache on the C++ side through the
150    // updateDirtyElements function.
151    let removedElmtIds: number[] = [];
152    this.updateFuncByElmtId.forEach((value: UpdateFunc, key: number) => {
153      this.purgeVariableDependenciesOnElmtId(key);
154      removedElmtIds.push(key);
155    });
156    this.deletedElmtIdsHaveBeenPurged(removedElmtIds);
157
158    this.updateFuncByElmtId.clear();
159    this.watchedProps.clear();
160    this.providedVars_.clear();
161    if (this.parent_) {
162      this.parent_.removeChild(this);
163    }
164  }
165
166  private setParent(parent: ViewPU) {
167    if (this.parent_ && parent) {
168      stateMgmtConsole.warn(`ViewPU('${this.constructor.name}', ${this.id__()}).setChild: changing parent to '${parent.constructor.name}', id ${parent.id__()} (unsafe operation)`);
169    }
170    this.parent_ = parent;
171  }
172
173  /**
174   * add given child and set 'this' as its parent
175   * @param child child to add
176   * @returns returns false if child with given child's id already exists
177   *
178   * framework internal function
179   * Note: Use of WeakRef ensures child and parent do not generate a cycle dependency.
180   * The add. Set<ids> is required to reliably tell what children still exist.
181   */
182  public addChild(child: ViewPU): boolean {
183    if (this.childrenWeakrefMap_.has(child.id__())) {
184      stateMgmtConsole.warn(`ViewPU('${this.constructor.name}', ${this.id__()}).addChild '${child.constructor.name}' id already exists ${child.id__()} !`);
185      return false;
186    }
187    this.childrenWeakrefMap_.set(child.id__(), new WeakRef(child));
188    child.setParent(this);
189    return true;
190  }
191
192  /**
193   * remove given child and remove 'this' as its parent
194   * @param child child to add
195   * @returns returns false if child with given child's id does not exist
196   */
197  public removeChild(child: ViewPU): boolean {
198    const hasBeenDeleted = this.childrenWeakrefMap_.delete(child.id__());
199    if (!hasBeenDeleted) {
200      stateMgmtConsole.warn(`ViewPU('${this.constructor.name}', ${this.id__()}).removeChild '${child.constructor.name}', child id ${child.id__()} not known!`);
201    } else {
202      child.setParent(undefined);
203    }
204    return hasBeenDeleted;
205  }
206
207  /**
208   * Retrieve child by given id
209   * @param id
210   * @returns child if in map and weak ref can still be downreferenced
211   */
212  public getChildById(id: number) {
213    const childWeakRef = this.childrenWeakrefMap_.get(id);
214    return childWeakRef ? childWeakRef.deref() : undefined;
215  }
216
217  protected abstract purgeVariableDependenciesOnElmtId(removedElmtId: number);
218  protected abstract initialRender(): void;
219  protected abstract rerender(): void;
220  protected updateStateVars(params: {}) : void {
221    stateMgmtConsole.warn("ViewPU.updateStateVars unimplemented. Pls upgrade to latest eDSL transpiler version.")
222  }
223
224  protected initialRenderView(): void {
225    this.initialRender();
226  }
227
228  private UpdateElement(elmtId: number): void {
229    // do not process an Element that has been marked to be deleted
230    const updateFunc: UpdateFunc = this.updateFuncByElmtId.get(elmtId);
231    if ((updateFunc == undefined) || (typeof updateFunc !== "function")) {
232      stateMgmtConsole.error(`${this.constructor.name}[${this.id__()}]: update function of ElementId ${elmtId} not found, internal error!`);
233    } else {
234      stateMgmtConsole.debug(`${this.constructor.name}[${this.id__()}]: updateDirtyElements: update function on elmtId ${elmtId} start ...`);
235      updateFunc(elmtId, /* isFirstRender */ false);
236      // continue in native JSView
237      // Finish the Update in JSView::JsFinishUpdateFunc
238      // this function appends no longer used elmtIds (as recrded by VSP) to the given allRmElmtIds array
239      this.finishUpdateFunc(elmtId);
240      stateMgmtConsole.debug(`View ${this.constructor.name} elmtId ${this.id__()}: ViewPU.updateDirtyElements: update function on ElementId ${elmtId} done`);
241    }
242  }
243
244  /**
245   * force a complete rerender / update by executing all update functions
246   * exec a regular rerender first
247   *
248   * @param deep recurse all children as well
249   *
250   * framework internal functions, apps must not call
251   */
252  public forceCompleteRerender(deep: boolean = false): void {
253    stateMgmtConsole.warn(`ViewPU('${this.constructor.name}', ${this.id__()}).forceCompleteRerender - start.`);
254
255    // request list of all (gloabbly) deleted elmtIds;
256    let deletedElmtIds: number[] = [];
257    this.getDeletedElemtIds(deletedElmtIds);
258
259    // see which elmtIds are managed by this View
260    // and clean up all book keeping for them
261    this.purgeDeletedElmtIds(deletedElmtIds);
262
263    Array.from(this.updateFuncByElmtId.keys()).sort(ViewPU.compareNumber).forEach(elmtId => this.UpdateElement(elmtId));
264
265    if (deep) {
266      this.childrenWeakrefMap_.forEach((weakRefChild: WeakRef<ViewPU>) => {
267        const child = weakRefChild.deref();
268        if (child) {
269          (child as ViewPU).forceCompleteRerender(true);
270        }
271      });
272    }
273    stateMgmtConsole.warn(`ViewPU('${this.constructor.name}', ${this.id__()}).forceCompleteRerender - end`);
274  }
275
276  /**
277   * force a complete rerender / update on specific node by executing update function.
278   *
279   * @param elmtId which node needs to update.
280   *
281   * framework internal functions, apps must not call
282   */
283  public forceRerenderNode(elmtId: number): void {
284    // request list of all (gloabbly) deleted elmtIds;
285    let deletedElmtIds: number[] = [];
286    this.getDeletedElemtIds(deletedElmtIds);
287
288    // see which elmtIds are managed by this View
289    // and clean up all book keeping for them
290    this.purgeDeletedElmtIds(deletedElmtIds);
291    this.UpdateElement(elmtId);
292
293    // remove elemtId from dirtDescendantElementIds.
294    this.dirtDescendantElementIds_.delete(elmtId);
295  }
296
297  public updateStateVarsOfChildByElmtId(elmtId, params: Object) : void {
298    stateMgmtConsole.debug(`ViewPU('${this.constructor.name}', ${this.id__()}).updateChildViewById(${elmtId}) - start`);
299
300    if (elmtId<0) {
301      stateMgmtConsole.warn(`ViewPU('${this.constructor.name}', ${this.id__()}).updateChildViewById(${elmtId}) - invalid elmtId - internal error!`);
302      return ;
303    }
304    let child : ViewPU = this.getChildById(elmtId);
305    if (!child) {
306      stateMgmtConsole.warn(`ViewPU('${this.constructor.name}', ${this.id__()}).updateChildViewById(${elmtId}) - no child with this elmtId - internal error!`);
307      return;
308    }
309    child.updateStateVars(params);
310    stateMgmtConsole.debug(`ViewPU('${this.constructor.name}', ${this.id__()}).updateChildViewById(${elmtId}) - end`);
311  }
312
313  // implements IMultiPropertiesChangeSubscriber
314  viewPropertyHasChanged(varName: PropertyInfo, dependentElmtIds: Set<number>): void {
315    stateMgmtTrace.scopedTrace(() => {
316      stateMgmtConsole.debug(`${this.constructor.name}: viewPropertyHasChanged property '${varName}'. View needs ${dependentElmtIds.size ? 'update' : 'no update'}.`);
317      this.syncInstanceId();
318
319      if (dependentElmtIds.size && !this.isFirstRender()) {
320        if (!this.dirtDescendantElementIds_.size) {
321          // mark Composedelement dirty when first elmtIds are added
322          // do not need to do this every time
323          this.markNeedUpdate();
324        }
325        stateMgmtConsole.debug(`${this.constructor.name}: viewPropertyHasChanged property '${varName}': elmtIds affected by value change [${Array.from(dependentElmtIds).toString()}].`)
326        const union: Set<number> = new Set<number>([...this.dirtDescendantElementIds_, ...dependentElmtIds]);
327        this.dirtDescendantElementIds_ = union;
328        stateMgmtConsole.debug(`${this.constructor.name}: viewPropertyHasChanged property '${varName}': all elmtIds need update [${Array.from(this.dirtDescendantElementIds_).toString()}].`)
329      }
330
331      let cb = this.watchedProps.get(varName)
332      if (cb) {
333        stateMgmtConsole.debug(`   .. calling @Watch function`);
334        cb.call(this, varName);
335      }
336
337      this.restoreInstanceId();
338    }, "ViewPU.viewPropertyHasChanged", this.constructor.name, varName, dependentElmtIds.size);
339  }
340
341  /**
342   * Function to be called from the constructor of the sub component
343   * to register a @Watch varibale
344   * @param propStr name of the variable. Note from @Provide and @Consume this is
345   *      the variable name and not the alias!
346   * @param callback application defined member function of sub-class
347   */
348  protected declareWatch(propStr: string, callback: (propName: string) => void): void {
349    this.watchedProps.set(propStr, callback);
350  }
351
352  /**
353   * This View @Provide's a variable under given name
354   * Call this function from the constructor of the sub class
355   * @param providedPropName either the variable name or the alias defined as
356   *        decorator param
357   * @param store the backing store object for this variable (not the get/set variable!)
358   */
359  protected addProvidedVar<T>(providedPropName: string, store: ObservedPropertyAbstractPU<T>) {
360    if (this.providedVars_.has(providedPropName)) {
361      throw new ReferenceError(`${this.constructor.name}: duplicate @Provide property with name ${providedPropName}.
362      Property with this name is provided by one of the ancestor Views already.`);
363    }
364    this.providedVars_.set(providedPropName, store);
365  }
366
367  /**
368   * Method for the sub-class to call from its constructor for resolving
369   *       a @Consume variable and initializing its backing store
370   *       with the yncedPropertyTwoWay<T> object created from the
371   *       @Provide variable's backing store.
372   * @param providedPropName the name of the @Provide'd variable.
373   *     This is either the @Consume decortor parameter, or variable name.
374   * @param consumeVarName the @Consume variable name (not the
375   *            @Consume decortor parameter)
376   * @returns initiaizing value of the @Consume backing store
377   */
378  protected initializeConsume<T>(providedPropName: string,
379    consumeVarName: string): ObservedPropertyAbstractPU<T> {
380    let providedVarStore = this.providedVars_.get(providedPropName);
381    if (providedVarStore === undefined) {
382      throw new ReferenceError(`${this.constructor.name}: missing @Provide property with name ${providedPropName}.
383     Fail to resolve @Consume(${providedPropName}).`);
384    }
385
386    return providedVarStore.createSync(
387      <T>(source: ObservedPropertyAbstract<T>) => (source instanceof ObservedPropertySimple)
388        ? new SynchedPropertySimpleTwoWayPU<T>(source, this, consumeVarName)
389        : new SynchedPropertyObjectTwoWayPU<T>(source, this, consumeVarName)) as ObservedPropertyAbstractPU<T>;
390  }
391
392
393  /**
394   * given the elmtid of a child or child of child within this custom component
395   * remember this component needs a partial update
396   * @param elmtId
397   */
398  public markElemenDirtyById(elmtId: number): void {
399    // TODO ace-ets2bundle, framework, compilated apps need to update together
400    // this function will be removed after a short transiition periode
401    stateMgmtConsole.error(`markElemenDirtyById no longer supported.
402        Please update your ace-ets2bundle and recompile your application!`);
403  }
404
405  /**
406   * For each recorded dirty Element in this custom component
407   * run its update function
408   *
409   */
410  public updateDirtyElements() {
411    do {
412        stateMgmtConsole.debug(`View ${this.constructor.name} elmtId ${this.id__()}:  updateDirtyElements: sorted dirty elmtIds: ${JSON.stringify(Array.from(this.dirtDescendantElementIds_).sort(ViewPU.compareNumber))}, starting ....`);
413
414        // request list of all (gloabbly) deleteelmtIds;
415        let deletedElmtIds: number[] = [];
416        this.getDeletedElemtIds(deletedElmtIds);
417
418        // see which elmtIds are managed by this View
419        // and clean up all book keeping for them
420        this.purgeDeletedElmtIds(deletedElmtIds);
421
422        // process all elmtIds marked as needing update in ascending order.
423        // ascending order ensures parent nodes will be updated before their children
424        // prior cleanup ensure no already deleted Elements have their update func executed
425        Array.from(this.dirtDescendantElementIds_).sort(ViewPU.compareNumber).forEach(elmtId => {
426            this.UpdateElement(elmtId);
427            this.dirtDescendantElementIds_.delete(elmtId);
428        });
429    } while(this.dirtDescendantElementIds_.size);
430  }
431
432  //  given a list elementIds removes these from state variables dependency list and from elmtId -> updateFunc map
433  purgeDeletedElmtIds(rmElmtIds: number[]) {
434    if (rmElmtIds.length == 0) {
435      return;
436    }
437
438    stateMgmtConsole.debug(`View ${this.constructor.name} elmtId ${this.id__()}.purgeDeletedElmtIds -  start.`);
439
440    // rmElmtIds is the array of ElemntIds that
441    let removedElmtIds: number[] = [];
442    rmElmtIds.forEach((elmtId: number) => {
443      // remove entry from Map elmtId -> update function
444      if (this.updateFuncByElmtId.delete(elmtId)) {
445
446        // for each state var, remove dependent elmtId (if present)
447        // purgeVariableDependenciesOnElmtId needs to be generated by the compiler
448        this.purgeVariableDependenciesOnElmtId(elmtId);
449
450        // keep track of elmtId that has been de-registered
451        removedElmtIds.push(elmtId);
452      }
453    });
454
455    this.deletedElmtIdsHaveBeenPurged(removedElmtIds);
456    stateMgmtConsole.debug(`View ${this.constructor.name} elmtId ${this.id__()}.purgeDeletedElmtIds: removed elemntIds  ${JSON.stringify(removedElmtIds)}.`);
457    stateMgmtConsole.debug(`   ... remaining update funcs for elmtIds ${JSON.stringify([... this.updateFuncByElmtId.keys()])} .`);
458  }
459
460  // the current executed update function
461  public observeComponentCreation(compilerAssignedUpdateFunc: UpdateFunc): void {
462    const elmtId = ViewStackProcessor.AllocateNewElmetIdForNextComponent();
463    stateMgmtConsole.debug(`${this.constructor.name}[${this.id__()}]: First render for elmtId ${elmtId} start ....`);
464    compilerAssignedUpdateFunc(elmtId, /* is first rneder */ true);
465
466    this.updateFuncByElmtId.set(elmtId, compilerAssignedUpdateFunc);
467    stateMgmtConsole.debug(`${this.constructor.name}[${this.id__()}]: First render for elmtId ${elmtId} - DONE.`);
468  }
469
470  // performs the update on a branch within if() { branch } else if (..) { branch } else { branch }
471  public ifElseBranchUpdateFunction(branchId : number, branchfunc : () => void ) : void {
472    const oldBranchid : number = If.getBranchId();
473
474    if (branchId == oldBranchid) {
475      stateMgmtConsole.log(`${this.constructor.name}[${this.id__()}] IfElse branch unchanged, no work to do.`);
476      return;
477    }
478
479    If.branchId(branchId);
480    branchfunc();
481  }
482
483   /**
484    Partial updates for ForEach.
485    * @param elmtId ID of element.
486    * @param itemArray Array of items for use of itemGenFunc.
487    * @param itemGenFunc Item generation function to generate new elements. If index parameter is
488    *                    given set itemGenFuncUsesIndex to true.
489    * @param idGenFunc   ID generation function to generate unique ID for each element. If index parameter is
490    *                    given set idGenFuncUsesIndex to true.
491    * @param itemGenFuncUsesIndex itemGenFunc optional index parameter is given or not.
492    * @param idGenFuncUsesIndex idGenFunc optional index parameter is given or not.
493    */
494  public forEachUpdateFunction(elmtId : number,
495    itemArray: Array<any>,
496    itemGenFunc: (item: any, index?: number) => void,
497    idGenFunc?: (item: any, index?: number) => string,
498    itemGenFuncUsesIndex: boolean = false,
499    idGenFuncUsesIndex: boolean = false) : void {
500
501    stateMgmtConsole.debug(`${this.constructor.name}[${this.id__()}]: forEachUpdateFunction `);
502
503    if (itemArray === null || itemArray === undefined) {
504      stateMgmtConsole.error(`ForEach input array is null or undefined error.`);
505      return;
506    }
507
508    if (itemGenFunc === null || itemGenFunc === undefined) {
509      stateMgmtConsole.error(`Error: Item generation function not defined in forEach function.`);
510      return;
511    }
512
513    if (idGenFunc === undefined) {
514      stateMgmtConsole.debug(`${this.constructor.name}[${this.id__()}]: providing default id gen function `);
515      idGenFuncUsesIndex = true;
516      // catch possible error caused by Stringify and re-throw an Error with a meaningful (!) error message
517      idGenFunc = (item: any, index : number) => {
518        try {
519          return `${index}__${JSON.stringify(item)}`;
520        } catch(e) {
521          throw new Error (`${this.constructor.name}[${this.id__()}]: ForEach id ${elmtId}: use of default id generator function not possble on provided data structure. Need to specify id generator function (ForEach 3rd parameter).`)
522        }
523      }
524    }
525
526    let diffIndexArray = []; // New indexes compared to old one.
527    let newIdArray = [];
528    let idDuplicates = [];
529    const arr = itemArray; // just to trigger a 'get' onto the array
530
531    // ID gen is with index.
532    if (idGenFuncUsesIndex) {
533      stateMgmtConsole.debug(`ID Gen with index parameter or with default id gen func`);
534      // Create array of new ids.
535      arr.forEach((item, indx) => {
536        newIdArray.push(idGenFunc(item, indx));
537      });
538    }
539    else {
540      // Create array of new ids.
541      stateMgmtConsole.debug(`ID Gen without index parameter`);
542      arr.forEach((item, index) => {
543        newIdArray.push(`${itemGenFuncUsesIndex ? index + '_':''}` + idGenFunc(item));
544      });
545    }
546
547    // Set new array on C++ side.
548    // C++ returns array of indexes of newly added array items.
549    // these are indexes in new child list.
550    ForEach.setIdArray(elmtId, newIdArray, diffIndexArray, idDuplicates);
551
552    // Its error if there are duplicate IDs.
553    if (idDuplicates.length > 0) {
554      idDuplicates.forEach((indx) => {
555        stateMgmtConsole.error(
556          `Error: ${newIdArray[indx]} generated for ${indx}${indx < 4 ? indx == 2 ? "nd" : "rd" : "th"} array item ${arr[indx]}.`);
557      });
558      stateMgmtConsole.error(`Ids generated by the ForEach id gen function must be unique, error.`);
559    }
560
561    stateMgmtConsole.debug(
562      `${this.constructor.name}[${this.id__()}]: diff indexes ${JSON.stringify(diffIndexArray)} . `);
563
564    // Item gen is with index.
565    stateMgmtConsole.debug(`Item Gen ${itemGenFuncUsesIndex ? 'with' : "without"} index`);
566    // Create new elements if any.
567    diffIndexArray.forEach((indx) => {
568      ForEach.createNewChildStart(newIdArray[indx], this);
569      if (itemGenFuncUsesIndex) {
570        itemGenFunc(arr[indx], indx);
571      } else {
572        itemGenFunc(arr[indx]);
573      }
574      ForEach.createNewChildFinish(newIdArray[indx], this);
575    });
576  }
577
578  /**
579     * CreateStorageLink and CreateStorageLinkPU are used by the implementation of @StorageLink and
580     * @LocalStotrageLink in full update and partial update solution respectively.
581     * These are not part of the public AppStorage API , apps should not use.
582     * @param storagePropName - key in LocalStorage
583     * @param defaultValue - value to use when creating a new prop in the LocalStotage
584     * @param owningView - the View/ViewPU owning the @StorageLink/@LocalStorageLink variable
585     * @param viewVariableName -  @StorageLink/@LocalStorageLink variable name
586     * @returns SynchedPropertySimple/ObjectTwoWay/PU
587     */
588  public createStorageLink<T>(storagePropName: string, defaultValue: T, viewVariableName: string): ObservedPropertyAbstractPU<T> {
589    return AppStorage.__CreateSync<T>(storagePropName, defaultValue,
590      <T>(source: ObservedPropertyAbstract<T>) => (source === undefined)
591        ? undefined
592        : (source instanceof ObservedPropertySimple)
593          ? new SynchedPropertySimpleTwoWayPU<T>(source, this, viewVariableName)
594          : new SynchedPropertyObjectTwoWayPU<T>(source, this, viewVariableName)
595    ) as ObservedPropertyAbstractPU<T>;
596  }
597
598  public createStorageProp<T>(storagePropName: string, defaultValue: T, viewVariableName: string): ObservedPropertyAbstractPU<T> {
599    return AppStorage.__CreateSync<T>(storagePropName, defaultValue,
600      <T>(source: ObservedPropertyAbstract<T>) => (source === undefined)
601        ? undefined
602        : (source instanceof ObservedPropertySimple)
603          ? new SynchedPropertySimpleOneWayPU<T>(source, this, viewVariableName)
604          : new SynchedPropertyObjectOneWayPU<T>(source, this, viewVariableName)
605    ) as ObservedPropertyAbstractPU<T>;
606  }
607
608  public createLocalStorageLink<T>(storagePropName: string, defaultValue: T,
609    viewVariableName: string): ObservedPropertyAbstractPU<T> {
610    return this.localStorage_.__createSync<T>(storagePropName, defaultValue,
611      <T>(source: ObservedPropertyAbstract<T>) => (source === undefined)
612        ? undefined
613        : (source instanceof ObservedPropertySimple)
614          ? new SynchedPropertySimpleTwoWayPU<T>(source, this, viewVariableName)
615          : new SynchedPropertyObjectTwoWayPU<T>(source, this, viewVariableName)
616    ) as ObservedPropertyAbstractPU<T>;
617  }
618
619  public createLocalStorageProp<T>(storagePropName: string, defaultValue: T,
620    viewVariableName: string): ObservedPropertyAbstractPU<T> {
621    return this.localStorage_.__createSync<T>(storagePropName, defaultValue,
622      <T>(source: ObservedPropertyAbstract<T>) => (source === undefined)
623        ? undefined
624        : (source instanceof ObservedPropertySimple)
625          ? new SynchedPropertySimpleOneWayPU<T>(source, this, viewVariableName)
626          : new SynchedPropertyObjectOneWayPU<T>(source, this, viewVariableName)
627    ) as ObservedPropertyAbstractPU<T>;
628  }
629}
630