1/* 2 * Copyright (c) 2021 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 SubscriberManager { 17 constructor() { 18 this.subscriberById_ = new Map(); 19 this.nextFreeId_ = 0; 20 aceConsole.debug("SubscriberManager has been created."); 21 } 22 static Get() { return SubscriberManager.INSTANCE_; } 23 has(id) { 24 return this.subscriberById_.has(id); 25 } 26 get(id) { 27 return this.subscriberById_.get(id); 28 } 29 delete(id) { 30 return this.subscriberById_.delete(id); 31 } 32 add(newSubsriber) { 33 if (this.has(newSubsriber.id())) { 34 return false; 35 } 36 this.subscriberById_.set(newSubsriber.id(), newSubsriber); 37 return true; 38 } 39 /** 40 * Method for testing purposes 41 * @returns number of subscribers 42 */ 43 numberOfSubscrbers() { 44 return this.subscriberById_.size; 45 } 46 /** 47 * for debug purposes dump all known subscriber's info to comsole 48 */ 49 dumpSubscriberInfo() { 50 aceConsole.debug("Dump of SubscriberManager +++ (sart)"); 51 for (let [id, subscriber] of this.subscriberById_) { 52 aceConsole.debug(`Id: ${id} -> ${subscriber['info'] ? subscriber['info']() : 'unknown'}`); 53 } 54 aceConsole.debug("Dump of SubscriberManager +++ (end)"); 55 } 56 MakeId() { 57 return this.nextFreeId_++; 58 } 59} 60SubscriberManager.INSTANCE_ = new SubscriberManager(); 61/** 62 * Abstract class that manages subscribing properties 63 * that implement the interfaces ISinglePropertyChangeSubscriber 64 * and/or IMultiPropertiesChangeSubscriber. Each using @State, @Link, etc 65 * decorated varibale in a component will make its own subscription. 66 * When the component is created the subscription is added, and when the 67 * component is deleted it unsubscribes. 68 * 69 * About lifecycle: It is legal use for two components with two @State 70 * decorated variables to share the same instance to a SubscribaleAbstract 71 * object. Each such decorated variable implementation makes its own 72 * subscription to the SubscribaleAbstract object. Hence, when both variables 73 * have unsubscribed the SubscribaleAbstract may do its own de-initilization., 74 * e.g. release held external resources. 75 * 76 * How to extend: 77 * A subclass manages the get and set to one or several properties on its own. 78 * The subclass needs to notify all relevant value changes to the framework for the 79 * UI to be updated. Notification should only be given for class properties that 80 * are used to generate the UI. 81 * 82 * A subclass must call super() in its constructor to let this base class 83 * initialize itself. 84 * 85 * A subclass must call 'notifyPropertyHasChanged' after the relevant property 86 * has changes. The framework will notify all dependent components to re-render. 87 * 88 * A sub-class may overwrite the 'addOwningProperty' function to add own 89 * functionality, but it must call super.addowningOwningProperty(..). E.g. 90 * the sub-class could connect to external resources upon the first subscriber. 91 * 92 * A sub-class may also overwrite the 'removeOwningProperty' function or 93 * 'removeOwningPropertyById' function to add own functionality, 94 * but it must call super.removeOwningProperty(..). 95 * E.g. the sub-class could release held external resources upon loosing the 96 * last subscriber. 97 * 98 */ 99class SubscribaleAbstract { 100 /** 101 * make sure the call super from subclass constructor! 102 */ 103 constructor() { 104 this.owningProperties_ = new Set(); 105 aceConsole.debug(`SubscribaleAbstract: construcstor done`); 106 } 107 /** 108 * A subsclass must call this function whenever one of its properties has 109 * changed that is used to construct the UI. 110 * @param propName name of the change property 111 * @param newValue the property value after the change 112 */ 113 notifyPropertyHasChanged(propName, newValue) { 114 aceConsole.debug(`SubscribaleAbstract: notifyPropertyHasChanged '${propName}'.`); 115 var registry = SubscriberManager.Get(); 116 this.owningProperties_.forEach((subscribedId) => { 117 var owningProperty = registry.get(subscribedId); 118 if (owningProperty) { 119 if ('hasChanged' in owningProperty) { 120 owningProperty.hasChanged(newValue); 121 } 122 if ('propertyHasChanged' in owningProperty) { 123 owningProperty.propertyHasChanged(propName); 124 } 125 } 126 else { 127 aceConsole.error(`SubscribaleAbstract: notifyHasChanged: unknown subscriber.'${subscribedId}' error!.`); 128 } 129 }); 130 } 131 /** 132 * Method used by the framework to add subscribing decorated variables 133 * Subclass may overwrite this function but must call the function of the base 134 * class from its own implementation. 135 * @param subscriber new subscriber that implements ISinglePropertyChangeSubscriber 136 * and/or IMultiPropertiesChangeSubscriber interfaces 137 */ 138 addOwningProperty(subscriber) { 139 aceConsole.debug(`SubscribaleAbstract: addOwningProperty: subscriber '${subscriber.id()}'.`); 140 this.owningProperties_.add(subscriber.id()); 141 } 142 /** 143 * Method used by the framework to ubsubscribing decorated variables 144 * Subclass may overwrite this function but must call the function of the base 145 * class from its own implementation. 146 * @param subscriber subscriber that implements ISinglePropertyChangeSubscriber 147 * and/or IMultiPropertiesChangeSubscriber interfaces 148 */ 149 removeOwningProperty(property) { 150 return this.removeOwningPropertyById(property.id()); 151 } 152 removeOwningPropertyById(subscriberId) { 153 aceConsole.debug(`SubscribaleAbstract: removeOwningProperty '${subscriberId}'.`); 154 this.owningProperties_.delete(subscriberId); 155 } 156} 157/** 158* @Observed Decorator function, use 159* @Observed class ClassA { ... } 160* when defining ClassA 161* 162* Can also be used to create a new Object and wrap it in 163* ObservedObject by calling 164* obsObj = Observed(ClassA)(params to ClassA constructor) 165* 166* Note this works only for classes, not for ClassA[] 167* Also does not work for classes with genetics it seems 168* In that case use factory function 169* obsObj = ObservedObject.createNew<ClassA[]>([]) 170*/ 171function Observed(target) { 172 var original = target; 173 // the new constructor behaviour 174 var f = function (...args) { 175 aceConsole.log(`New ${original.name}, gets wrapped inside ObservableObject proxy.`); 176 return new ObservedObject(new original(...args), undefined); 177 }; 178 Object.setPrototypeOf(f, Object.getPrototypeOf(original)); 179 // return new constructor (will override original) 180 return f; 181} 182class SubscribableHandler { 183 constructor(owningProperty) { 184 this.owningProperties_ = new Set(); 185 if (owningProperty) { 186 this.addOwningProperty(owningProperty); 187 } 188 aceConsole.debug(`SubscribableHandler: construcstor done`); 189 } 190 addOwningProperty(subscriber) { 191 aceConsole.debug(`SubscribableHandler: addOwningProperty: subscriber '${subscriber.id()}'.`); 192 this.owningProperties_.add(subscriber.id()); 193 } 194 /* 195 the inverse function of createOneWaySync or createTwoWaySync 196 */ 197 removeOwningProperty(property) { 198 return this.removeOwningPropertyById(property.id()); 199 } 200 removeOwningPropertyById(subscriberId) { 201 aceConsole.debug(`SubscribableHandler: removeOwningProperty '${subscriberId}'.`); 202 this.owningProperties_.delete(subscriberId); 203 } 204 notifyPropertyHasChanged(propName, newValue) { 205 aceConsole.debug(`SubscribableHandler: notifyPropertyHasChanged '${propName}'.`); 206 var registry = SubscriberManager.Get(); 207 this.owningProperties_.forEach((subscribedId) => { 208 var owningProperty = registry.get(subscribedId); 209 if (owningProperty) { 210 if ('hasChanged' in owningProperty) { 211 owningProperty.hasChanged(newValue); 212 } 213 if ('propertyHasChanged' in owningProperty) { 214 owningProperty.propertyHasChanged(propName); 215 } 216 } 217 else { 218 aceConsole.error(`SubscribableHandler: notifyHasChanged: unknown subscriber.'${subscribedId}' error!.`); 219 } 220 }); 221 } 222 get(target, property) { 223 return (property === SubscribableHandler.IS_OBSERVED_OBJECT) ? true : 224 (property === SubscribableHandler.RAW_OBJECT) ? target : target[property]; 225 } 226 set(target, property, newValue) { 227 switch (property) { 228 case SubscribableHandler.SUBSCRIBE: 229 // assignment obsObj[SubscribableHandler.SUBSCRCRIBE] = subscriber 230 this.addOwningProperty(newValue); 231 return true; 232 break; 233 case SubscribableHandler.UNSUBSCRIBE: 234 // assignment obsObj[SubscribableHandler.UN_SUBSCRCRIBE] = subscriber 235 this.removeOwningProperty(newValue); 236 return true; 237 break; 238 default: 239 if (target[property] == newValue) { 240 return true; 241 } 242 aceConsole.log(`SubscribableHandler: set property '${property.toString()}' to new value'`); 243 target[property] = newValue; 244 this.notifyPropertyHasChanged(property.toString(), newValue); // FIXME PropertyKey.toString 245 return true; 246 break; 247 } 248 // unreachable 249 return false; 250 } 251} 252SubscribableHandler.IS_OBSERVED_OBJECT = Symbol("_____is_observed_object__"); 253SubscribableHandler.RAW_OBJECT = Symbol("_____raw_object__"); 254SubscribableHandler.SUBSCRIBE = Symbol("_____subscribe__"); 255SubscribableHandler.UNSUBSCRIBE = Symbol("_____unsubscribe__"); 256class ExtendableProxy { 257 constructor(obj, handler) { 258 return new Proxy(obj, handler); 259 } 260} 261class ObservedObject extends ExtendableProxy { 262 /** 263 * Factory function for ObservedObjects / 264 * wrapping of objects for proxying 265 * 266 * @param rawObject unproxied Object or ObservedObject 267 * @param objOwner owner of this Object to sign uop for propertyChange 268 * notifications 269 * @returns the rawObject if object is already an ObservedObject, 270 * otherwise the newly created ObservedObject 271 */ 272 static createNew(rawObject, owningProperty) { 273 if (ObservedObject.IsObservedObject(rawObject)) { 274 ObservedObject.addOwningProperty(rawObject, owningProperty); 275 return rawObject; 276 } 277 else { 278 return new ObservedObject(rawObject, owningProperty); 279 } 280 } 281 /* 282 Return the unproxied object 'inside' the ObservedObject / the ES6 Proxy 283 no set observation, no notification of changes! 284 Use with caution, do not store any references 285 */ 286 static GetRawObject(obj) { 287 return !ObservedObject.IsObservedObject(obj) ? obj : obj[SubscribableHandler.RAW_OBJECT]; 288 } 289 /** 290 * 291 * @param obj anything 292 * @returns true if the parameter is an Object wrpped with a ObservedObject 293 * Note: Since ES6 Proying is transparent, 'instance of' will not work. Use 294 * this static function instead. 295 */ 296 static IsObservedObject(obj) { 297 return obj ? (obj[SubscribableHandler.IS_OBSERVED_OBJECT] == true) : false; 298 } 299 static addOwningProperty(obj, subscriber) { 300 if (!ObservedObject.IsObservedObject(obj)) { 301 return false; 302 } 303 obj[SubscribableHandler.SUBSCRIBE] = subscriber; 304 return true; 305 } 306 static removeOwningProperty(obj, subscriber) { 307 if (!ObservedObject.IsObservedObject(obj)) { 308 return false; 309 } 310 obj[SubscribableHandler.UNSUBSCRIBE] = subscriber; 311 return true; 312 } 313 /** 314 * Create a new ObservableObject and subscribe its owner to propertyHasChanged 315 * ntifications 316 * @param obj raw Object, if obj is a ObservableOject throws an error 317 * @param objectOwner 318 */ 319 constructor(obj, objectOwningProperty) { 320 if (ObservedObject.IsObservedObject(obj)) { 321 throw new Error("Invalid constructor argument error: ObservableObject contructor called with an ObservedObject as parameer"); 322 } 323 let handler = new SubscribableHandler(objectOwningProperty); 324 super(obj, handler); 325 if (ObservedObject.IsObservedObject(obj)) { 326 aceConsole.error("ObservableOject constructor: INTERNAL ERROR: after jsObj is observedObject already"); 327 } 328 } // end of constructor 329} 330/* 331 332 Overview of the Observed Property class hiararchy 333 334 ObservedPropertyAbstract 335 |-- ObservedSimplePropertyAbstract - boolean, number, string 336 | |-- ObservedSimpleProperty - owns the property 337 | |-- SynchedSimplePropertyOneWay - one way sync from ObservedSimpleProperty 338 | | |--SynchedPropertySimpleOneWaySubscribing - one way sync 339 | | from ObservedSimpleProperty, return value of AppStorage.prop(..) 340 | |-- SynchedSimplePropertyTwoWay - two way sync with ObservedSimpleProperty 341 | 342 |-- ObservedObjectPropertyAbstract - Object proxied by ObservedObject 343 |-- ObservedObjectProperty - owns the property 344 |-- SynchedObjectPropertyTwoWay - two way sync with ObservedObjectProperty 345 346*/ 347/* 348 manage subscriptions to a property 349 managing the property is left to sub 350 classes 351 Extended by ObservedProperty, SyncedPropertyOneWay 352 and SyncedPropertyTwoWay 353*/ 354class ObservedPropertyAbstract { 355 constructor(subscribeMe, info) { 356 this.subscribers_ = new Set(); 357 this.id_ = SubscriberManager.Get().MakeId(); 358 SubscriberManager.Get().add(this); 359 if (subscribeMe) { 360 this.subscribers_.add(subscribeMe.id()); 361 } 362 if (info) { 363 this.info_ = info; 364 } 365 } 366 aboutToBeDeleted() { 367 SubscriberManager.Get().delete(this.id()); 368 } 369 id() { 370 return this.id_; 371 } 372 info() { 373 return this.info_; 374 } 375 subscribeMe(subscriber) { 376 aceConsole.debug(`ObservedPropertyAbstract[${this.id()}, '${this.info() || "unknown"}']: subscribeMe: Property new subscriber '${subscriber.id()}'`); 377 this.subscribers_.add(subscriber.id()); 378 } 379 /* 380 the inverse function of createOneWaySync or createTwoWaySync 381 */ 382 unlinkSuscriber(subscriberId) { 383 this.subscribers_.delete(subscriberId); 384 } 385 notifyHasChanged(newValue, isCrossWindow) { 386 aceConsole.debug(`ObservedPropertyAbstract[${this.id()}, '${this.info() || "unknown"}']: notifyHasChanged, notifying.`); 387 var registry = SubscriberManager.Get(); 388 this.subscribers_.forEach((subscribedId) => { 389 var subscriber = registry.get(subscribedId); 390 if (subscriber) { 391 if ('hasChanged' in subscriber) { 392 subscriber.hasChanged(newValue, isCrossWindow); 393 } 394 if ('propertyHasChanged' in subscriber) { 395 subscriber.propertyHasChanged(this.info_, isCrossWindow); 396 } 397 } 398 else { 399 aceConsole.error(`ObservedPropertyAbstract[${this.id()}, '${this.info() || "unknown"}']: notifyHasChanged: unknown subscriber ID '${subscribedId}' error!`); 400 } 401 }); 402 } 403 notifyPropertyRead() { 404 aceConsole.debug(`ObservedPropertyAbstract[${this.id()}, '${this.info() || "unknown"}']: propertyRead.`); 405 var registry = SubscriberManager.Get(); 406 this.subscribers_.forEach((subscribedId) => { 407 var subscriber = registry.get(subscribedId); 408 if (subscriber) { 409 if ('propertyRead' in subscriber) { 410 subscriber.propertyRead(this.info_); 411 } 412 } 413 }); 414 } 415 /* 416 return numebr of subscribers to this property 417 mostly useful for unit testin 418 */ 419 numberOfSubscrbers() { 420 return this.subscribers_.size; 421 } 422 /** 423 * factory function for concrete 'object' or 'simple' ObservedProperty object 424 * depending if value is Class object 425 * or simple type (boolean | number | string) 426 * @param value 427 * @param owningView 428 * @param thisPropertyName 429 * @returns either 430 */ 431 static CreateObservedObject(value, owningView, thisPropertyName) { 432 return (typeof value === "object") ? 433 new ObservedPropertyObject(value, owningView, thisPropertyName) 434 : new ObservedPropertySimple(value, owningView, thisPropertyName); 435 } 436} 437/** 438 * common bbase class of ObservedPropertyObject and 439 * SyncedObjectPropertyTwoWay 440 * adds the createObjectLink to the ObservedPropertyAbstract base 441 */ 442class ObservedPropertyObjectAbstract extends ObservedPropertyAbstract { 443 constructor(owningView, thisPropertyName) { 444 super(owningView, thisPropertyName); 445 } 446} 447/* 448 class that holds an actual property value of type T 449 uses its base class to manage subscribers to this 450 property. 451*/ 452class ObservedPropertyObject extends ObservedPropertyObjectAbstract { 453 constructor(value, owningView, propertyName) { 454 super(owningView, propertyName); 455 this.setValueInternal(value); 456 } 457 aboutToBeDeleted(unsubscribeMe) { 458 this.unsubscribeFromOwningProperty(); 459 if (unsubscribeMe) { 460 this.unlinkSuscriber(unsubscribeMe.id()); 461 } 462 super.aboutToBeDeleted(); 463 } 464 // FIXME 465 // notification from ObservedObject value one of its 466 // props has chnaged. Implies the ObservedProperty has changed 467 // Note: this function gets called when in this case: 468 // thisProp.aObsObj.aProp = 47 a object prop gets changed 469 // It is NOT called when 470 // thisProp.aObsObj = new ClassA 471 hasChanged(newValue, isCrossWindow) { 472 aceConsole.debug(`ObservedPropertyObject[${this.id()}, '${this.info() || "unknown"}']: hasChanged`); 473 this.notifyHasChanged(this.wrappedValue_, isCrossWindow); 474 } 475 unsubscribeFromOwningProperty() { 476 if (this.wrappedValue_) { 477 if (this.wrappedValue_ instanceof SubscribaleAbstract) { 478 this.wrappedValue_.removeOwningProperty(this); 479 } 480 else { 481 ObservedObject.removeOwningProperty(this.wrappedValue_, this); 482 } 483 } 484 } 485 /* 486 actually update this.wrappedValue_ 487 called needs to do value change check 488 and also notify with this.aboutToChange(); 489 */ 490 setValueInternal(newValue) { 491 if (typeof newValue !== 'object') { 492 aceConsole.debug(`ObservedPropertyObject[${this.id()}, '${this.info() || "unknown"}'] new value is NOT an object. Application error. Ignoring set.`); 493 return false; 494 } 495 this.unsubscribeFromOwningProperty(); 496 if (ObservedObject.IsObservedObject(newValue)) { 497 aceConsole.debug(`ObservedPropertyObject[${this.id()}, '${this.info() || "unknown"}'] new value is an ObservedObject already`); 498 ObservedObject.addOwningProperty(newValue, this); 499 this.wrappedValue_ = newValue; 500 } 501 else if (newValue instanceof SubscribaleAbstract) { 502 aceConsole.debug(`ObservedPropertyObject[${this.id()}, '${this.info() || "unknown"}'] new value is an SubscribaleAbstract, subscribiung to it.`); 503 this.wrappedValue_ = newValue; 504 this.wrappedValue_.addOwningProperty(this); 505 } 506 else { 507 aceConsole.debug(`ObservedPropertyObject[${this.id()}, '${this.info() || "unknown"}'] new value is an Object, needs to be wrapped in an ObservedObject.`); 508 this.wrappedValue_ = ObservedObject.createNew(newValue, this); 509 } 510 return true; 511 } 512 get() { 513 aceConsole.debug(`ObservedPropertyObject[${this.id()}, '${this.info() || "unknown"}']: get`); 514 this.notifyPropertyRead(); 515 return this.wrappedValue_; 516 } 517 set(newValue, isCrossWindow) { 518 if (this.wrappedValue_ == newValue) { 519 aceConsole.debug(`ObservedPropertyObject[${this.id()}, '${this.info() || "unknown"}']: set with unchanged value - ignoring.`); 520 return; 521 } 522 aceConsole.debug(`ObservedPropertyObject[${this.id()}, '${this.info() || "unknown"}']: set, changed`); 523 this.setValueInternal(newValue); 524 this.notifyHasChanged(newValue, isCrossWindow); 525 } 526 /** 527 * These functions are meant for use in connection with the App Stoage and 528 * business logic implementation. 529 * the created Link and Prop will update when 'this' property value 530 * changes. 531 */ 532 createLink(subscribeOwner, linkPropName, contentObserver) { 533 return new SynchedPropertyObjectTwoWay(this, subscribeOwner, linkPropName, contentObserver); 534 } 535 createProp(subscribeOwner, linkPropName, contentObserver) { 536 throw new Error("Creating a 'Prop' proerty is unsuppoeted for Object type prperty value."); 537 } 538} 539class ObservedPropertySimpleAbstract extends ObservedPropertyAbstract { 540 constructor(owningView, propertyName) { 541 super(owningView, propertyName); 542 } 543} 544/* 545 class that holds an actual property value of type T 546 uses its base class to manage subscribers to this 547 property. 548*/ 549class ObservedPropertySimple extends ObservedPropertySimpleAbstract { 550 constructor(value, owningView, propertyName) { 551 super(owningView, propertyName); 552 if (typeof value === "object") { 553 throw new SyntaxError("ObservedPropertySimple value must not be an object"); 554 } 555 this.setValueInternal(value); 556 } 557 aboutToBeDeleted(unsubscribeMe) { 558 if (unsubscribeMe) { 559 this.unlinkSuscriber(unsubscribeMe.id()); 560 } 561 super.aboutToBeDeleted(); 562 } 563 hasChanged(newValue, isCrossWindow) { 564 aceConsole.debug(`ObservedPropertySimple[${this.id()}, '${this.info() || "unknown"}']: hasChanged`); 565 this.notifyHasChanged(this.wrappedValue_, isCrossWindow); 566 } 567 /* 568 actually update this.wrappedValue_ 569 called needs to do value change check 570 and also notify with this.aboutToChange(); 571 */ 572 setValueInternal(newValue) { 573 aceConsole.debug(`ObservedPropertySimple[${this.id()}, '${this.info() || "unknown"}'] new value is of simple type`); 574 this.wrappedValue_ = newValue; 575 } 576 get() { 577 aceConsole.debug(`ObservedPropertySimple[${this.id()}, '${this.info() || "unknown"}']: get returns '${JSON.stringify(this.wrappedValue_)}' .`); 578 this.notifyPropertyRead(); 579 return this.wrappedValue_; 580 } 581 set(newValue, isCrossWindow) { 582 if (this.wrappedValue_ == newValue) { 583 aceConsole.debug(`ObservedPropertySimple[${this.id()}, '${this.info() || "unknown"}']: set with unchanged value - ignoring.`); 584 return; 585 } 586 aceConsole.debug(`ObservedPropertySimple[${this.id()}, '${this.info() || "unknown"}']: set, changed from '${JSON.stringify(this.wrappedValue_)}' to '${JSON.stringify(newValue)}.`); 587 this.setValueInternal(newValue); 588 this.notifyHasChanged(newValue, isCrossWindow); 589 } 590 /** 591 * These functions are meant for use in connection with the App Stoage and 592 * business logic implementation. 593 * the created Link and Prop will update when 'this' property value 594 * changes. 595 */ 596 createLink(subscribeOwner, linkPropName, contentObserver) { 597 return new SynchedPropertySimpleTwoWay(this, subscribeOwner, linkPropName, contentObserver); 598 } 599 createProp(subscribeOwner, linkPropName, contentObserver) { 600 return new SynchedPropertySimpleOneWaySubscribing(this, subscribeOwner, linkPropName, contentObserver); 601 } 602} 603class SynchedPropertyObjectTwoWay extends ObservedPropertyObjectAbstract { 604 constructor(linkSource, owningChildView, thisPropertyName, contentStoragelinkedParentProperty) { 605 super(owningChildView, thisPropertyName); 606 this.linkedParentProperty_ = linkSource; 607 // register to the parent property 608 this.linkedParentProperty_.subscribeMe(this); 609 if (contentStoragelinkedParentProperty) { 610 this.contentStoragelinkedParentProperty_ = contentStoragelinkedParentProperty; 611 } 612 // register to the ObservedObject 613 ObservedObject.addOwningProperty(this.getObject(), this); 614 } 615 /* 616 like a destructor, need to call this before deleting 617 the property. 618 */ 619 aboutToBeDeleted() { 620 // unregister from parent of this link 621 this.linkedParentProperty_.unlinkSuscriber(this.id()); 622 // unregister from the ObservedObject 623 ObservedObject.removeOwningProperty(this.getObject(), this); 624 super.aboutToBeDeleted(); 625 } 626 getObject() { 627 this.notifyPropertyRead(); 628 return this.linkedParentProperty_.get(); 629 } 630 setObject(newValue) { 631 this.linkedParentProperty_.set(newValue); 632 } 633 // this object is subscriber to ObservedObject 634 // will call this cb function when property has changed 635 hasChanged(newValue, isCrossWindow) { 636 aceConsole.debug(`SynchedPropertyObjectTwoWay[${this.id()}, '${this.info() || "unknown"}']: contained ObservedObject hasChanged'.`); 637 this.notifyHasChanged(this.getObject(), isCrossWindow); 638 } 639 // get 'read through` from the ObservedProperty 640 get() { 641 aceConsole.debug(`SynchedPropertyObjectTwoWay[${this.id()}, '${this.info() || "unknown"}']: get`); 642 if (this.contentStoragelinkedParentProperty_) { 643 return this.contentStoragelinkedParentProperty_.get(); 644 } 645 return this.getObject(); 646 } 647 // set 'writes through` to the ObservedProperty 648 set(newValue, isCrossWindow) { 649 if (this.contentStoragelinkedParentProperty_) { 650 this.contentStoragelinkedParentProperty_.set(newValue, isCrossWindow); 651 return; 652 } 653 if (this.getObject() == newValue) { 654 aceConsole.debug(`SynchedPropertyObjectTwoWay[${this.id()}IP, '${this.info() || "unknown"}']: set with unchanged value '${newValue}'- ignoring.`); 655 return; 656 } 657 aceConsole.debug(`SynchedPropertyObjectTwoWay[${this.id()}, '${this.info() || "unknown"}']: set to newValue: '${newValue}'.`); 658 ObservedObject.removeOwningProperty(this.getObject(), this); 659 this.setObject(newValue); 660 ObservedObject.addOwningProperty(this.getObject(), this); 661 this.notifyHasChanged(newValue, isCrossWindow); 662 } 663 /** 664 * These functions are meant for use in connection with the App Stoage and 665 * business logic implementation. 666 * the created Link and Prop will update when 'this' property value 667 * changes. 668 */ 669 createLink(subscribeOwner, linkPropName, contentStoragelinkedParentProperty) { 670 return new SynchedPropertyObjectTwoWay(this, subscribeOwner, linkPropName, contentStoragelinkedParentProperty); 671 } 672 createProp(subscribeOwner, linkPropName, contentStoragelinkedParentProperty) { 673 throw new Error("Creating a 'Prop' proerty is unsuppoeted for Object type prperty value."); 674 } 675} 676class SynchedPropertyNesedObject extends ObservedPropertyObjectAbstract { 677 /** 678 * Construct a Property of a su component that links to a variable of parent view that holds an ObservedObject 679 * example 680 * this.b.$a with b of type PC and a of type C, or 681 * this.$b[5] with this.b of type PC and array item b[5] of type C; 682 * 683 * @param subscribeMe 684 * @param propName 685 */ 686 constructor(obsObject, owningChildView, propertyName) { 687 super(owningChildView, propertyName); 688 this.obsObject_ = obsObject; 689 // register to the ObservedObject 690 ObservedObject.addOwningProperty(this.obsObject_, this); 691 } 692 /* 693 like a destructor, need to call this before deleting 694 the property. 695 */ 696 aboutToBeDeleted() { 697 // unregister from the ObservedObject 698 ObservedObject.removeOwningProperty(this.obsObject_, this); 699 super.aboutToBeDeleted(); 700 } 701 // this object is subscriber to ObservedObject 702 // will call this cb function when property has changed 703 hasChanged(newValue, isCrossWindow) { 704 aceConsole.debug(`SynchedPropertyNesedObject[${this.id()}, '${this.info() || "unknown"}']: contained ObservedObject hasChanged'.`); 705 this.notifyHasChanged(this.obsObject_, isCrossWindow); 706 } 707 // get 'read through` from the ObservedProperty 708 get() { 709 aceConsole.debug(`SynchedPropertyNesedObject[${this.id()}, '${this.info() || "unknown"}']: get`); 710 this.notifyPropertyRead(); 711 return this.obsObject_; 712 } 713 // set 'writes through` to the ObservedProperty 714 set(newValue, isCrossWindow) { 715 if (this.obsObject_ == newValue) { 716 aceConsole.debug(`SynchedPropertyNesedObject[${this.id()}IP, '${this.info() || "unknown"}']: set with unchanged value '${newValue}'- ignoring.`); 717 return; 718 } 719 aceConsole.debug(`SynchedPropertyNesedObject[${this.id()}, '${this.info() || "unknown"}']: set to newValue: '${newValue}'.`); 720 // unsubscribe from the old value ObservedObject 721 ObservedObject.removeOwningProperty(this.obsObject_, this); 722 this.obsObject_ = newValue; 723 // subscribe to the new value ObservedObject 724 ObservedObject.addOwningProperty(this.obsObject_, this); 725 // notify value change to subscribing View 726 this.notifyHasChanged(this.obsObject_, isCrossWindow); 727 } 728 /** 729 * These functions are meant for use in connection with the App Stoage and 730 * business logic implementation. 731 * the created Link and Prop will update when 'this' property value 732 * changes. 733 */ 734 createLink(subscribeOwner, linkPropName, contentObserver) { 735 throw new Error("Method not supported for property linking to a nested objects."); 736 } 737 createProp(subscribeOwner, linkPropName, contentObserver) { 738 throw new Error("Creating a 'Prop' proerty is unsuppoeted for Object type prperty value."); 739 } 740} 741class SynchedPropertySimpleOneWay extends ObservedPropertySimpleAbstract { 742 constructor(value, subscribeMe, info, contentObserver) { 743 super(subscribeMe, info); 744 // TODO prop is only supported for simple types 745 // add a test here that T is a simple type 746 this.wrappedValue_ = value; 747 if (contentObserver) { 748 this.contentStorageLinkedParentProperty_ = contentObserver; 749 } 750 } 751 /* 752 like a destructor, need to call this before deleting 753 the property. 754 */ 755 aboutToBeDeleted() { 756 super.aboutToBeDeleted(); 757 } 758 // get 'read through` from the ObservedProperty 759 get() { 760 aceConsole.debug(`SynchedPropertySimpleOneWay[${this.id()}, '${this.info() || "unknown"}']: get returns '${this.wrappedValue_}'`); 761 this.notifyPropertyRead(); 762 if (this.contentStorageLinkedParentProperty_) { 763 return this.contentStorageLinkedParentProperty_.get(); 764 } 765 return this.wrappedValue_; 766 } 767 set(newValue, isCrossWindow) { 768 if (this.contentStorageLinkedParentProperty_) { 769 this.contentStorageLinkedParentProperty_.set(newValue, isCrossWindow); 770 } 771 if (this.wrappedValue_ == newValue) { 772 aceConsole.debug(`SynchedPropertySimpleOneWay[${this.id()}, '${this.info() || "unknown"}']: set with unchanged value '${this.wrappedValue_}'- ignoring.`); 773 return; 774 } 775 aceConsole.debug(`SynchedPropertySimpleOneWay[${this.id()}, '${this.info() || "unknown"}']: set from '${this.wrappedValue_} to '${newValue}'.`); 776 this.wrappedValue_ = newValue; 777 this.notifyHasChanged(newValue, isCrossWindow); 778 } 779 /** 780 * These functions are meant for use in connection with the App Stoage and 781 * business logic implementation. 782 * the created Link and Prop will update when 'this' property value 783 * changes. 784 */ 785 createLink(subscribeOwner, linkPropName, contentObserver) { 786 throw new Error("Can not create a 'Link' from a 'Prop' property. "); 787 } 788 createProp(subscribeOwner, linkPropName, contentObserver) { 789 throw new Error("Method not supported, create a SynchedPropertySimpleOneWaySubscribing from, where to create a Prop."); 790 } 791} 792/* 793 This exrension of SynchedPropertySimpleOneWay needs to be used for AppStorage 794 because it needs to be notified about the source property changing 795 ( there is no re-render process as in Views to update the wrappedValue ) 796*/ 797class SynchedPropertySimpleOneWaySubscribing extends SynchedPropertySimpleOneWay { 798 constructor(linkedProperty, subscribeMe, info, contentObserver) { 799 super(linkedProperty.get(), subscribeMe, info, contentObserver); 800 this.linkedParentProperty_ = linkedProperty; 801 this.linkedParentProperty_.subscribeMe(this); 802 } 803 aboutToBeDeleted() { 804 // unregister from parent of this prop 805 this.linkedParentProperty_.unlinkSuscriber(this.id()); 806 super.aboutToBeDeleted(); 807 } 808 hasChanged(newValue, isCrossWindow) { 809 aceConsole.debug(`SynchedPropertySimpleOneWaySubscribing[${this.id()}, '${this.info() || "unknown"}']: source property hasChanged'.`); 810 this.set(newValue, isCrossWindow); 811 } 812 /** 813 * These functions are meant for use in connection with the App Stoage and 814 * business logic implementation. 815 * the created Link and Prop will update when 'this' property value 816 * changes. 817 */ 818 createLink(subscribeOwner, linkPropName) { 819 throw new Error("Can not create a 'Link' from a 'Prop' property. "); 820 } 821 createProp(subscribeOwner, propPropName, contentObserver) { 822 return new SynchedPropertySimpleOneWaySubscribing(this, subscribeOwner, propPropName, contentObserver); 823 } 824} 825class SynchedPropertySimpleTwoWay extends ObservedPropertySimpleAbstract { 826 constructor(source, owningView, owningViewPropNme, contentObserver) { 827 super(owningView, owningViewPropNme); 828 this.source_ = source; 829 this.source_.subscribeMe(this); 830 if (contentObserver) { 831 this.contentObserver_ = contentObserver; 832 } 833 } 834 /* 835 like a destructor, need to call this before deleting 836 the property. 837 */ 838 aboutToBeDeleted() { 839 this.source_.unlinkSuscriber(this.id()); 840 this.source_ = undefined; 841 super.aboutToBeDeleted(); 842 } 843 // this object is subscriber to SynchedPropertySimpleTwoWay 844 // will call this cb function when property has changed 845 // a set (newValue) is not done because get reads through for the source_ 846 hasChanged(newValue, isCrossWindow) { 847 aceConsole.debug(`SynchedPropertySimpleTwoWay[${this.id()}, '${this.info() || "unknown"}']: hasChanged to '${newValue}'.`); 848 this.notifyHasChanged(newValue, isCrossWindow); 849 } 850 // get 'read through` from the ObservedProperty 851 get() { 852 aceConsole.debug(`SynchedPropertySimpleTwoWay[${this.id()}IP, '${this.info() || "unknown"}']: get`); 853 this.notifyPropertyRead(); 854 if (this.contentObserver_) { 855 return this.contentObserver_.get(); 856 } 857 return this.source_.get(); 858 } 859 // set 'writes through` to the ObservedProperty 860 set(newValue, isCrossWindow) { 861 if (this.contentObserver_) { 862 this.contentObserver_.set(newValue, isCrossWindow); 863 return; 864 } 865 if (this.source_.get() == newValue) { 866 aceConsole.debug(`SynchedPropertySimpleTwoWay[${this.id()}IP, '${this.info() || "unknown"}']: set with unchanged value '${newValue}'- ignoring.`); 867 return; 868 } 869 // aceConsole.error(`SynchedPropertySimpleTwoWay[${this.id()}IP, '${this.info() || "unknown"}']: set to newValue: '${newValue}'.`); 870 // the source_ ObservedProeprty will call: this.hasChanged(newValue); 871 this.notifyHasChanged(newValue, isCrossWindow); 872 return this.source_.set(newValue); 873 } 874 /** 875 * These functions are meant for use in connection with the App Stoage and 876 * business logic implementation. 877 * the created Link and Prop will update when 'this' property value 878 * changes. 879 */ 880 createLink(subscribeOwner, linkPropName, contentObserver) { 881 return new SynchedPropertySimpleTwoWay(this, subscribeOwner, linkPropName, contentObserver); 882 } 883 createProp(subscribeOwner, propPropName, contentObserver) { 884 return new SynchedPropertySimpleOneWaySubscribing(this, subscribeOwner, propPropName, contentObserver); 885 } 886} 887class ContentStorage { 888 constructor() { 889 this.storage_ = new Map(); 890 } 891 has(propName) { 892 return this.storage_.has(propName); 893 } 894 get(key) { 895 var p = this.storage_.get(key); 896 return (p) ? p.get() : undefined; 897 } 898 set(propName, newValue) { 899 var p = this.storage_.get(propName); 900 if (p) { 901 p.set(newValue); 902 return true; 903 } 904 else { 905 return false; 906 } 907 } 908 setOrCreate(propName, newValue) { 909 var p = this.storage_.get(propName); 910 if (p) { 911 aceConsole.log(`ContentStorage.setOrCreate(${propName}, ${newValue}) update existing property`); 912 p.set(newValue); 913 } 914 else { 915 aceConsole.log(`ContentStorage.setOrCreate(${propName}, ${newValue}) create new entry and set value`); 916 const newProp = (typeof newValue === "object") ? 917 new ObservedPropertyObject(newValue, undefined, propName) 918 : new ObservedPropertySimple(newValue, undefined, propName); 919 this.storage_.set(propName, newProp); 920 } 921 } 922 delete(propName) { 923 var p = this.storage_.get(propName); 924 if (p) { 925 if (p.numberOfSubscrbers()) { 926 aceConsole.error(`Attempt to delete property ${propName} that has ${p.numberOfSubscrbers()} subscribers. Subscribers need to unsubscribe before prop deletion.`); 927 return false; 928 } 929 p.aboutToBeDeleted(); 930 this.storage_.delete(propName); 931 return true; 932 } 933 else { 934 aceConsole.warn(`Attempt to delete unknown property ${propName}.`); 935 return false; 936 } 937 } 938 keys() { 939 return this.storage_.keys(); 940 } 941 size() { 942 return this.storage_.size; 943 } 944 aboutToBeDeleted() { 945 return this.clear(); 946 } 947 numberOfSubscribersTo(propName) { 948 var p = this.storage_.get(propName); 949 if (p) { 950 return p.numberOfSubscrbers(); 951 } 952 return undefined; 953 } 954 subscribeToChangesOf(propName, subscriber) { 955 var p = this.storage_.get(propName); 956 if (p) { 957 p.subscribeMe(subscriber); 958 return true; 959 } 960 return false; 961 } 962 unsubscribeFromChangesOf(propName, subscriberId) { 963 var p = this.storage_.get(propName); 964 if (p) { 965 p.unlinkSuscriber(subscriberId); 966 return true; 967 } 968 return false; 969 } 970 isMutable(key) { 971 return true; 972 } 973 clear() { 974 for (let propName of this.keys()) { 975 var p = this.storage_.get(propName); 976 if (p.numberOfSubscrbers()) { 977 aceConsole.error(`ContentStorage.deleteAll: Attempt to delete property ${propName} that has ${p.numberOfSubscrbers()} subscribers. Subscribers need to unsubscribe before prop deletion.`); 978 return false; 979 } 980 } 981 for (let propName of this.keys()) { 982 var p = this.storage_.get(propName); 983 p.aboutToBeDeleted(); 984 } 985 aceConsole.debug(`ContentStorage.deleteAll: success`); 986 } 987 link(propName, linkUser) { 988 var p = this.storage_.get(propName); 989 return (p) ? p.createLink(linkUser, propName) : undefined; 990 } 991 setAndLink(propName, defaultValue, linkUser) { 992 var p = this.storage_.get(propName); 993 if (!p) { 994 this.setOrCreate(propName, defaultValue); 995 } 996 return this.link(propName, linkUser); 997 } 998 prop(propName, propUser) { 999 var p = this.storage_.get(propName); 1000 return (p) ? p.createProp(propUser, propName) : undefined; 1001 } 1002 setAndProp(propName, defaultValue, propUser) { 1003 var p = this.storage_.get(propName); 1004 if (!p) { 1005 if (typeof defaultValue === "boolean" || 1006 typeof defaultValue === "number" || typeof defaultValue === "string") { 1007 this.setOrCreate(propName, defaultValue); 1008 } 1009 else { 1010 return undefined; 1011 } 1012 } 1013 return this.prop(propName, propUser); 1014 } 1015} 1016 1017class AppStorage { 1018 constructor() { 1019 this.storage_ = new Map(); 1020 } 1021 // FIXME: Perhaps "GetInstance" would be better name for this 1022 // static Get(): AppStorage { return AppStorage.Instance_; } 1023 static GetOrCreate() { 1024 if (!AppStorage.Instance_) { 1025 AppStorage.Instance_ = new AppStorage(); 1026 } 1027 return AppStorage.Instance_; 1028 } 1029 static Link(key) { 1030 return AppStorage.GetOrCreate().link(key); 1031 } 1032 static SetAndLink(key, defaultValue) { 1033 return AppStorage.GetOrCreate().setAndLink(key, defaultValue); 1034 } 1035 static Prop(key) { 1036 return AppStorage.GetOrCreate().prop(key); 1037 } 1038 static SetAndProp(key, defaultValue) { 1039 return AppStorage.GetOrCreate().setAndProp(key, defaultValue); 1040 } 1041 static Has(key) { 1042 return AppStorage.GetOrCreate().has(key); 1043 } 1044 static Get(key) { 1045 return AppStorage.GetOrCreate().get(key); 1046 } 1047 static Set(key, newValue) { 1048 return AppStorage.GetOrCreate().set(key, newValue); 1049 } 1050 // FIXME(cvetan): No mechanism to create "immutable" properties 1051 static SetOrCreate(key, newValue) { 1052 AppStorage.GetOrCreate().setOrCreate(key, newValue); 1053 } 1054 static Delete(key) { 1055 return AppStorage.GetOrCreate().delete(key); 1056 } 1057 static Keys() { 1058 return AppStorage.GetOrCreate().keys(); 1059 } 1060 static Size() { 1061 return AppStorage.GetOrCreate().size(); 1062 } 1063 static Clear() { 1064 return AppStorage.GetOrCreate().clear(); 1065 } 1066 static AboutToBeDeleted() { 1067 AppStorage.GetOrCreate().aboutToBeDeleted(); 1068 } 1069 static NumberOfSubscribersTo(propName) { 1070 return AppStorage.GetOrCreate().numberOfSubscrbersTo(propName); 1071 } 1072 static SubscribeToChangesOf(propName, subscriber) { 1073 return AppStorage.GetOrCreate().subscribeToChangesOf(propName, subscriber); 1074 } 1075 static UnsubscribeFromChangesOf(propName, subscriberId) { 1076 return AppStorage.GetOrCreate().unsubscribeFromChangesOf(propName, subscriberId); 1077 } 1078 static IsMutable(key) { 1079 // FIXME(cvetan): No mechanism for immutable/mutable properties 1080 return true; 1081 } 1082 /** 1083 * App should call this method to order close down app storage before 1084 * terminating itself. 1085 * Before deleting a prop from app storage all its subscribers need to 1086 * unsubscribe from the property. 1087 * 1088 * @returns true if all properties could be removed from app storage 1089 */ 1090 aboutToBeDeleted() { 1091 return this.clear(); 1092 } 1093 get(propName) { 1094 var p = this.storage_.get(propName); 1095 return (p) ? p.get() : undefined; 1096 } 1097 set(propName, newValue) { 1098 var p = this.storage_.get(propName); 1099 if (p) { 1100 p.set(newValue); 1101 return true; 1102 } 1103 else { 1104 return false; 1105 } 1106 } 1107 setOrCreate(propName, newValue) { 1108 var p = this.storage_.get(propName); 1109 if (p) { 1110 aceConsole.log(`AppStorage.setOrCreate(${propName}, ${newValue}) update existing property`); 1111 p.set(newValue); 1112 } 1113 else { 1114 aceConsole.log(`AppStorage.setOrCreate(${propName}, ${newValue}) create new entry and set value`); 1115 const newProp = (typeof newValue === "object") ? 1116 new ObservedPropertyObject(newValue, undefined, propName) 1117 : new ObservedPropertySimple(newValue, undefined, propName); 1118 this.storage_.set(propName, newProp); 1119 } 1120 } 1121 has(propName) { 1122 aceConsole.log(`AppStorage.has(${propName})`); 1123 return this.storage_.has(propName); 1124 } 1125 /** 1126 * Delete poperty from AppStorage 1127 * must only use with caution: 1128 * Before deleting a prop from app storage all its subscribers need to 1129 * unsubscribe from the property. 1130 * This method fails and returns false if given property still has subscribers 1131 * Another reason for failing is unkmown property. 1132 * 1133 * @param propName 1134 * @returns false if method failed 1135 */ 1136 delete(propName) { 1137 var p = this.storage_.get(propName); 1138 if (p) { 1139 if (p.numberOfSubscrbers()) { 1140 aceConsole.error(`Attempt to delete property ${propName} that has ${p.numberOfSubscrbers()} subscribers. Subscribers need to unsubscribe before prop deletion.`); 1141 return false; 1142 } 1143 p.aboutToBeDeleted(); 1144 this.storage_.delete(propName); 1145 return true; 1146 } 1147 else { 1148 aceConsole.warn(`Attempt to delete unknown property ${propName}.`); 1149 return false; 1150 } 1151 } 1152 /** 1153 * delete all properties from the AppStorage 1154 * precondition is that there are no subscribers anymore 1155 * method returns false and deletes no poperties if there is any property 1156 * that still has subscribers 1157 */ 1158 clear() { 1159 for (let propName of this.keys()) { 1160 var p = this.storage_.get(propName); 1161 if (p.numberOfSubscrbers()) { 1162 aceConsole.error(`AppStorage.deleteAll: Attempt to delete property ${propName} that has ${p.numberOfSubscrbers()} subscribers. Subscribers need to unsubscribe before prop deletion.`); 1163 return false; 1164 } 1165 } 1166 for (let propName of this.keys()) { 1167 var p = this.storage_.get(propName); 1168 p.aboutToBeDeleted(); 1169 } 1170 aceConsole.debug(`AppStorage.deleteAll: success`); 1171 } 1172 keys() { 1173 return this.storage_.keys(); 1174 } 1175 size() { 1176 return this.storage_.size; 1177 } 1178 link(propName, linkUser, contentObserver) { 1179 var p = this.storage_.get(propName); 1180 return (p) ? p.createLink(linkUser, propName, contentObserver) : undefined; 1181 } 1182 setAndLink(propName, defaultValue, linkUser) { 1183 var p = this.storage_.get(propName); 1184 if (!p) { 1185 this.setOrCreate(propName, defaultValue); 1186 } 1187 if (linkUser && (linkUser instanceof View) && linkUser.getContentStorage()) { 1188 var contentObserver = linkUser.getContentStorage().setAndLink(propName, defaultValue, linkUser); 1189 return this.link(propName, linkUser, contentObserver); 1190 } 1191 return this.link(propName, linkUser); 1192 } 1193 prop(propName, propUser, contentObserver) { 1194 var p = this.storage_.get(propName); 1195 return (p) ? p.createProp(propUser, propName, contentObserver) : undefined; 1196 } 1197 setAndProp(propName, defaultValue, propUser) { 1198 var p = this.storage_.get(propName); 1199 if (!p) { 1200 if (typeof defaultValue === "boolean" || 1201 typeof defaultValue === "number" || typeof defaultValue === "string") { 1202 this.setOrCreate(propName, defaultValue); 1203 } 1204 else { 1205 return undefined; 1206 } 1207 } 1208 if (propUser && propUser.getContentStorage()) { 1209 var contentObserver = propUser.getContentStorage().setAndProp(propName, defaultValue, propUser); 1210 return this.prop(propName, propUser, contentObserver); 1211 } 1212 return this.prop(propName, propUser); 1213 } 1214 subscribeToChangesOf(propName, subscriber) { 1215 var p = this.storage_.get(propName); 1216 if (p) { 1217 p.subscribeMe(subscriber); 1218 return true; 1219 } 1220 return false; 1221 } 1222 unsubscribeFromChangesOf(propName, subscriberId) { 1223 var p = this.storage_.get(propName); 1224 if (p) { 1225 p.unlinkSuscriber(subscriberId); 1226 return true; 1227 } 1228 return false; 1229 } 1230 /* 1231 return number of subscribers to this property 1232 mostly useful for unit testin 1233 */ 1234 numberOfSubscrbersTo(propName) { 1235 var p = this.storage_.get(propName); 1236 if (p) { 1237 return p.numberOfSubscrbers(); 1238 } 1239 return undefined; 1240 } 1241 /* 1242 cross window notify 1243 */ 1244 crossWindowNotify(propName, newValue) { 1245 var p = this.storage_.get(propName); 1246 try { 1247 newValue = JSON.parse(newValue); 1248 } 1249 catch (error) { 1250 aceConsole.error(`PersistentStorage: convert for ${propName} has error: ` + error.toString()); 1251 } 1252 if (p) { 1253 aceConsole.debug(`crossWindowNotify(${propName}, ${newValue}) update existing property`); 1254 p.set(newValue, true); 1255 } 1256 else { 1257 aceConsole.debug(`crossWindowNotify(${propName}, ${newValue}) create new entry and set value`); 1258 const newProp = (typeof newValue === "object") ? 1259 new ObservedPropertyObject(newValue, undefined, propName) 1260 : new ObservedPropertySimple(newValue, undefined, propName); 1261 this.storage_.set(propName, newProp); 1262 } 1263 } 1264} 1265AppStorage.Instance_ = undefined;