From e9503f579321ff53f5cf2a6c8da1c8625e5f9daa Mon Sep 17 00:00:00 2001 From: Nicholas Date: Fri, 22 Apr 2011 14:37:55 -0700 Subject: [PATCH] Mediator pattern in JavaScript --- .../gallery-mediator-debug.js | 132 ++++++++++++++++++ .../gallery-mediator/gallery-mediator-min.js | 1 + build/gallery-mediator/gallery-mediator.js | 132 ++++++++++++++++++ src/gallery-mediator/build.properties | 4 + src/gallery-mediator/build.xml | 6 + src/gallery-mediator/js/mediator.js | 126 +++++++++++++++++ src/gallery-mediator/tests/mediator.html | 128 +++++++++++++++++ 7 files changed, 529 insertions(+) create mode 100644 build/gallery-mediator/gallery-mediator-debug.js create mode 100644 build/gallery-mediator/gallery-mediator-min.js create mode 100644 build/gallery-mediator/gallery-mediator.js create mode 100644 src/gallery-mediator/build.properties create mode 100644 src/gallery-mediator/build.xml create mode 100644 src/gallery-mediator/js/mediator.js create mode 100644 src/gallery-mediator/tests/mediator.html diff --git a/build/gallery-mediator/gallery-mediator-debug.js b/build/gallery-mediator/gallery-mediator-debug.js new file mode 100644 index 0000000000..4a5d8b55df --- /dev/null +++ b/build/gallery-mediator/gallery-mediator-debug.js @@ -0,0 +1,132 @@ +YUI.add('gallery-mediator', function(Y) { + +/*global YUI*/ +/* + * Copyright (c) 2011 Yahoo! Inc. All rights reserved. + * Author: Nicholas C. Zakas, nczonline.net + */ + +/** + * Mediator pattern in JavaScript. For more info on Mediator pattern: + * http://en.wikipedia.org/wiki/Mediator_pattern + * @module gallery-mediator + */ + +/** + * Implementation of the mediator pattern. Purposely does not + * require Y.Event.Target to avoid confusion with regular + * event target pattern and also to keep the code as light + * and simple as possible. + * @class Mediator + * @static + */ +Y.Mediator = function(){ + /** + * Array of listeners. + * @type Array + * @property _listeners + * @private + */ + this._listeners = {}; +}; + +Y.Mediator.prototype = { + + //restore constructor + constructor: Y.Mediator, + + /** + * Broadcasts a message throughout the system, calling any + * registered callbacks. + * @param {String} name The name of the message to fire. + * @param {variant} data (Optional) Any additional data. + * @return {void} + * @method broadcast + */ + broadcast: function(name, data){ + var i, len, + nameListeners = this._listeners[name]; + + if (nameListeners){ + /* + * Create a clone of the array list. This handles the case where + * a callback calls listen() or unlisten() and thus alters the number of + * listeners. Using the clone ensures that the original listeners all + * get called. + */ + nameListeners = nameListeners.concat(); + for (i=0, len=nameListeners.length; i < len; i++){ + nameListeners[i].callback.call(nameListeners[i].scope, { + type: name, + data: data + }); + } + } + }, + + /** + * Registers a listener for a particular message. + * @param {String} name The name of the message to listen for. + * @param {Function} callback The function to call when the message occurs. + * @param {Object} scope The value for "this" inside of the callback. + * @return {void} + * @method listen + */ + listen: function(name, callback, scope){ + var listeners = this._listeners; + + if (!listeners[name]){ + listeners[name] = []; + } + + /* + * In my experience, the #1 cause of issues with callback functions + * is that someone passes in a value that they think contains a + * function but actually doesn't. Then when something tries to call + * that function, there's an error that's hard to track down. Throwing + * an error here allows you to trap the issue at the listen() method, + * which is where the wrong value is being passed in. This improves + * debugging such issues dramatically. + */ + if (typeof callback == "function"){ + listeners[name].push({ + callback: callback, + scope:scope + }); + } else { + throw new Error("Callback must be a function."); + } + }, + + /** + * Unregisters a listener for a particular message. The callback function + * and the scope must match the ones passed into listen() to be removed. + * @param {String} name The name of the message the listener was registered for. + * @param {Function} callback The function to remove. + * @param {Object} scope The value for "this" inside of the callback. + * @return {Boolean} True if the callback was removed, false if not. + * @method listen + */ + unlisten: function(name, callback, scope){ + var i, len, + nameListeners = this._listeners[name], + removed = false; + + if (nameListeners){ + for (i=0, len=nameListeners.length; i < len; i++){ + if (nameListeners[i].callback === callback && nameListeners[i].scope === scope){ + nameListeners[i].splice(i, 1); + removed = true; + break; + } + } + } + + return removed; + } + +}; + + + +}, '@VERSION@' ); diff --git a/build/gallery-mediator/gallery-mediator-min.js b/build/gallery-mediator/gallery-mediator-min.js new file mode 100644 index 0000000000..7d91e250b7 --- /dev/null +++ b/build/gallery-mediator/gallery-mediator-min.js @@ -0,0 +1 @@ +YUI.add("gallery-mediator",function(A){A.Mediator=function(){this._listeners={};};A.Mediator.prototype={constructor:A.Mediator,broadcast:function(C,F){var D,B,E=this._listeners[C];if(E){E=E.concat();for(D=0,B=E.length;D + + + + diff --git a/src/gallery-mediator/js/mediator.js b/src/gallery-mediator/js/mediator.js new file mode 100644 index 0000000000..1b9dc4d8e3 --- /dev/null +++ b/src/gallery-mediator/js/mediator.js @@ -0,0 +1,126 @@ +/*global YUI*/ +/* + * Copyright (c) 2011 Yahoo! Inc. All rights reserved. + * Author: Nicholas C. Zakas, nczonline.net + */ + +/** + * Mediator pattern in JavaScript. For more info on Mediator pattern: + * http://en.wikipedia.org/wiki/Mediator_pattern + * @module gallery-mediator + */ + +/** + * Implementation of the mediator pattern. Purposely does not + * require Y.Event.Target to avoid confusion with regular + * event target pattern and also to keep the code as light + * and simple as possible. + * @class Mediator + * @static + */ +Y.Mediator = function(){ + /** + * Array of listeners. + * @type Array + * @property _listeners + * @private + */ + this._listeners = {}; +}; + +Y.Mediator.prototype = { + + //restore constructor + constructor: Y.Mediator, + + /** + * Broadcasts a message throughout the system, calling any + * registered callbacks. + * @param {String} name The name of the message to fire. + * @param {variant} data (Optional) Any additional data. + * @return {void} + * @method broadcast + */ + broadcast: function(name, data){ + var i, len, + nameListeners = this._listeners[name]; + + if (nameListeners){ + /* + * Create a clone of the array list. This handles the case where + * a callback calls listen() or unlisten() and thus alters the number of + * listeners. Using the clone ensures that the original listeners all + * get called. + */ + nameListeners = nameListeners.concat(); + for (i=0, len=nameListeners.length; i < len; i++){ + nameListeners[i].callback.call(nameListeners[i].scope, { + type: name, + data: data + }); + } + } + }, + + /** + * Registers a listener for a particular message. + * @param {String} name The name of the message to listen for. + * @param {Function} callback The function to call when the message occurs. + * @param {Object} scope The value for "this" inside of the callback. + * @return {void} + * @method listen + */ + listen: function(name, callback, scope){ + var listeners = this._listeners; + + if (!listeners[name]){ + listeners[name] = []; + } + + /* + * In my experience, the #1 cause of issues with callback functions + * is that someone passes in a value that they think contains a + * function but actually doesn't. Then when something tries to call + * that function, there's an error that's hard to track down. Throwing + * an error here allows you to trap the issue at the listen() method, + * which is where the wrong value is being passed in. This improves + * debugging such issues dramatically. + */ + if (typeof callback == "function"){ + listeners[name].push({ + callback: callback, + scope:scope + }); + } else { + throw new Error("Callback must be a function."); + } + }, + + /** + * Unregisters a listener for a particular message. The callback function + * and the scope must match the ones passed into listen() to be removed. + * @param {String} name The name of the message the listener was registered for. + * @param {Function} callback The function to remove. + * @param {Object} scope The value for "this" inside of the callback. + * @return {Boolean} True if the callback was removed, false if not. + * @method listen + */ + unlisten: function(name, callback, scope){ + var i, len, + nameListeners = this._listeners[name], + removed = false; + + if (nameListeners){ + for (i=0, len=nameListeners.length; i < len; i++){ + if (nameListeners[i].callback === callback && nameListeners[i].scope === scope){ + nameListeners[i].splice(i, 1); + removed = true; + break; + } + } + } + + return removed; + } + +}; \ No newline at end of file diff --git a/src/gallery-mediator/tests/mediator.html b/src/gallery-mediator/tests/mediator.html new file mode 100644 index 0000000000..599dffe1b8 --- /dev/null +++ b/src/gallery-mediator/tests/mediator.html @@ -0,0 +1,128 @@ + + +Mediator Tests + + + +

Mediator Tests

+
+ + +