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