ruk·si

JS Pattern
Observer

Updated at 2015-05-13 13:08
var Subject = (function() {
  'use strict';

  /**
   * Allows mixed object to notify linked observers.
   * @constructor
   */
  var Subject = function() {
    this.observers = [];
  };
  Subject.mixinTo = function(target) {
    var source = new Subject();
    for (var key in source) {
      target[key] = source[key];
    }
  };
  Subject.prototype.addObserver = function(observer) {
    this.observers.push(observer);
  };
  Subject.prototype.removeObserver = function(observer) {
    var i = this.observers.length;
    while (i--) {
      if (this.observers[i] === observer) {
        this.observers.splice(i, 1);
      }
    }
    return null;
  };
  Subject.prototype.notifyObservers = function(context) {
    context = context || {};
    var observerCount = this.observers.length;
    for (var i = 0; i < observerCount; i++) {
      this.observers[i].onNotify(context);
    }
  };
  Subject.prototype.getObserverAt = function(index) {
    var v = this.observers[index];
    return (v === void 0) ? null : v;
  };
  Subject.prototype.getObserverCount = function() {
    return this.observers.length;
  };
  Subject.prototype.removeAllObservers = function() {
    this.observers = [];
  };

  return Subject;
}());

var Observer = (function() {
  'use strict';

  /**
   * Allows mixed object to listen notifications made by linked subjects.
   * @constructor
   */
  var Observer = function(options) {
    options = options || {onNotify: function() {}};
    this.onNotify = options.onNotify;
  };
  Observer.mixinTo = function(target, onNotify) {
    var source = new Observer({onNotify: onNotify});
    for (var key in source) {
      target[key] = source[key];
    }
  };

  return Observer;
}());

var mySubject = {};
Subject.mixinTo(mySubject);

var check = {};
Observer.mixinTo(check, function(context) {
  console.log('check: ' + context.msg);
});

var done = {};
Observer.mixinTo(done, function(context) {
  console.log('done: ' + context.msg);
});

console.assert(mySubject.getObserverCount() === 0);
mySubject.addObserver(check);
console.assert(mySubject.getObserverCount() === 1);
mySubject.addObserver(done);
console.assert(mySubject.getObserverCount() === 2);
console.assert(mySubject.getObserverAt(3) === null);
mySubject.notifyObservers({msg: '1'});
mySubject.removeObserver(check);
console.assert(mySubject.getObserverCount() === 1);
mySubject.notifyObservers({msg: '2'});
mySubject.addObserver(check);
console.assert(mySubject.getObserverAt(0) === done);
mySubject.removeAllObservers();
console.assert(mySubject.getObserverCount() === 0);
mySubject.notifyObservers({msg: '3'});
console.assert(mySubject.getObserverAt(0) === null);