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 16/** 17 * SynchedPropertyNestedObjectPU 18 * implementation of @ObjectLink decorated variables 19 * 20 * all definitions in this file are framework internal 21 * 22 */ 23class SynchedPropertyNestedObjectPU<C extends Object> 24 extends ObservedPropertyAbstractPU<C> 25 implements ObservedObjectEventsPUReceiver<C> { 26 27 private obsObject_: C = undefined; 28 29 private staticWatchId?: number; 30 31 /** 32 * Construct a Property of a su component that links to a variable of parent view that holds an ObservedObject 33 * example 34 * this.b.$a with b of type PC and a of type C, or 35 * this.$b[5] with this.b of type PC and array item b[5] of type C; 36 * 37 * @param subscribeMe 38 * @param propName 39 */ 40 constructor(obsObject: C, 41 owningChildView: IPropertySubscriber, propertyName: PropertyInfo) { 42 super(owningChildView, propertyName); 43 this.createSourceDependency(obsObject); 44 this.setValueInternal(obsObject); 45 this.setDecoratorInfo("@ObjectLink"); 46 } 47 48 /* 49 like a destructor, need to call this before deleting 50 the property. 51 */ 52 aboutToBeDeleted() { 53 // unregister from the ObservedObject 54 ObservedObject.removeOwningProperty(this.obsObject_, this); 55 super.aboutToBeDeleted(); 56 } 57 58 public getUnmonitored(): C { 59 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: getUnmonitored.`); 60 // unmonitored get access , no call to notifyPropertyRead ! 61 return this.obsObject_; 62 } 63 64 // get 'read through` from the ObservedProperty 65 public get(): C { 66 stateMgmtProfiler.begin('SynchedPropertyNestedObjectPU.get'); 67 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: get`) 68 this.recordPropertyDependentUpdate(); 69 if (this.shouldInstallTrackedObjectReadCb) { 70 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: get: @Track optimised mode. Will install read cb func if value is an object`); 71 ObservedObject.registerPropertyReadCb(this.obsObject_, this.onOptimisedObjectPropertyRead, this); 72 } else { 73 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: get: compatibility mode. `); 74 } 75 stateMgmtProfiler.end(); 76 return this.obsObject_; 77 } 78 79 // parent ViewPU rerender, runs update lambda with child ViewPU that contains a @ObjectLink 80 // calls ViewPU.updateStateVarsByElmtId, calls updateStateVars in application class, calls this 'set' function 81 public set(newValue: C): void { 82 if (this.obsObject_ === newValue) { 83 stateMgmtConsole.debug(`SynchedPropertyNestedObjectPU[${this.id__()}IP, '${this.info() || 'unknown'}']: set @ObjectLink with unchanged value - nothing to do.`); 84 return; 85 } 86 87 stateMgmtConsole.propertyAccess(`${this.debugInfo()}: set: value about to change.`); 88 const oldValue = this.obsObject_; 89 if (this.setValueInternal(newValue)) { 90 this.createSourceDependency(newValue); 91 // notify value change to subscribing View 92 TrackedObject.notifyObjectValueAssignment(/* old value */ oldValue, /* new value */ this.obsObject_, 93 this.notifyPropertyHasChangedPU, 94 this.notifyTrackedObjectPropertyHasChanged, this); 95 } 96 } 97 98 protected onOptimisedObjectPropertyRead(readObservedObject: C, readPropertyName: string, isTracked: boolean): void { 99 stateMgmtProfiler.begin('SynchedPropertyNestedObjectPU.onOptimisedObjectPropertyRead'); 100 const renderingElmtId = this.getRenderingElmtId(); 101 if (renderingElmtId >= 0) { 102 if (!isTracked) { 103 stateMgmtConsole.applicationError(`${this.debugInfo()}: onOptimisedObjectPropertyRead read NOT TRACKED property '${readPropertyName}' during rendering!`); 104 throw new Error(`Illegal usage of not @Track'ed property '${readPropertyName}' on UI!`); 105 } else { 106 stateMgmtConsole.debug(`${this.debugInfo()}: onOptimisedObjectPropertyRead: ObservedObject property '@Track ${readPropertyName}' read.`); 107 if (this.getUnmonitored() === readObservedObject) { 108 this.recordTrackObjectPropertyDependencyForElmtId(renderingElmtId, readPropertyName) 109 } 110 } 111 } 112 stateMgmtProfiler.end(); 113 } 114 115 private createSourceDependency(sourceObject: C): void { 116 if (ObservedObject.IsObservedObject(sourceObject)) { 117 stateMgmtConsole.debug(`${this.debugInfo()} createSourceDependency: create dependency on source ObservedObject ...`); 118 const fake = (sourceObject as Object)[TrackedObject.___TRACKED_OPTI_ASSIGNMENT_FAKE_OBJLINK_PROPERTY]; 119 } 120 } 121 122 private setValueInternal(newValue: C): boolean { 123 if (!this.checkIsObject(newValue)) { 124 return false; 125 } 126 127 if (this.obsObject_ != undefined) { 128 if (this.obsObject_ instanceof SubscribableAbstract) { 129 // unregister from SubscribableAbstract object 130 (this.obsObject_ as SubscribableAbstract).removeOwningProperty(this); 131 } else if (ObservedObject.IsObservedObject(this.obsObject_)) { 132 // unregister from the ObservedObject 133 ObservedObject.removeOwningProperty(this.obsObject_, this); 134 135 // make sure the ObservedObject no longer has a read callback function 136 // assigned to it 137 ObservedObject.unregisterPropertyReadCb(this.obsObject_); 138 // for interop 139 } else if (InteropConfigureStateMgmt.instance.needsInterop() && this.staticWatchId && typeof this.obsObject_ === 'object' && 140 'removeWatchSubscriber' in this.obsObject_ && typeof this.obsObject_.removeWatchSubscriber === 'function') { 141 this.obsObject_.removeWatchSubscriber(this.staticWatchId); 142 } 143 } 144 145 this.obsObject_ = newValue; 146 147 if (this.obsObject_ != undefined) { 148 if (this.obsObject_ instanceof SubscribableAbstract) { 149 // register to SubscribableAbstract object 150 (this.obsObject_ as SubscribableAbstract).addOwningProperty(this); 151 } else if (ObservedObject.IsObservedObject(this.obsObject_)) { 152 // register to the ObservedObject 153 ObservedObject.addOwningProperty(this.obsObject_, this); 154 this.shouldInstallTrackedObjectReadCb = TrackedObject.needsPropertyReadCb(this.obsObject_); 155 // for interop 156 } else if (InteropConfigureStateMgmt.instance.needsInterop() && typeof this.obsObject_ === 'object' && 157 'addWatchSubscriber' in this.obsObject_ && typeof this.obsObject_.addWatchSubscriber === 'function') { 158 const callback = () => { 159 this.notifyPropertyHasChangedPU(); 160 }; 161 if (typeof InteropExtractorModule.createWatchFunc !== undefined && typeof InteropExtractorModule.createWatchFunc === 'function') { 162 this.staticWatchId = InteropExtractorModule.createWatchFunc(callback, this.obsObject_); 163 } 164 } else { 165 stateMgmtConsole.applicationWarn(`${this.debugInfo()}: set/init (method setValueInternal): assigned value is not 166 be decorated by @Observed. Value changes will not be observed and UI will not update.`); 167 } 168 } 169 return true; 170 } 171} 172 173/** backward compatibility after typo in classname fix */ 174class SynchedPropertyNesedObjectPU<C extends Object> extends SynchedPropertyNestedObjectPU<C> { 175 176}