Skip to content

Instantly share code, notes, and snippets.

@bangedorrunt
Last active August 29, 2015 14:21
Show Gist options
  • Save bangedorrunt/c1ddfabbd555081fd148 to your computer and use it in GitHub Desktop.
Save bangedorrunt/c1ddfabbd555081fd148 to your computer and use it in GitHub Desktop.
Design Patterns for JavaScript and Ruby
function Event(sender) {
this._sender = sender;
this._listeners = [];
}
Event.prototype = {
attach: function(listener) {
this._listeners.push(listener);
},
notify: function(args) {
var index;
for (index = 0; index < this._listeners.length; index += 1) {
this._listeners[index](this._sender, args);
}
}
};
var events = (function() {
var topics = {};
var hOP = topics.hasOwnProperty;
return {
subscribe: function(topic, listener) {
// Create the topic's object if not yet created
if (!hOP.call(topics, topic)) topics[topic] = [];
// Add the listener to queue
var index = topics[topic].push(listener) - 1;
// Provide handle back for removal of topic
return {
remove: function() {
delete topics[topic][index];
}
};
},
publish: function(topic, info) {
// If the topic doesn't exist, or there's no listeners in queue, just leave
if (!hOP.call(topics, topic)) return;
// Cycle through topics queue, fire!
topics[topic].forEach(function(item) {
item(info != undefined ? info : {});
});
}
};
})();
/**
* The Model. Model stores items and notifies
* observers about changes.
*/
function ListModel(items) {
this._items = items;
this._selectedIndex = -1;
}
ListModel.prototype = {
getItems: function() {
return [].concat(this._items);
},
addItem: function(item) {
this._items.push(item);
events.publish('itemAdded', {
item: item
});
},
removeItemAt: function(index) {
var item;
item = this._items[index];
this._items.splice(index, 1);
events.publish('itemRemoved', {
item: item
});
if (index === this._selectedIndex) {
this.setSelectedIndex(-1);
}
},
getSelectedIndex: function() {
return this._selectedIndex;
},
setSelectedIndex: function(index) {
var previousIndex;
previousIndex = this._selectedIndex;
this._selectedIndex = index;
events.publish('selectedIndexChanged', {
previous: previousIndex
});
}
};
/**
* The View. View presents the model and provides
* the UI events. The controller is attached to these
* events to handle the user interraction.
*/
function ListView(elements) {
this._elements = elements;
var _this = this;
// attach listeners to HTML controls
this._elements.list.change(function(e) {
events.publish('listModified', {
index: e.target.selectedIndex
});
});
this._elements.addButton.click(function() {
events.publish('addButtonClicked', {});
});
this._elements.delButton.click(function() {
events.publish('delButtonClicked', {});
});
}
ListView.prototype = {
};
/**
* The Controller. Controller responds to user actions and
* invokes changes on the model.
*/
function ListController(model, view) {
this._model = model;
this._view = view;
var _this = this;
events.subscribe('listModified', function(data) {
_this.updateSelected(data.index);
});
events.subscribe('addButtonClicked', function(data) {
_this.addItem();
});
events.subscribe('delButtonClicked', function(data) {
_this.delItem();
});
// attach model listeners
events.subscribe('itemAdded', function(data) {
_this.rebuildList();
});
events.subscribe('itemRemoved', function(data) {
_this.rebuildList();
});
}
ListController.prototype = {
addItem: function() {
var item = window.prompt('Add item:', '');
if (item) {
this._model.addItem(item);
}
},
delItem: function() {
var index;
index = this._model.getSelectedIndex();
if (index !== -1) {
this._model.removeItemAt(this._model.getSelectedIndex());
}
},
updateSelected: function(index) {
this._model.setSelectedIndex(index);
},
show: function() {
this.rebuildList();
},
rebuildList: function() {
var list, items, key;
list = this._view._elements.list;
list.html('');
items = this._model.getItems();
for (key in items) {
if (items.hasOwnProperty(key)) {
list.append($('<option>' + items[key] + '</option>'));
}
}
this._model.setSelectedIndex(-1);
}
};
$(function() {
var model = new ListModel(['PHP', 'JavaScript']),
view = new ListView({
'list': $('#list'),
'addButton': $('#plusBtn'),
'delButton': $('#minusBtn')
}),
controller = new ListController(model, view);
controller.show();
});
// Source: http://addyosmani.com/resources/essentialjsdesignpatterns/book/#observerpatternjavascript
// ToDo: Convert to ES6
function ObserverList(){
this.observerList = [];
}
ObserverList.prototype.add = function( obj ){
return this.observerList.push( obj );
};
ObserverList.prototype.count = function(){
return this.observerList.length;
};
ObserverList.prototype.get = function( index ){
if( index > -1 && index < this.observerList.length ){
return this.observerList[ index ];
}
};
ObserverList.prototype.indexOf = function( obj, startIndex ){
var i = startIndex;
while( i < this.observerList.length ){
if( this.observerList[i] === obj ){
return i;
}
i++;
}
return -1;
};
ObserverList.prototype.removeAt = function( index ){
this.observerList.splice( index, 1 );
};
// The Subject
function Subject(){
this.observers = new ObserverList();
}
Subject.prototype.addObserver = function( observer ){
this.observers.add( observer );
};
Subject.prototype.removeObserver = function( observer ){
this.observers.removeAt( this.observers.indexOf( observer, 0 ) );
};
Subject.prototype.notify = function( context ){
var observerCount = this.observers.count();
for(var i=0; i < observerCount; i++){
this.observers.get(i).update( context );
}
};
// The Observer
function Observer(){
this.update = function(){
// ...
};
}
// Source: http://davidwalsh.name/pubsub-javascript
// Library: https://github.com/mroderick/PubSubJS
// ToDo: Convert to ES6
var events = (function(){
var topics = {};
var hOP = topics.hasOwnProperty;
return {
subscribe: function(topic, listener) {
// Create the topic's object if not yet created
if(!hOP.call(topics, topic)) topics[topic] = [];
// Add the listener to queue
var index = topics[topic].push(listener) -1;
// Provide handle back for removal of topic
return {
remove: function() {
delete topics[topic][index];
}
};
},
publish: function(topic, info) {
// If the topic doesn't exist, or there's no listeners in queue, just leave
if(!hOP.call(topics, topic)) return;
// Cycle through topics queue, fire!
topics[topic].forEach(function(item) {
item(info != undefined ? info : {});
});
}
};
})();
// Publishing to a topic
events.publish('/page/load', {
url: '/some/url/path' // any argument
});
// Subscribing to a topic
var subscription = events.subscribe('/page/load', function(obj) {
// Do something now that the event has occurred
});
// ...Sometime later where I no longer want subscription...
subscription.remove();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment