While working on a project that needs to notify when updates are made, I started playing with the Observer Pattern in Javascript. Changes made to the Observed Object trigger a console.log when updates are made. The actual implementation is using SocketIO to notify users of updates concurrent with updates to the persistence layer.
Note: The code is still in development and needs additional refactoring.
(function () { 'use strict'; /*global console */ function Observable() { // Observable class var _observingArray = []; return { action: function (action, func) { console.log(action, func); }, addWatcher: function (obj) { var funcName = arguments[0].name; if (!_observingArray[funcName]) { this.action('New Watcher:', funcName); _observingArray.push(obj); } }, removeWatcher: function(obj) { var i = 0, len = _observingArray.length; for (i; i < len; i++ ) { if (_observingArray[i] === obj ) { _observingArray.splice(i, 1); var funcName = arguments[0].name; this.action('Removing Watcher:', funcName); return true; } } return false; }, notify: function () { // arguments is an array like object / converted to array here var args = Array.prototype.slice.call( arguments, 0 ); var i = 0, len = _observingArray.length; for (i; i < len; i++) { _observingArray[i].update.apply( null, args ); } } }; } // basic example of observer object with function MakeObservableObject() { // very basic object comparator function updateObject(obj, updates) { var updated = false; Object.keys(updates).forEach(function(k) { // if property values are different update if (obj.contents[k] !== updates[k]) { if (updates[k] > 0 || updates[k].length > 0) { // if property has a value - update obj.contents[k] = updates[k]; } else { // if property has no value - delete the property delete obj[k]; } updated = true; } }); if (updated) { return obj; } return false; } var items = {}; var observer = MakeObserver(this); // notifies watchers this.getItems = function getItems() { observer.notify(items); }; // initial configuration of object properties // does not notify watchers this.setItems = function setItems(obj) { items = obj; }; // updates object properties // notifies watchers of new object contents this.update = function updateItems(updates) { if (updates) { var updated = updateObject(items, updates); if (updated) { items = updated; observer.notify(items); } } }; } function MakeObserver(obj) { // obj passed in is extended with Observable methods and returned var observable = Observable(); // obj passed in is added as an observer obj.addObserver = function addObserver(observer) { observable.addWatcher(observer); }; // obj passed in is removed from observable as an observer obj.removeObserver = function removeObserver(observer) { observable.removeWatcher(observer); }; return observable; } // ============================ // ========= Examples ========= // ============================ // CUSTOM OBSERVER Methods var ItemUpdater = { name: 'ItemUpdater', update : function() { var item = arguments[0]; console.log( 'Update ItemUpdater:', item.name, ' = ', JSON.stringify(item.contents)); } }; var ItemCharts = { name: 'ItemCharts', update : function() { var item = arguments[0]; console.log( 'Update ItemCharts:', item.name, ' = ', JSON.stringify(item.contents)); } }; // OBSERVABLE OBJECT var app = new MakeObservableObject(); // OBSERVABLE OBJECT CONTENTS var items = { name: 'clothes', 'contents': {socks : 7, pants : 3, shirts : 4} }; // SET OBSERVABLE OBJECTS - PUBLIC CONTENTS app.setItems.call(app, items); // ADD OBSERVER app.addObserver(ItemUpdater); // ADD OBSERVER app.addObserver(ItemCharts); // CHECK CURRENT CONTENTS / TRIGGER NOTIFICATION app.getItems(); // REMOVE OBSERVER app.removeObserver(ItemUpdater); // REMOVE OBSERVER app.removeObserver(ItemCharts); // ADD OBSERVER app.addObserver(ItemCharts); app.update({'ties':26.00}); app.getItems(); // OBSERVABLE OBJECT var music = new MakeObservableObject(); // OBSERVABLE CONTENTS var albums = { name: 'music', 'contents': {blues : 1, jazz : 3, punk : 2} }; music.setItems(albums); music.addObserver(ItemCharts); music.addObserver(ItemUpdater); }());