• 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    stateMgmtConsole.debug(`${this.constructor.name}: viewPropertyHasChanged property '${varName}'. View needs ${dependentElmtIds.size ? 'update' : 'no update'}.`);
316    this.syncInstanceId();
317
318    if (dependentElmtIds.size && !this.isFirstRender()) {
319      if (!this.dirtDescendantElementIds_.size) {
320        // mark Composedelement dirty when first elmtIds are added
321        // do not need to do this every time
322        this.markNeedUpdate();
323      }
324      stateMgmtConsole.debug(`${this.constructor.name}: viewPropertyHasChanged property '${varName}': elmtIds affected by value change [${Array.from(dependentElmtIds).toString()}].`)
325      const union: Set<number> = new Set<number>([...this.dirtDescendantElementIds_, ...dependentElmtIds]);
326      this.dirtDescendantElementIds_ = union;
327      stateMgmtConsole.debug(`${this.constructor.name}: viewPropertyHasChanged property '${varName}': all elmtIds need update [${Array.from(this.dirtDescendantElementIds_).toString()}].`)
328    }
329
330    let cb = this.watchedProps.get(varName)
331    if (cb) {
332      stateMgmtConsole.debug(`   .. calling @Watch function`);
333      cb.call(this, varName);
334    }
335
336    this.restoreInstanceId();
337  }
338
339  /**
340   * Function to be called from the constructor of the sub component
341   * to register a @Watch varibale
342   * @param propStr name of the variable. Note from @Provide and @Consume this is
343   *      the variable name and not the alias!
344   * @param callback application defined member function of sub-class
345   */
346  protected declareWatch(propStr: string, callback: (propName: string) => void): void {
347    this.watchedProps.set(propStr, callback);
348  }
349
350  /**
351   * This View @Provide's a variable under given name
352   * Call this function from the constructor of the sub class
353   * @param providedPropName either the variable name or the alias defined as
354   *        decorator param
355   * @param store the backing store object for this variable (not the get/set variable!)
356   */
357  protected addProvidedVar<T>(providedPropName: string, store: ObservedPropertyAbstractPU<T>) {
358    if (this.providedVars_.has(providedPropName)) {
359      throw new ReferenceError(`${this.constructor.name}: duplicate @Provide property with name ${providedPropName}.
360      Property with this name is provided by one of the ancestor Views already.`);
361    }
362    this.providedVars_.set(providedPropName, store);
363  }
364
365  /**
366   * Method for the sub-class to call from its constructor for resolving
367   *       a @Consume variable and initializing its backing store
368   *       with the yncedPropertyTwoWay<T> object created from the
369   *       @Provide variable's backing store.
370   * @param providedPropName the name of the @Provide'd variable.
371   *     This is either the @Consume decortor parameter, or variable name.
372   * @param consumeVarName the @Consume variable name (not the
373   *            @Consume decortor parameter)
374   * @returns initiaizing value of the @Consume backing store
375   */
376  protected initializeConsume<T>(providedPropName: string,
377    consumeVarName: string): ObservedPropertyAbstractPU<T> {
378    let providedVarStore = this.providedVars_.get(providedPropName);
379    if (providedVarStore === undefined) {
380      throw new ReferenceError(`${this.constructor.name}: missing @Provide property with name ${providedPropName}.
381     Fail to resolve @Consume(${providedPropName}).`);
382    }
383
384    return providedVarStore.createSync(
385      <T>(source: ObservedPropertyAbstract<T>) => (source instanceof ObservedPropertySimple)
386        ? new SynchedPropertySimpleTwoWayPU<T>(source, this, consumeVarName)
387        : new SynchedPropertyObjectTwoWayPU<T>(source, this, consumeVarName)) as ObservedPropertyAbstractPU<T>;
388  }
389
390
391  /**
392   * given the elmtid of a child or child of child within this custom component
393   * remember this component needs a partial update
394   * @param elmtId
395   */
396  public markElemenDirtyById(elmtId: number): void {
397    // TODO ace-ets2bundle, framework, compilated apps need to update together
398    // this function will be removed after a short transiition periode
399    stateMgmtConsole.error(`markElemenDirtyById no longer supported.
400        Please update your ace-ets2bundle and recompile your application!`);
401  }
402
403  /**
404   * For each recorded dirty Element in this custom component
405   * run its update function
406   *
407   */
408  public updateDirtyElements() {
409    do {
410        stateMgmtConsole.debug(`View ${this.constructor.name} elmtId ${this.id__()}:  updateDirtyElements: sorted dirty elmtIds: ${JSON.stringify(Array.from(this.dirtDescendantElementIds_).sort(ViewPU.compareNumber))}, starting ....`);
411
412        // request list of all (gloabbly) deleteelmtIds;
413        let deletedElmtIds: number[] = [];
414        this.getDeletedElemtIds(deletedElmtIds);
415
416        // see which elmtIds are managed by this View
417        // and clean up all book keeping for them
418        this.purgeDeletedElmtIds(deletedElmtIds);
419
420        // process all elmtIds marked as needing update in ascending order.
421        // ascending order ensures parent nodes will be updated before their children
422        // prior cleanup ensure no already deleted Elements have their update func executed
423        Array.from(this.dirtDescendantElementIds_).sort(ViewPU.compareNumber).forEach(elmtId => {
424            this.UpdateElement(elmtId);
425            this.dirtDescendantElementIds_.delete(elmtId);
426        });
427    } while(this.dirtDescendantElementIds_.size);
428  }
429
430  //  given a list elementIds removes these from state variables dependency list and from elmtId -> updateFunc map
431  purgeDeletedElmtIds(rmElmtIds: number[]) {
432    if (rmElmtIds.length == 0) {
433      return;
434    }
435
436    stateMgmtConsole.debug(`View ${this.constructor.name} elmtId ${this.id__()}.purgeDeletedElmtIds -  start.`);
437
438    // rmElmtIds is the array of ElemntIds that
439    let removedElmtIds: number[] = [];
440    rmElmtIds.forEach((elmtId: number) => {
441      // remove entry from Map elmtId -> update function
442      if (this.updateFuncByElmtId.delete(elmtId)) {
443
444        // for each state var, remove dependent elmtId (if present)
445        // purgeVariableDependenciesOnElmtId needs to be generated by the compiler
446        this.purgeVariableDependenciesOnElmtId(elmtId);
447
448        // keep track of elmtId that has been de-registered
449        removedElmtIds.push(elmtId);
450      }
451    });
452
453    this.deletedElmtIdsHaveBeenPurged(removedElmtIds);
454    stateMgmtConsole.debug(`View ${this.constructor.name} elmtId ${this.id__()}.purgeDeletedElmtIds: removed elemntIds  ${JSON.stringify(removedElmtIds)}.`);
455    stateMgmtConsole.debug(`   ... remaining update funcs for elmtIds ${JSON.stringify([... this.updateFuncByElmtId.keys()])} .`);
456  }
457
458  // the current executed update function
459  public observeComponentCreation(compilerAssignedUpdateFunc: UpdateFunc): void {
460    const elmtId = ViewStackProcessor.AllocateNewElmetIdForNextComponent();
461    stateMgmtConsole.debug(`${this.constructor.name}[${this.id__()}]: First render for elmtId ${elmtId} start ....`);
462    compilerAssignedUpdateFunc(elmtId, /* is first rneder */ true);
463
464    this.updateFuncByElmtId.set(elmtId, compilerAssignedUpdateFunc);
465    stateMgmtConsole.debug(`${this.constructor.name}[${this.id__()}]: First render for elmtId ${elmtId} - DONE.`);
466  }
467
468  // performs the update on a branch within if() { branch } else if (..) { branch } else { branch }
469  public ifElseBranchUpdateFunction(branchId : number, branchfunc : () => void ) : void {
470    const oldBranchid : number = If.getBranchId();
471
472    if (branchId == oldBranchid) {
473      stateMgmtConsole.log(`${this.constructor.name}[${this.id__()}] IfElse branch unchanged, no work to do.`);
474      return;
475    }
476
477    If.branchId(branchId);
478    branchfunc();
479  }
480
481   /**
482    Partial updates for ForEach.
483    * @param elmtId ID of element.
484    * @param itemArray Array of items for use of itemGenFunc.
485    * @param itemGenFunc Item generation function to generate new elements. If index parameter is
486    *                    given set itemGenFuncUsesIndex to true.
487    * @param idGenFunc   ID generation function to generate unique ID for each element. If index parameter is
488    *                    given set idGenFuncUsesIndex to true.
489    * @param itemGenFuncUsesIndex itemGenFunc optional index parameter is given or not.
490    * @param idGenFuncUsesIndex idGenFunc optional index parameter is given or not.
491    */
492  public forEachUpdateFunction(elmtId : number,
493    itemArray: Array<any>,
494    itemGenFunc: (item: any, index?: number) => void,
495    idGenFunc?: (item: any, index?: number) => string,
496    itemGenFuncUsesIndex: boolean = false,
497    idGenFuncUsesIndex: boolean = false) : void {
498
499    stateMgmtConsole.debug(`${this.constructor.name}[${this.id__()}]: forEachUpdateFunction `);
500
501    if (itemArray === null || itemArray === undefined) {
502      stateMgmtConsole.error(`ForEach input array is null or undefined error.`);
503      return;
504    }
505
506    if (itemGenFunc === null || itemGenFunc === undefined) {
507      stateMgmtConsole.error(`Error: Item generation function not defined in forEach function.`);
508      return;
509    }
510
511    if (idGenFunc === undefined) {
512      stateMgmtConsole.debug(`${this.constructor.name}[${this.id__()}]: providing default id gen function `);
513      idGenFunc = (item: any, index : number) => `${index}__${JSON.stringify(item)}`;
514      idGenFuncUsesIndex = true;
515    }
516
517    let diffIndexArray = []; // New indexes compared to old one.
518    let newIdArray = [];
519    let idDuplicates = [];
520    const arr = itemArray; // just to trigger a 'get' onto the array
521
522    // ID gen is with index.
523    if (idGenFuncUsesIndex) {
524      stateMgmtConsole.debug(`ID Gen with index parameter or with default id gen func`);
525      // Create array of new ids.
526      arr.forEach((item, indx) => {
527        newIdArray.push(idGenFunc(item, indx));
528      });
529    }
530    else {
531      // Create array of new ids.
532      stateMgmtConsole.debug(`ID Gen without index parameter`);
533      arr.forEach((item, index) => {
534        newIdArray.push(`${itemGenFuncUsesIndex ? index + '_':''}` + idGenFunc(item));
535      });
536    }
537
538    // Set new array on C++ side.
539    // C++ returns array of indexes of newly added array items.
540    // these are indexes in new child list.
541    ForEach.setIdArray(elmtId, newIdArray, diffIndexArray, idDuplicates);
542
543    // Its error if there are duplicate IDs.
544    if (idDuplicates.length > 0) {
545      idDuplicates.forEach((indx) => {
546        stateMgmtConsole.error(
547          `Error: ${newIdArray[indx]} generated for ${indx}${indx < 4 ? indx == 2 ? "nd" : "rd" : "th"} array item ${arr[indx]}.`);
548      });
549      stateMgmtConsole.error(`Ids generated by the ForEach id gen function must be unique, error.`);
550    }
551
552    stateMgmtConsole.debug(
553      `${this.constructor.name}[${this.id__()}]: diff indexes ${JSON.stringify(diffIndexArray)} . `);
554
555    // Item gen is with index.
556    stateMgmtConsole.debug(`Item Gen ${itemGenFuncUsesIndex ? 'with' : "without"} index`);
557    // Create new elements if any.
558    diffIndexArray.forEach((indx) => {
559      ForEach.createNewChildStart(newIdArray[indx], this);
560      if (itemGenFuncUsesIndex) {
561        itemGenFunc(arr[indx], indx);
562      } else {
563        itemGenFunc(arr[indx]);
564      }
565      ForEach.createNewChildFinish(newIdArray[indx], this);
566    });
567  }
568
569  /**
570     * CreateStorageLink and CreateStorageLinkPU are used by the implementation of @StorageLink and
571     * @LocalStotrageLink in full update and partial update solution respectively.
572     * These are not part of the public AppStorage API , apps should not use.
573     * @param storagePropName - key in LocalStorage
574     * @param defaultValue - value to use when creating a new prop in the LocalStotage
575     * @param owningView - the View/ViewPU owning the @StorageLink/@LocalStorageLink variable
576     * @param viewVariableName -  @StorageLink/@LocalStorageLink variable name
577     * @returns SynchedPropertySimple/ObjectTwoWay/PU
578     */
579  public createStorageLink<T>(storagePropName: string, defaultValue: T, viewVariableName: string): ObservedPropertyAbstractPU<T> {
580    return AppStorage.__CreateSync<T>(storagePropName, defaultValue,
581      <T>(source: ObservedPropertyAbstract<T>) => (source === undefined)
582        ? undefined
583        : (source instanceof ObservedPropertySimple)
584          ? new SynchedPropertySimpleTwoWayPU<T>(source, this, viewVariableName)
585          : new SynchedPropertyObjectTwoWayPU<T>(source, this, viewVariableName)
586    ) as ObservedPropertyAbstractPU<T>;
587  }
588
589  public createStorageProp<T>(storagePropName: string, defaultValue: T, viewVariableName: string): ObservedPropertyAbstractPU<T> {
590    return AppStorage.__CreateSync<T>(storagePropName, defaultValue,
591      <T>(source: ObservedPropertyAbstract<T>) => (source === undefined)
592        ? undefined
593        : (source instanceof ObservedPropertySimple)
594          ? new SynchedPropertySimpleOneWayPU<T>(source, this, viewVariableName)
595          : new SynchedPropertyObjectOneWayPU<T>(source, this, viewVariableName)
596    ) as ObservedPropertyAbstractPU<T>;
597  }
598
599  public createLocalStorageLink<T>(storagePropName: string, defaultValue: T,
600    viewVariableName: string): ObservedPropertyAbstractPU<T> {
601    return this.localStorage_.__createSync<T>(storagePropName, defaultValue,
602      <T>(source: ObservedPropertyAbstract<T>) => (source === undefined)
603        ? undefined
604        : (source instanceof ObservedPropertySimple)
605          ? new SynchedPropertySimpleTwoWayPU<T>(source, this, viewVariableName)
606          : new SynchedPropertyObjectTwoWayPU<T>(source, this, viewVariableName)
607    ) as ObservedPropertyAbstractPU<T>;
608  }
609
610  public createLocalStorageProp<T>(storagePropName: string, defaultValue: T,
611    viewVariableName: string): ObservedPropertyAbstractPU<T> {
612    return this.localStorage_.__createSync<T>(storagePropName, defaultValue,
613      <T>(source: ObservedPropertyAbstract<T>) => (source === undefined)
614        ? undefined
615        : (source instanceof ObservedPropertySimple)
616          ? new SynchedPropertySimpleOneWayPU<T>(source, this, viewVariableName)
617          : new SynchedPropertyObjectOneWayPU<T>(source, this, viewVariableName)
618    ) as ObservedPropertyAbstractPU<T>;
619  }
620}