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 16class SynchedPropertyObjectOneWayPU<C extends Object> 17 extends ObservedPropertyObjectAbstractPU<C> 18 implements ISinglePropertyChangeSubscriber<C>, IMultiPropertiesChangeSubscriber { 19 20 private wrappedValue_: C; 21 private source_: ObservedPropertyAbstract<C>; 22 23 constructor(source: ObservedPropertyAbstract<C> | C, 24 owningChildView: IPropertySubscriber, 25 thisPropertyName: PropertyInfo) { 26 super(owningChildView, thisPropertyName); 27 28 if (source && (typeof (source) === "object") && ("notifyHasChanged" in source) && ("subscribeMe" in source)) { 29 // code path for @(Local)StorageProp 30 this.source_ = source as ObservedPropertyAbstract<C>; 31 // subscribe to receive value change updates from LocalStorage source property 32 this.source_.subscribeMe(this); 33 } else { 34 // code path for @Prop 35 if (!ObservedObject.IsObservedObject(source)) { 36 stateMgmtConsole.warn(`@Prop ${this.info()} Provided source object's class 37 lacks @Observed class decorator. Object property changes will not be observed.`); 38 } 39 40 this.source_ = new ObservedPropertyObjectPU<C>(source as C, this, thisPropertyName); 41 } 42 43 // deep copy source Object and wrap it 44 this.setWrapperValue(this.source_.get()); 45 stateMgmtConsole.debug(`SynchedPropertyObjectOneWayPU[${this.id__()}, '${this.info() || "unknown"}']: constructor ready with wrappedValue '${JSON.stringify(this.wrappedValue_)}'.`); 46 } 47 48 /* 49 like a destructor, need to call this before deleting 50 the property. 51 */ 52 aboutToBeDeleted() { 53 if (this.source_) { 54 this.source_.unlinkSuscriber(this.id__()); 55 this.source_ = undefined; 56 } 57 super.aboutToBeDeleted(); 58 } 59 60 61 // this object is subscriber to this.source_ 62 // when source notifies a property change, copy its value to local backing store 63 // the guard for newValue being an Object is needed because also property changes of wrappedValue_ 64 // are notified via this function. We ignore those, these are handled correctly by propertyHasChanged 65 public hasChanged(newValue: C): void { 66 if (typeof newValue == "object") { 67 stateMgmtConsole.debug(`SynchedPropertyObjectOneWayPU[${this.id__()}, '${this.info() || "unknown"}']: hasChanged: newValue '${JSON.stringify(newValue)}'.`); 68 this.setWrapperValue(newValue); 69 this.notifyHasChanged(ObservedObject.GetRawObject(this.wrappedValue_)); 70 } 71 } 72 73 public propertyHasChanged(propName : string) : void { 74 stateMgmtConsole.debug(`SynchedPropertyObjectOneWayPU[${this.id__()}, '${this.info() || "unknown"}']: propertyHasChanged '${propName}'.`); 75 this.notifyHasChanged(ObservedObject.GetRawObject(this.wrappedValue_)); 76 } 77 78 public getUnmonitored(): C { 79 stateMgmtConsole.debug(`SynchedPropertyObjectOneWayPU[${this.id__()}, '${this.info() || "unknown"}']: getUnmonitored returns '${JSON.stringify(this.wrappedValue_)}'.`); 80 // unmonitored get access , no call to notifyPropertyRead ! 81 return this.wrappedValue_; 82 } 83 84 // get 'read through` from the ObservedObject 85 public get(): C { 86 stateMgmtConsole.debug(`SynchedPropertyObjectOneWayPU[${this.id__()}, '${this.info() || "unknown"}']: get returning ${JSON.stringify(this.wrappedValue_)}.`) 87 this.notifyPropertyRead(); 88 return this.wrappedValue_; 89 } 90 91 // assignment to local variable in the form of this.aProp = <object value> 92 // set 'writes through` to the ObservedObject 93 public set(newValue: C): void { 94 if (this.wrappedValue_ == newValue) { 95 stateMgmtConsole.debug(`SynchedPropertyObjectOneWayPU[${this.id__()}IP, '${this.info() || "unknown"}']: set with unchanged value '${JSON.stringify(newValue)}'- ignoring.`); 96 return; 97 } 98 99 stateMgmtConsole.debug(`SynchedPropertyObjectOneWayPU[${this.id__()}, '${this.info() || "unknown"}']: set to newValue: '${JSON.stringify(newValue)}'.`); 100 if (!ObservedObject.IsObservedObject(newValue)) { 101 stateMgmtConsole.warn(`@Prop ${this.info()} Set: Provided new object's class 102 lacks @Observed class decorator. Object property changes will not be observed.`); 103 } 104 105 this.setWrapperValue(newValue); 106 this.notifyHasChanged(this.wrappedValue_); 107 } 108 109 public reset(sourceChangedValue: C): void { 110 stateMgmtConsole.debug(`SynchedPropertyObjectOneWayPU[${this.id__()}, '${this.info() || "unknown"}']: reset from '${JSON.stringify(this.wrappedValue_)}' to '${JSON.stringify(sourceChangedValue)}'.`); 111 // if set causes an actual change, then, ObservedPropertyObject source_ will call hasChanged 112 this.source_.set(sourceChangedValue); 113 } 114 115 private setWrapperValue(value: C): void { 116 let rawValue = ObservedObject.GetRawObject(value); 117 if (rawValue instanceof Array) { 118 this.wrappedValue_ = ObservedObject.createNew([ ...rawValue ], this); 119 } else { 120 this.wrappedValue_ = ObservedObject.createNew({ ...rawValue }, this); 121 } 122 } 123} 124