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 * 18 * SubscriableAbstract 19 * 20 * This class is part of the SDK. 21 * @since 9 22 * 23 * SubscriableAbstract is an abstract class that manages subscribers 24 * to value changes. These subscribers are the implementation of 25 * @State, @Link, @Provide, @Consume decorated variables inside the 26 * framework. Each using @State, @Link, etc., decorated varibale in 27 * a @Component will make its own subscription. When the component 28 * is created the subscription is added, and before the component 29 * is deleted it unsubscribes 30 * 31 * An application may extend SubscriableAbstract for a custom class 32 * that manages state data. @State, @Link, @Provide, @Consume 33 * decorated variables can hold an Object that is instance of 34 * SubscribaleAbstract. 35 * 36 * About lifecycle: It is legal use for two @Components with two @State 37 * decorated variables to share the same SubscribaleAbstract object. 38 * Each such decorated variable implementation makes its own 39 * subscription to the SubscribaleAbstract object. Hence, when both variables 40 * have unsubscribed the SubscribaleAbstract custom class may do its own 41 * de-initilialization, e.g. release held external resources. 42 * 43 * How to extend: 44 * A subclass manages the get and set to one or several properties on its own. 45 * The subclass needs to notify all relevant value changes to the framework for the 46 * UI to be updated. Notification should only be given for class properties that 47 * are used to generate the UI. 48 * 49 * A subclass must call super() in its constructor to let this base class 50 * initialize itself. 51 * 52 * A subclass must call 'notifyPropertyHasChanged*(' after the relevant property 53 * has changes. The framework will notify all dependent components to re-render. 54 * 55 * A sub-class may overwrite the 'addOwningProperty' function to add own 56 * functionality, but it must call super.addowningOwningProperty(..). E.g. 57 * the sub-class could connect to external resources upon the first subscriber. 58 * 59 * A sub-class may also overwrite the 'removeOwningProperty' function or 60 * 'removeOwningPropertyById' function to add own functionality, 61 * but it must call super.removeOwningProperty(..). 62 * E.g. the sub-class could release held external resources upon loosing the 63 * last subscriber. 64 * 65 */ 66 67abstract class SubscribaleAbstract { 68 69 // keeps track of all subscribing properties 70 private owningProperties_: Set<number>; 71 72 /** 73 * make sure to call super() from subclass constructor! 74 * 75 * @since 9 76 */ 77 constructor() { 78 this.owningProperties_ = new Set<number>(); 79 stateMgmtConsole.debug(`SubscribaleAbstract: construcstor done`); 80 } 81 82 /** 83 * A subsclass must call this function whenever one of its properties has 84 * changed that is used to construct the UI. 85 * @param propName name of the change property 86 * @param newValue the property value after the change 87 * 88 * @since 9 89 */ 90 protected notifyPropertyHasChanged(propName: string, newValue: any) { 91 stateMgmtConsole.debug(`SubscribaleAbstract: notifyPropertyHasChanged '${propName}'.`) 92 this.owningProperties_.forEach((subscribedId) => { 93 var owningProperty: IPropertySubscriber = SubscriberManager.Find(subscribedId) 94 if (owningProperty) { 95 if ('objectPropertyHasChangedPU' in owningProperty) { 96 // PU code path 97 (owningProperty as unknown as ObservedObjectEventsPUReceiver<any>).objectPropertyHasChangedPU(this, propName); 98 } 99 100 // FU code path 101 if ('hasChanged' in owningProperty) { 102 (owningProperty as ISinglePropertyChangeSubscriber<any>).hasChanged(newValue); 103 } 104 if ('propertyHasChanged' in owningProperty) { 105 (owningProperty as IMultiPropertiesChangeSubscriber).propertyHasChanged(propName); 106 } 107 } else { 108 stateMgmtConsole.error(`SubscribaleAbstract: notifyHasChanged: unknown subscriber.'${subscribedId}' error!.`); 109 } 110 }); 111 } 112 113 /** 114 * Method used by the framework to add subscribing decorated variables 115 * Subclass may overwrite this function but must call the function of the base 116 * class from its own implementation. 117 * @param subscriber new subscriber that implements ISinglePropertyChangeSubscriber 118 * and/or IMultiPropertiesChangeSubscriber interfaces 119 * 120 * @since 9 121 */ 122 123 public addOwningProperty(subscriber: IPropertySubscriber): void { 124 stateMgmtConsole.debug(`SubscribaleAbstract: addOwningProperty: subscriber '${subscriber.id__()}'.`) 125 this.owningProperties_.add(subscriber.id__()); 126 } 127 128 /** 129 * Method used by the framework to ubsubscribing decorated variables 130 * Subclass may overwrite this function but must call the function of the base 131 * class from its own implementation. 132 * @param subscriber subscriber that implements ISinglePropertyChangeSubscriber 133 * and/or IMultiPropertiesChangeSubscriber interfaces 134 * 135 * @since 9 136 */ 137 public removeOwningProperty(property: IPropertySubscriber): void { 138 return this.removeOwningPropertyById(property.id__()); 139 } 140 141 /** 142 * Same as @see removeOwningProperty() but by Subscriber id. 143 * @param subscriberId 144 * 145 * @since 9 146 */ 147 public removeOwningPropertyById(subscriberId: number): void { 148 stateMgmtConsole.debug(`SubscribaleAbstract: removeOwningProperty '${subscriberId}'.`) 149 this.owningProperties_.delete(subscriberId); 150 } 151} 152