1 /**
  2  * Emily.js - http://flams.github.com/emily/
  3  * Copyright(c) 2012-2014 Olivier Scherrer <pode.fr@gmail.com>
  4  * MIT Licensed
  5  */
  6 "use strict";
  7 
  8 var Tools = require("./Tools");
  9 /**
 10 * @class
 11 * Observable is an implementation of the Observer design pattern,
 12 * which is also known as publish/subscribe.
 13 *
 14 * This service creates an Observable to which you can add subscribers.
 15 *
 16 * @returns {Observable}
 17 */
 18 module.exports = function ObservableConstructor() {
 19 
 20     /**
 21      * The list of topics
 22      * @private
 23      */
 24     var _topics = {};
 25 
 26     /**
 27      * Add an observer
 28      * @param {String} topic the topic to observe
 29      * @param {Function} callback the callback to execute
 30      * @param {Object} scope the scope in which to execute the callback
 31      * @returns handle
 32      */
 33     this.watch = function watch(topic, callback, scope) {
 34         if (typeof callback == "function") {
 35             var observers = _topics[topic] = _topics[topic] || [],
 36             observer = [callback, scope];
 37 
 38             observers.push(observer);
 39             return [topic,observers.indexOf(observer)];
 40 
 41         } else {
 42             return false;
 43         }
 44     };
 45 
 46     /**
 47      * Listen to an event just once before removing the handler
 48      * @param {String} topic the topic to observe
 49      * @param {Function} callback the callback to execute
 50      * @param {Object} scope the scope in which to execute the callback
 51      * @returns handle
 52      */
 53     this.once = function once(topic, callback, scope) {
 54         var handle = this.watch(topic, function () {
 55             callback.apply(scope, arguments);
 56             this.unwatch(handle);
 57         }, this);
 58         return handle;
 59     };
 60 
 61     /**
 62      * Remove an observer
 63      * @param {Handle} handle returned by the watch method
 64      * @returns {Boolean} true if there were subscribers
 65      */
 66     this.unwatch = function unwatch(handle) {
 67         var topic = handle[0], idx = handle[1];
 68         if (_topics[topic] && _topics[topic][idx]) {
 69             // delete value so the indexes don't move
 70             delete _topics[topic][idx];
 71             // If the topic is only set with falsy values, delete it;
 72             if (!_topics[topic].some(function (value) {
 73                 return !!value;
 74             })) {
 75                 delete _topics[topic];
 76             }
 77             return true;
 78         } else {
 79             return false;
 80         }
 81     };
 82 
 83     /**
 84      * Notifies observers that a topic has a new message
 85      * @param {String} topic the name of the topic to publish to
 86      * @param subject
 87      * @returns {Boolean} true if there was subscribers
 88      */
 89     this.notify = function notify(topic) {
 90         var observers = _topics[topic],
 91             args = Tools.toArray(arguments).slice(1);
 92 
 93         if (observers) {
 94             Tools.loop(observers, function (value) {
 95                 try {
 96                     if (value) {
 97                         value[0].apply(value[1] || null, args);
 98                     }
 99                 } catch (err) { }
100             });
101             return true;
102         } else {
103             return false;
104         }
105     };
106 
107     /**
108      * Check if topic has the described observer
109      * @param {Handle}
110      * @returns {Boolean} true if exists
111      */
112     this.hasObserver = function hasObserver(handle) {
113         return !!( handle && _topics[handle[0]] && _topics[handle[0]][handle[1]]);
114     };
115 
116     /**
117      * Check if a topic has observers
118      * @param {String} topic the name of the topic
119      * @returns {Boolean} true if topic is listened
120      */
121     this.hasTopic = function hasTopic(topic) {
122         return !!_topics[topic];
123     };
124 
125     /**
126      * Unwatch all or unwatch all from topic
127      * @param {String} topic optional unwatch all from topic
128      * @returns {Boolean} true if ok
129      */
130     this.unwatchAll = function unwatchAll(topic) {
131         if (_topics[topic]) {
132             delete _topics[topic];
133         } else {
134             _topics = {};
135         }
136         return true;
137     };
138 };