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 };