UNPKG

bugcore

Version:

bugcore is a JavaScript library that provides a foundational architecture for object oriented JS

410 lines (349 loc) 14.8 kB
/* * Copyright (c) 2016 airbug Inc. http://airbug.com * * bugcore may be freely distributed under the MIT license. */ //------------------------------------------------------------------------------- // Annotations //------------------------------------------------------------------------------- //@Export('Observable') //@Require('ArgumentBug') //@Require('Class') //@Require('IObservable') //@Require('MultiListMap') //@Require('Observation') //@Require('ObservationPropagator') //@Require('Observer') //@Require('Set') //@Require('TypeUtil') //------------------------------------------------------------------------------- // Context //------------------------------------------------------------------------------- require('bugpack').context("*", function(bugpack) { //------------------------------------------------------------------------------- // BugPack //------------------------------------------------------------------------------- var ArgumentBug = bugpack.require('ArgumentBug'); var Class = bugpack.require('Class'); var IObservable = bugpack.require('IObservable'); var MultiListMap = bugpack.require('MultiListMap'); var Observation = bugpack.require('Observation'); var ObservationPropagator = bugpack.require('ObservationPropagator'); var Observer = bugpack.require('Observer'); var Set = bugpack.require('Set'); var TypeUtil = bugpack.require('TypeUtil'); //------------------------------------------------------------------------------- // Class //------------------------------------------------------------------------------- /** * @class * @extends {ObservationPropagator} * @implements {IObservable} */ var Observable = Class.extend(ObservationPropagator, { _name: "Observable", //------------------------------------------------------------------------------- // Constructor //------------------------------------------------------------------------------- /** * @constructs */ _constructor: function() { this._super(); //------------------------------------------------------------------------------- // Private Properties //------------------------------------------------------------------------------- /** * @private * @type {MultiListMap.<string, Observer>} */ this.changeTypeObserverMap = new MultiListMap(); }, //------------------------------------------------------------------------------- // Getters and Setters //------------------------------------------------------------------------------- /** * @return {MultiListMap.<string, Observer>} */ getChangeTypeObserverMap: function() { return this.changeTypeObserverMap; }, //------------------------------------------------------------------------------- // IObservable Implementation //------------------------------------------------------------------------------- /** * @param {(string | Array.<string>)} changeTypes * @param {(string | Array.<string>)} pathPatterns * @param {function(Observation)} observerFunction * @param {Object=} observerContext */ addObserver: function(changeTypes, pathPatterns, observerFunction, observerContext) { if (!TypeUtil.isArray(changeTypes) && !TypeUtil.isString(changeTypes)) { throw new ArgumentBug(ArgumentBug.ILLEGAL, "changeTypes", changeTypes, "parameter must either be an Array or a string"); } if (!TypeUtil.isArray(pathPatterns) && !TypeUtil.isString(pathPatterns)) { throw new ArgumentBug(ArgumentBug.ILLEGAL, "observationPathPatterns", pathPatterns, "parameter must either be an Array or a string"); } this.buildObservers(changeTypes, pathPatterns, observerFunction, observerContext); }, /** * @param {string} changeType * @param {string} pathPattern * @param {function(Observation)} observerFunction * @param {Object=} observerContext * @return {boolean} */ hasObserver: function(changeType, pathPattern, observerFunction, observerContext) { var changeTypeObserverList = this.changeTypeObserverMap.get(changeType); if (changeTypeObserverList) { var observer = this.factoryObserver(pathPattern, observerFunction, observerContext); return changeTypeObserverList.contains(observer); } return false; }, /** * @param {string} changeType * @param {string} observationPath * @return {boolean} */ isObserving: function(changeType, observationPath) { var changeTypeObserverList = this.changeTypeObserverMap.get(changeType); if (changeTypeObserverList) { var iterator = changeTypeObserverList.iterator(); while (iterator.hasNext()) { var observer = iterator.next(); if (observer.match(observationPath)) { return true; } } } return false; }, /** * @param {Change} change * @param {string=} observationPath */ notifyObservers: function(change, observationPath) { observationPath = observationPath || ""; var observation = this.factoryObservation(change, observationPath); observation.setChangingObservable(this); observation.setReportingObservable(this); this.propagateObservationToObservers(observation); this.propagateObservationToPropagators(observation); }, /** * @param {(string | Array.<string>)} changeTypes * @param {(string | Array.<string>)} pathPatterns * @param {function(Observation)} observerFunction * @param {?Object=} observerContext */ removeObserver: function(changeTypes, pathPatterns, observerFunction, observerContext) { if (TypeUtil.isArray(pathPatterns)) { var observerSet = this.factoryObservers(pathPatterns, observerFunction, observerContext); this.detachObserversFromTypes(changeTypes, observerSet); } else { var observer = this.factoryObserver(pathPatterns, observerFunction, observerContext); this.detachObserverFromTypes(changeTypes, observer); } }, /** * */ removeAllObservers: function() { this.changeTypeObserverMap.clear(); }, //------------------------------------------------------------------------------- // ObservationPropagator Methods //------------------------------------------------------------------------------- /** * @override * @param {Observation} change */ propagateObservation: function(change) { change.setReportingObservable(this); this.propagateObservationToObservers(change); this.propagateObservationToPropagators(change); }, //------------------------------------------------------------------------------- // Public Methods //------------------------------------------------------------------------------- /** * @param {string} changeType * @param {Observer} observer */ attachObserver: function(changeType, observer) { var changeTypeObserverList = this.changeTypeObserverMap.get(changeType); if (!changeTypeObserverList) { this.changeTypeObserverMap.put(changeType, observer); } else if (!changeTypeObserverList.contains(observer)) { changeTypeObserverList.add(observer); } }, /** * @param {(string | Array.<string>)} changeTypes * @param {Observer} observer */ attachObserverToTypes: function(changeTypes, observer) { var _this = this; if (TypeUtil.isArray(changeTypes)) { changeTypes.forEach(function(changeType) { _this.attachObserver(changeType, observer); }); } else { this.attachObserver(changeTypes, observer); } }, /** * @param {(string | Array.<string>)} changeTypes * @param {Set.<Observer>} observerSet */ attachObserversToTypes: function(changeTypes, observerSet) { var _this = this; observerSet.forEach(function(observer) { _this.attachObserverToTypes(changeTypes, observer); }); }, /** * */ detachAllObservers: function() { this.changeTypeObserverMap.clear(); }, /** * @param {string} changeType * @param {Observer} observer */ detachObserver: function(changeType, observer) { this.changeTypeObserverMap.removeKeyValuePair(changeType, observer); }, /** * @param {(string | Array.<string>)} changeTypes * @param {Observer} observer */ detachObserverFromTypes: function(changeTypes, observer) { var _this = this; if (TypeUtil.isArray(changeTypes)) { changeTypes.forEach(function(changeType) { _this.detachObserver(changeType, observer); }); } else { this.detachObserver(changeTypes, observer); } }, /** * @param {(string | Array.<string>)} changeTypes * @param {Set.<Observer>} observerSet */ detachObserversFromTypes: function(changeTypes, observerSet) { var _this = this; observerSet.forEach(function(observer) { _this.detachObserverFromTypes(changeTypes, observer); }); }, /** * */ unobserve: function() { this.removeObserver.apply(this, arguments); }, /** * */ unobserveAll: function() { this.detachAllObservers(); }, /** * */ observe: function() { this.addObserver.apply(this, arguments); }, //------------------------------------------------------------------------------- // Private Methods //------------------------------------------------------------------------------- /** * @private * @param {(string | Array.<string>)} changeTypes * @param {(string | Array.<string>)} pathPatterns * @param {function(Observation)} observerFunction * @param {Object=} observerContext */ buildObservers: function(changeTypes, pathPatterns, observerFunction, observerContext) { if (TypeUtil.isArray(pathPatterns)) { var observerSet = this.factoryObservers(pathPatterns, observerFunction, observerContext); this.attachObserversToTypes(changeTypes, observerSet); } else { var observer = this.factoryObserver(pathPatterns, observerFunction, observerContext); this.attachObserverToTypes(changeTypes, observer); } }, /** * @private * @param {Change} change * @param {string} observationPath * @return {Observation} */ factoryObservation: function(change, observationPath) { return new Observation(change, observationPath); }, /** * @private * @param {string} pathPattern * @param {function(Observation)} observerFunction * @param {Object=} observerContext */ factoryObserver: function(pathPattern, observerFunction, observerContext) { return new Observer(pathPattern, observerFunction, observerContext); }, /** * @private * @param {Array.<string>} pathPatterns * @param {function(Observation)} observerFunction * @param {Object=} observerContext */ factoryObservers: function(pathPatterns, observerFunction, observerContext) { var _this = this; var observerSet = new Set(); pathPatterns.forEach(function(pathPattern) { var observer = _this.factoryObserver(pathPattern, observerFunction, observerContext); observerSet.add(observer); }); return observerSet; }, /** * @private * @param {Observation} observation */ propagateObservationToObservers: function(observation) { observation.setReportingObservable(this); var changeTypeObserverList = this.changeTypeObserverMap.get(observation.getChangeType()); if (changeTypeObserverList) { var iterator = changeTypeObserverList.iterator(); while (iterator.hasNext()) { var observer = iterator.next(); if (observer.match(observation.getObservationPath())) { observer.observeObservation(observation); } } } }, /** * @private * @param {Observation} observation */ propagateObservationToPropagators: function(observation) { var clonePropagatorList = this.getObservationPropagatorList().clone(); clonePropagatorList.forEach(function(propagator) { propagator.propagateObservation(observation); }); } }); //------------------------------------------------------------------------------- // Interfaces //------------------------------------------------------------------------------- Class.implement(Observable, IObservable); //------------------------------------------------------------------------------- // Exports //------------------------------------------------------------------------------- bugpack.export('Observable', Observable); });