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