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}