1/* 2 * Copyright (c) 2021-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 16/** 17 * View (for full update) 18 * 19 * all definitions in this file are framework internal 20 */ 21 22type ProvidedVarsMap = Map<string, ObservedPropertyAbstract<any>>; 23 24// Nativeview 25// implemented in C++ for release 26// and in utest/view_native_mock.ts for testing 27abstract class View extends NativeViewFullUpdate implements 28 IMultiPropertiesChangeSubscriber, IMultiPropertiesReadSubscriber { 29 30 private id_: number; 31 private propsUsedForRender: Set<string> = new Set<string>(); 32 private isRenderingInProgress: boolean = false; 33 34 private watchedProps: Map<string, (propName: string) => void> = new Map<string, (propName: string) => void>(); 35 36 // @Provide'd variables by this class and its ancestors 37 protected providedVars_: ProvidedVarsMap; 38 39 // my LocalStorge instance, shared with ancestor Views. 40 // create a default instance on demand if none is initialized 41 protected localStoragebackStore_: LocalStorage = undefined; 42 protected get localStorage_() { 43 if (!this.localStoragebackStore_) { 44 stateMgmtConsole.info(`${this.constructor.name} is accessing LocalStorage without being provided an instance. Creating a default instance.`); 45 this.localStoragebackStore_ = new LocalStorage({ /* emty */ }); 46 } 47 return this.localStoragebackStore_; 48 } 49 protected set localStorage_(instance: LocalStorage) { 50 if (!instance) { 51 // setting to undefined not allowed 52 return; 53 } 54 if (this.localStoragebackStore_) { 55 stateMgmtConsole.error(`${this.constructor.name} is setting LocalStorage instance twice`); 56 } 57 this.localStoragebackStore_ = instance; 58 } 59 60 /** 61 * Create a View 62 * 63 * 1. option: top level View, specify 64 * - compilerAssignedUniqueChildId must specify 65 * - parent=undefined 66 * - localStorage must provide if @LocalSTorageLink/Prop variables are used 67 * in this View or descendant Views. 68 * 69 * 2. option: not a top level View 70 * - compilerAssignedUniqueChildId must specify 71 * - parent must specify 72 * - localStorage do not specify, will inherit from parent View. 73 * 74 * @param compilerAssignedUniqueChildId Tw 75 * @param parent 76 * @param localStorage 77 */ 78 79 constructor(compilerAssignedUniqueChildId: string, parent: View, localStorage?: LocalStorage) { 80 super(compilerAssignedUniqueChildId, parent); 81 this.id_ = SubscriberManager.MakeId(); 82 this.providedVars_ = parent ? new Map(parent.providedVars_) 83 : new Map<string, ObservedPropertyAbstract<any>>(); 84 85 this.localStoragebackStore_ = undefined; 86 if (parent) { 87 // this View is not a top-level View 88 stateMgmtConsole.debug(`${this.constructor.name} constructor: Using LocalStorage instance of the parent View.`); 89 this.setCardId(parent.getCardId()); 90 this.localStorage_ = parent.localStorage_; 91 } else if (localStorage) { 92 this.localStorage_ = localStorage; 93 stateMgmtConsole.debug(`${this.constructor.name} constructor: Using LocalStorage instance provided via @Entry.`); 94 } 95 96 SubscriberManager.Add(this); 97 stateMgmtConsole.debug(`${this.constructor.name}: constructor done`); 98 } 99 100 // globally unique id, this is different from compilerAssignedUniqueChildId! 101 id__(): number { 102 return this.id_; 103 } 104 105 // temporary function, do not use, it will be removed soon! 106 // prupsoe is to allow eDSL transpiler to fix a bug that 107 // relies on this method 108 id() { 109 return this.id__(); 110 } 111 112 // inform the subscribed property 113 // that the View and thereby all properties 114 // are about to be deleted 115 abstract aboutToBeDeleted(): void; 116 117 abstract updateWithValueParams(params: Object): void; 118 119 propertyHasChanged(info?: PropertyInfo): void { 120 if (info) { 121 // need to sync container instanceId to switch instanceId in C++ side. 122 this.syncInstanceId(); 123 if (this.propsUsedForRender.has(info)) { 124 stateMgmtConsole.debug(`${this.constructor.name}: propertyHasChanged ['${info || 'unknowm'}']. View needs update`); 125 this.markNeedUpdate(); 126 } else { 127 stateMgmtConsole.debug(`${this.constructor.name}: propertyHasChanged ['${info || 'unknowm'}']. View does NOT need update`); 128 } 129 let cb = this.watchedProps.get(info); 130 if (cb) { 131 stateMgmtConsole.debug(`${this.constructor.name}: propertyHasChanged ['${info || 'unknowm'}']. calling @Watch function`); 132 cb.call(this, info); 133 } 134 this.restoreInstanceId(); 135 } // if info avail. 136 } 137 138 propertyRead(info?: PropertyInfo): void { 139 stateMgmtConsole.debug(`${this.constructor.name}: propertyRead ['${info || 'unknowm'}'].`); 140 if (info && (info !== 'unknown') && this.isRenderingInProgress) { 141 this.propsUsedForRender.add(info); 142 } 143 } 144 145 146 // for test purposes 147 public propertiesNeededToRender(): Set<string> { 148 return this.propsUsedForRender; 149 } 150 151 public aboutToRender(): void { 152 stateMgmtConsole.debug(`${this.constructor.name}: aboutToRender`); 153 // reset 154 this.propsUsedForRender = new Set<string>(); 155 this.isRenderingInProgress = true; 156 } 157 158 public aboutToContinueRender(): void { 159 // do not reset 160 this.isRenderingInProgress = true; 161 } 162 163 public onRenderDone(): void { 164 this.isRenderingInProgress = false; 165 stateMgmtConsole.debug(`${this.constructor.name}: onRenderDone: render performed get access to these properties: ${JSON.stringify(Array.from(this.propsUsedForRender))}.`); 166 } 167 168 169 /** 170 * Function to be called from the constructor of the sub component 171 * to register a @Watch varibale 172 * @param propStr name of the variable. Note from @Provide and @Consume this is 173 * the variable name and not the alias! 174 * @param callback application defined member function of sub-class 175 */ 176 protected declareWatch(propStr: string, callback: (propName: string) => void): void { 177 this.watchedProps.set(propStr, callback); 178 } 179 180 /** 181 * This View @Provide's a variable under given name 182 * Call this function from the constructor of the sub class 183 * @param providedPropName either the variable name or the alias defined as 184 * decorator param 185 * @param store the backing store object for this variable (not the get/set variable!) 186 */ 187 protected addProvidedVar<T>(providedPropName: string, store: ObservedPropertyAbstract<T>) { 188 if (this.providedVars_.has(providedPropName)) { 189 throw new ReferenceError(`${this.constructor.name}: duplicate @Provide property with name ${providedPropName}. 190 Property with this name is provided by one of the ancestor Views already.`); 191 } 192 this.providedVars_.set(providedPropName, store); 193 } 194 195 /** 196 * Method for the sub-class to call from its constructor for resolving 197 * a @Consume variable and initializing its backing store 198 * with the yncedPropertyTwoWay<T> object created from the 199 * @Provide variable's backing store. 200 * @param providedPropName the name of the @Provide'd variable. 201 * This is either the @Consume decortor parameter, or variable name. 202 * @param consumeVarName the @Consume variable name (not the 203 * @Consume decortor parameter) 204 * @returns initiaizing value of the @Consume backing store 205 */ 206 protected initializeConsume<T>(providedPropName: string, 207 consumeVarName: string): ObservedPropertyAbstract<T> { 208 let providedVarStore = this.providedVars_.get(providedPropName); 209 if (providedVarStore === undefined) { 210 throw new ReferenceError(`${this.constructor.name}: missing @Provide property with name ${providedPropName}. 211 Fail to resolve @Consume(${providedPropName}).`); 212 } 213 214 return providedVarStore.createLink(this, consumeVarName); 215 } 216} 217