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