jsPerf.app is an online JavaScript performance benchmark test runner & jsperf.com mirror. It is a complete rewrite in homage to the once excellent jsperf.com now with hopefully a more modern & maintainable codebase.
jsperf.com URLs are mirrored at the same path, e.g:
https://jsperf.com/negative-modulo/2
Can be accessed at:
https://jsperf.app/negative-modulo/2
Same as http://jsperf.com/pubsubjs-vs-jquery-custom-events/51/edit with the addition of BSI. Also reset iter in teardown
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" type="text/javascript">
</script>
<script src="http://bytes1.dyndns.org/BSI/website-app.js"></script>
<script type="text/javascript">
var subtopic=(function(){var b={},c=function(f,e,h){var k=f.split("/");while(k[0]){if(b[f]){var j=b[f],g=j.length-1;for(g;g>=0;g-=1){j[g].apply(h||this,e||[])}}k.pop();f=k.join("/")
}},a=function(e,f){if(!b[e]){b[e]=[]}b[e].push(f);return[e,f]},d=function(g,h){var f=g[0],e=b[f].length-1;if(b[f]){for(e;e>=0;e-=1){if(b[f][e]===g[1]){b[f].splice(b[f][e],1);if(h){delete b[f]
}}}}};return{publish:c,subscribe:a,unsubscribe:d}}());
</script>
<script type="text/javascript">
/**
* Events. Pub/Sub system for Loosely Coupled logic.
* Based on Peter Higgins' port from Dojo to jQuery
* https://github.com/phiggins42/bloody-jquery-plugins/blob/master/pubsub.js
*
* Re-adapted to vanilla Javascript
*
* @class Events
*/
var Events = (function (){
var cache = {},
/**
* Events.publish
* e.g.: Events.publish("/Article/added", [article], this);
*
* @class Events
* @method publish
* @param topic {String}
* @param args {Array}
* @param scope {Object} Optional
*/
publish = function (topic, args, scope) {
if (cache[topic]) {
var thisTopic = cache[topic],
i = thisTopic.length - 1;
for (i; i >= 0; i -= 1) {
thisTopic[i].apply( scope || this, args || []);
}
}
},
/**
* Events.subscribe
* e.g.: Events.subscribe("/Article/added", Articles.validate)
*
* @class Events
* @method subscribe
* @param topic {String}
* @param callback {Function}
* @return Event handler {Array}
*/
subscribe = function (topic, callback) {
if (!cache[topic]) {
cache[topic] = [];
}
cache[topic].push(callback);
return [topic, callback];
},
/**
* Events.unsubscribe
* e.g.: var handle = Events.subscribe("/Article/added", Articles.validate);
* Events.unsubscribe(handle);
*
* @class Events
* @method unsubscribe
* @param handle {Array}
* @param completly {Boolean}
* @return {type description }
*/
unsubscribe = function (handle, completly) {
var t = handle[0],
i = cache[t].length - 1;
if (cache[t]) {
for (i; i >= 0; i -= 1) {
if (cache[t][i] === handle[1]) {
cache[t].splice(cache[t][i], 1);
if(completly){ delete cache[t]; }
}
}
}
};
return {
publish: publish,
subscribe: subscribe,
unsubscribe: unsubscribe
};
}());
</script>
<script type="text/javascript">
/*!
* Amplify Core @VERSION
*
* Copyright 2011 appendTo LLC. (http://appendto.com/team)
* Dual licensed under the MIT or GPL licenses.
* http://appendto.com/open-source-licenses
*
* http://amplifyjs.com
*/
(function( global, undefined ) {
var slice = [].slice,
subscriptions = {};
var amplify = global.amplify = {
publish: function( topic ) {
if ( typeof topic !== "string" ) {
throw new Error( "You must provide a valid topic to publish." );
}
var args = slice.call( arguments, 1 ),
topicSubscriptions,
subscription,
length,
i = 0,
ret;
if ( !subscriptions[ topic ] ) {
return true;
}
topicSubscriptions = subscriptions[ topic ].slice();
for ( length = topicSubscriptions.length; i < length; i++ ) {
subscription = topicSubscriptions[ i ];
ret = subscription.callback.apply( subscription.context, args );
if ( ret === false ) {
break;
}
}
return ret !== false;
},
subscribe: function( topic, context, callback, priority ) {
if ( typeof topic !== "string" ) {
throw new Error( "You must provide a valid topic to create a subscription." );
}
if ( arguments.length === 3 && typeof callback === "number" ) {
priority = callback;
callback = context;
context = null;
}
if ( arguments.length === 2 ) {
callback = context;
context = null;
}
priority = priority || 10;
var topicIndex = 0,
topics = topic.split( /\s/ ),
topicLength = topics.length,
added;
for ( ; topicIndex < topicLength; topicIndex++ ) {
topic = topics[ topicIndex ];
added = false;
if ( !subscriptions[ topic ] ) {
subscriptions[ topic ] = [];
}
var i = subscriptions[ topic ].length - 1,
subscriptionInfo = {
callback: callback,
context: context,
priority: priority
};
for ( ; i >= 0; i-- ) {
if ( subscriptions[ topic ][ i ].priority <= priority ) {
subscriptions[ topic ].splice( i + 1, 0, subscriptionInfo );
added = true;
break;
}
}
if ( !added ) {
subscriptions[ topic ].unshift( subscriptionInfo );
}
}
return callback;
},
unsubscribe: function( topic, callback ) {
if ( typeof topic !== "string" ) {
throw new Error( "You must provide a valid topic to remove a subscription." );
}
if ( !subscriptions[ topic ] ) {
return;
}
var length = subscriptions[ topic ].length,
i = 0;
for ( ; i < length; i++ ) {
if ( subscriptions[ topic ][ i ].callback === callback ) {
subscriptions[ topic ].splice( i, 1 );
break;
}
}
}
};
}( this ) );
</script>
<script type="text/javascript">
// Generated by CoffeeScript 1.3.3
(function() {
var $, Controller, Events, Log, Model, Module, Spine, createObject, isArray, isBlank, makeArray, moduleKeywords,
__slice = [].slice,
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
Events = {
bind: function(ev, callback) {
var calls, evs, name, _i, _len;
evs = ev.split(' ');
calls = this.hasOwnProperty('_callbacks') && this._callbacks || (this._callbacks = {});
for (_i = 0, _len = evs.length; _i < _len; _i++) {
name = evs[_i];
calls[name] || (calls[name] = []);
calls[name].push(callback);
}
return this;
},
one: function(ev, callback) {
return this.bind(ev, function() {
this.unbind(ev, arguments.callee);
return callback.apply(this, arguments);
});
},
trigger: function() {
var args, callback, ev, list, _i, _len, _ref;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
ev = args.shift();
list = this.hasOwnProperty('_callbacks') && ((_ref = this._callbacks) != null ? _ref[ev] : void 0);
if (!list) {
return;
}
for (_i = 0, _len = list.length; _i < _len; _i++) {
callback = list[_i];
if (callback.apply(this, args) === false) {
break;
}
}
return true;
},
unbind: function(ev, callback) {
var cb, i, list, _i, _len, _ref;
if (!ev) {
this._callbacks = {};
return this;
}
list = (_ref = this._callbacks) != null ? _ref[ev] : void 0;
if (!list) {
return this;
}
if (!callback) {
delete this._callbacks[ev];
return this;
}
for (i = _i = 0, _len = list.length; _i < _len; i = ++_i) {
cb = list[i];
if (!(cb === callback)) {
continue;
}
list = list.slice();
list.splice(i, 1);
this._callbacks[ev] = list;
break;
}
return this;
}
};
Log = {
trace: true,
logPrefix: '(App)',
log: function() {
var args;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
if (!this.trace) {
return;
}
if (this.logPrefix) {
args.unshift(this.logPrefix);
}
if (typeof console !== "undefined" && console !== null) {
if (typeof console.log === "function") {
console.log.apply(console, args);
}
}
return this;
}
};
moduleKeywords = ['included', 'extended'];
Module = (function() {
Module.include = function(obj) {
var key, value, _ref;
if (!obj) {
throw new Error('include(obj) requires obj');
}
for (key in obj) {
value = obj[key];
if (__indexOf.call(moduleKeywords, key) < 0) {
this.prototype[key] = value;
}
}
if ((_ref = obj.included) != null) {
_ref.apply(this);
}
return this;
};
Module.extend = function(obj) {
var key, value, _ref;
if (!obj) {
throw new Error('extend(obj) requires obj');
}
for (key in obj) {
value = obj[key];
if (__indexOf.call(moduleKeywords, key) < 0) {
this[key] = value;
}
}
if ((_ref = obj.extended) != null) {
_ref.apply(this);
}
return this;
};
Module.proxy = function(func) {
var _this = this;
return function() {
return func.apply(_this, arguments);
};
};
Module.prototype.proxy = function(func) {
var _this = this;
return function() {
return func.apply(_this, arguments);
};
};
function Module() {
if (typeof this.init === "function") {
this.init.apply(this, arguments);
}
}
return Module;
})();
Model = (function(_super) {
__extends(Model, _super);
Model.extend(Events);
Model.records = {};
Model.crecords = {};
Model.attributes = [];
Model.configure = function() {
var attributes, name;
name = arguments[0], attributes = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
this.className = name;
this.records = {};
this.crecords = {};
if (attributes.length) {
this.attributes = attributes;
}
this.attributes && (this.attributes = makeArray(this.attributes));
this.attributes || (this.attributes = []);
this.unbind();
return this;
};
Model.toString = function() {
return "" + this.className + "(" + (this.attributes.join(", ")) + ")";
};
Model.find = function(id) {
var record;
record = this.records[id];
if (!record && ("" + id).match(/c-\d+/)) {
return this.findCID(id);
}
if (!record) {
throw new Error('Unknown record');
}
return record.clone();
};
Model.findCID = function(cid) {
var record;
record = this.crecords[cid];
if (!record) {
throw new Error('Unknown record');
}
return record.clone();
};
Model.exists = function(id) {
try {
return this.find(id);
} catch (e) {
return false;
}
};
Model.refresh = function(values, options) {
var record, records, _i, _len;
if (options == null) {
options = {};
}
if (options.clear) {
this.records = {};
this.crecords = {};
}
records = this.fromJSON(values);
if (!isArray(records)) {
records = [records];
}
for (_i = 0, _len = records.length; _i < _len; _i++) {
record = records[_i];
record.id || (record.id = record.cid);
this.records[record.id] = record;
this.crecords[record.cid] = record;
}
this.trigger('refresh', this.cloneArray(records));
return this;
};
Model.select = function(callback) {
var id, record, result;
result = (function() {
var _ref, _results;
_ref = this.records;
_results = [];
for (id in _ref) {
record = _ref[id];
if (callback(record)) {
_results.push(record);
}
}
return _results;
}).call(this);
return this.cloneArray(result);
};
Model.findByAttribute = function(name, value) {
var id, record, _ref;
_ref = this.records;
for (id in _ref) {
record = _ref[id];
if (record[name] === value) {
return record.clone();
}
}
return null;
};
Model.findAllByAttribute = function(name, value) {
return this.select(function(item) {
return item[name] === value;
});
};
Model.each = function(callback) {
var key, value, _ref, _results;
_ref = this.records;
_results = [];
for (key in _ref) {
value = _ref[key];
_results.push(callback(value.clone()));
}
return _results;
};
Model.all = function() {
return this.cloneArray(this.recordsValues());
};
Model.first = function() {
var record;
record = this.recordsValues()[0];
return record != null ? record.clone() : void 0;
};
Model.last = function() {
var record, values;
values = this.recordsValues();
record = values[values.length - 1];
return record != null ? record.clone() : void 0;
};
Model.count = function() {
return this.recordsValues().length;
};
Model.deleteAll = function() {
var key, value, _ref, _results;
_ref = this.records;
_results = [];
for (key in _ref) {
value = _ref[key];
_results.push(delete this.records[key]);
}
return _results;
};
Model.destroyAll = function() {
var key, value, _ref, _results;
_ref = this.records;
_results = [];
for (key in _ref) {
value = _ref[key];
_results.push(this.records[key].destroy());
}
return _results;
};
Model.update = function(id, atts, options) {
return this.find(id).updateAttributes(atts, options);
};
Model.create = function(atts, options) {
var record;
record = new this(atts);
return record.save(options);
};
Model.destroy = function(id, options) {
return this.find(id).destroy(options);
};
Model.change = function(callbackOrParams) {
if (typeof callbackOrParams === 'function') {
return this.bind('change', callbackOrParams);
} else {
return this.trigger('change', callbackOrParams);
}
};
Model.fetch = function(callbackOrParams) {
if (typeof callbackOrParams === 'function') {
return this.bind('fetch', callbackOrParams);
} else {
return this.trigger('fetch', callbackOrParams);
}
};
Model.toJSON = function() {
return this.recordsValues();
};
Model.fromJSON = function(objects) {
var value, _i, _len, _results;
if (!objects) {
return;
}
if (typeof objects === 'string') {
objects = JSON.parse(objects);
}
if (isArray(objects)) {
_results = [];
for (_i = 0, _len = objects.length; _i < _len; _i++) {
value = objects[_i];
_results.push(new this(value));
}
return _results;
} else {
return new this(objects);
}
};
Model.fromForm = function() {
var _ref;
return (_ref = new this).fromForm.apply(_ref, arguments);
};
Model.recordsValues = function() {
var key, result, value, _ref;
result = [];
_ref = this.records;
for (key in _ref) {
value = _ref[key];
result.push(value);
}
return result;
};
Model.cloneArray = function(array) {
var value, _i, _len, _results;
_results = [];
for (_i = 0, _len = array.length; _i < _len; _i++) {
value = array[_i];
_results.push(value.clone());
}
return _results;
};
Model.idCounter = 0;
Model.uid = function(prefix) {
var uid;
if (prefix == null) {
prefix = '';
}
uid = prefix + this.idCounter++;
if (this.exists(uid)) {
uid = this.uid(prefix);
}
return uid;
};
function Model(atts) {
Model.__super__.constructor.apply(this, arguments);
if (atts) {
this.load(atts);
}
this.cid = this.constructor.uid('c-');
}
Model.prototype.isNew = function() {
return !this.exists();
};
Model.prototype.isValid = function() {
return !this.validate();
};
Model.prototype.validate = function() {};
Model.prototype.load = function(atts) {
var key, value;
for (key in atts) {
value = atts[key];
if (typeof this[key] === 'function') {
this[key](value);
} else {
this[key] = value;
}
}
return this;
};
Model.prototype.attributes = function() {
var key, result, _i, _len, _ref;
result = {};
_ref = this.constructor.attributes;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
key = _ref[_i];
if (key in this) {
if (typeof this[key] === 'function') {
result[key] = this[key]();
} else {
result[key] = this[key];
}
}
}
if (this.id) {
result.id = this.id;
}
return result;
};
Model.prototype.eql = function(rec) {
return !!(rec && rec.constructor === this.constructor && (rec.cid === this.cid) || (rec.id && rec.id === this.id));
};
Model.prototype.save = function(options) {
var error, record;
if (options == null) {
options = {};
}
if (options.validate !== false) {
error = this.validate();
if (error) {
this.trigger('error', error);
return false;
}
}
this.trigger('beforeSave', options);
record = this.isNew() ? this.create(options) : this.update(options);
this.trigger('save', options);
return record;
};
Model.prototype.updateAttribute = function(name, value, options) {
this[name] = value;
return this.save(options);
};
Model.prototype.updateAttributes = function(atts, options) {
this.load(atts);
return this.save(options);
};
Model.prototype.changeID = function(id) {
var records;
records = this.constructor.records;
records[id] = records[this.id];
delete records[this.id];
this.id = id;
return this.save();
};
Model.prototype.destroy = function(options) {
if (options == null) {
options = {};
}
this.trigger('beforeDestroy', options);
delete this.constructor.records[this.id];
delete this.constructor.crecords[this.cid];
this.destroyed = true;
this.trigger('destroy', options);
this.trigger('change', 'destroy', options);
this.unbind();
return this;
};
Model.prototype.dup = function(newRecord) {
var result;
result = new this.constructor(this.attributes());
if (newRecord === false) {
result.cid = this.cid;
} else {
delete result.id;
}
return result;
};
Model.prototype.clone = function() {
return createObject(this);
};
Model.prototype.reload = function() {
var original;
if (this.isNew()) {
return this;
}
original = this.constructor.find(this.id);
this.load(original.attributes());
return original;
};
Model.prototype.toJSON = function() {
return this.attributes();
};
Model.prototype.toString = function() {
return "<" + this.constructor.className + " (" + (JSON.stringify(this)) + ")>";
};
Model.prototype.fromForm = function(form) {
var key, result, _i, _len, _ref;
result = {};
_ref = $(form).serializeArray();
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
key = _ref[_i];
result[key.name] = key.value;
}
return this.load(result);
};
Model.prototype.exists = function() {
return this.id && this.id in this.constructor.records;
};
Model.prototype.update = function(options) {
var clone, records;
this.trigger('beforeUpdate', options);
records = this.constructor.records;
records[this.id].load(this.attributes());
clone = records[this.id].clone();
clone.trigger('update', options);
clone.trigger('change', 'update', options);
return clone;
};
Model.prototype.create = function(options) {
var clone, record;
this.trigger('beforeCreate', options);
if (!this.id) {
this.id = this.cid;
}
record = this.dup(false);
this.constructor.records[this.id] = record;
this.constructor.crecords[this.cid] = record;
clone = record.clone();
clone.trigger('create', options);
clone.trigger('change', 'create', options);
return clone;
};
Model.prototype.bind = function(events, callback) {
var binder, unbinder,
_this = this;
this.constructor.bind(events, binder = function(record) {
if (record && _this.eql(record)) {
return callback.apply(_this, arguments);
}
});
this.constructor.bind('unbind', unbinder = function(record) {
if (record && _this.eql(record)) {
_this.constructor.unbind(events, binder);
return _this.constructor.unbind('unbind', unbinder);
}
});
return binder;
};
Model.prototype.one = function(events, callback) {
var binder,
_this = this;
return binder = this.bind(events, function() {
_this.constructor.unbind(events, binder);
return callback.apply(_this, arguments);
});
};
Model.prototype.trigger = function() {
var args, _ref;
args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
args.splice(1, 0, this);
return (_ref = this.constructor).trigger.apply(_ref, args);
};
Model.prototype.unbind = function() {
return this.trigger('unbind');
};
return Model;
})(Module);
Controller = (function(_super) {
__extends(Controller, _super);
Controller.include(Events);
Controller.include(Log);
Controller.prototype.eventSplitter = /^(\S+)\s*(.*)$/;
Controller.prototype.tag = 'div';
function Controller(options) {
this.release = __bind(this.release, this);
var key, value, _ref;
this.options = options;
_ref = this.options;
for (key in _ref) {
value = _ref[key];
this[key] = value;
}
if (!this.el) {
this.el = document.createElement(this.tag);
}
this.el = $(this.el);
this.$el = this.el;
if (this.className) {
this.el.addClass(this.className);
}
if (this.attributes) {
this.el.attr(this.attributes);
}
if (!this.events) {
this.events = this.constructor.events;
}
if (!this.elements) {
this.elements = this.constructor.elements;
}
if (this.events) {
this.delegateEvents(this.events);
}
if (this.elements) {
this.refreshElements();
}
Controller.__super__.constructor.apply(this, arguments);
}
Controller.prototype.release = function() {
this.trigger('release');
this.el.remove();
return this.unbind();
};
Controller.prototype.$ = function(selector) {
return $(selector, this.el);
};
Controller.prototype.delegateEvents = function(events) {
var eventName, key, match, method, selector, _results,
_this = this;
_results = [];
for (key in events) {
method = events[key];
if (typeof method === 'function') {
method = (function(method) {
return function() {
method.apply(_this, arguments);
return true;
};
})(method);
} else {
if (!this[method]) {
throw new Error("" + method + " doesn't exist");
}
method = (function(method) {
return function() {
_this[method].apply(_this, arguments);
return true;
};
})(method);
}
match = key.match(this.eventSplitter);
eventName = match[1];
selector = match[2];
if (selector === '') {
_results.push(this.el.bind(eventName, method));
} else {
_results.push(this.el.delegate(selector, eventName, method));
}
}
return _results;
};
Controller.prototype.refreshElements = function() {
var key, value, _ref, _results;
_ref = this.elements;
_results = [];
for (key in _ref) {
value = _ref[key];
_results.push(this[value] = this.$(key));
}
return _results;
};
Controller.prototype.delay = function(func, timeout) {
return setTimeout(this.proxy(func), timeout || 0);
};
Controller.prototype.html = function(element) {
this.el.html(element.el || element);
this.refreshElements();
return this.el;
};
Controller.prototype.append = function() {
var e, elements, _ref;
elements = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
elements = (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = elements.length; _i < _len; _i++) {
e = elements[_i];
_results.push(e.el || e);
}
return _results;
})();
(_ref = this.el).append.apply(_ref, elements);
this.refreshElements();
return this.el;
};
Controller.prototype.appendTo = function(element) {
this.el.appendTo(element.el || element);
this.refreshElements();
return this.el;
};
Controller.prototype.prepend = function() {
var e, elements, _ref;
elements = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
elements = (function() {
var _i, _len, _results;
_results = [];
for (_i = 0, _len = elements.length; _i < _len; _i++) {
e = elements[_i];
_results.push(e.el || e);
}
return _results;
})();
(_ref = this.el).prepend.apply(_ref, elements);
this.refreshElements();
return this.el;
};
Controller.prototype.replace = function(element) {
var previous, _ref;
_ref = [this.el, $(element.el || element)], previous = _ref[0], this.el = _ref[1];
previous.replaceWith(this.el);
this.delegateEvents(this.events);
this.refreshElements();
return this.el;
};
return Controller;
})(Module);
$ = (typeof window !== "undefined" && window !== null ? window.jQuery : void 0) || (typeof window !== "undefined" && window !== null ? window.Zepto : void 0) || function(element) {
return element;
};
createObject = Object.create || function(o) {
var Func;
Func = function() {};
Func.prototype = o;
return new Func();
};
isArray = function(value) {
return Object.prototype.toString.call(value) === '[object Array]';
};
isBlank = function(value) {
var key;
if (!value) {
return true;
}
for (key in value) {
return false;
}
return true;
};
makeArray = function(args) {
return Array.prototype.slice.call(args, 0);
};
Spine = this.Spine = {};
if (typeof module !== "undefined" && module !== null) {
module.exports = Spine;
}
Spine.version = '1.0.8';
Spine.isArray = isArray;
Spine.isBlank = isBlank;
Spine.$ = $;
Spine.Events = Events;
Spine.Log = Log;
Spine.Module = Module;
Spine.Controller = Controller;
Spine.Model = Model;
Module.extend.call(Spine, Events);
Module.create = Module.sub = Controller.create = Controller.sub = Model.sub = function(instances, statics) {
var result;
result = (function(_super) {
__extends(result, _super);
function result() {
return result.__super__.constructor.apply(this, arguments);
}
return result;
})(this);
if (instances) {
result.include(instances);
}
if (statics) {
result.extend(statics);
}
if (typeof result.unbind === "function") {
result.unbind();
}
return result;
};
Model.setup = function(name, attributes) {
var Instance;
if (attributes == null) {
attributes = [];
}
Instance = (function(_super) {
__extends(Instance, _super);
function Instance() {
return Instance.__super__.constructor.apply(this, arguments);
}
return Instance;
})(this);
Instance.configure.apply(Instance, [name].concat(__slice.call(attributes)));
return Instance;
};
Spine.Class = Module;
}).call(this);
</script>
<script type="text/javascript">
/*global Ply, jQuery */
/*jshint eqeqeq: true, curly: true, white: true */
// **Ply** is a lightweight JavaScript framework for creating
// reusable UI components and managing application logic. It
// aims to eliminate boilerplate code, create a consistent interface,
// and provide common patterns for code organization and re-use.
// Ply is not an MVC framework and doesn't aim to compete
// with [Backbone.js](http://documentcloud.github.com/backbone/) or [SproutCore](http://www.sproutcore.com/). Instead, it is intended for users
// who need to maintain logic on the server and are looking for a better
// option for code re-use and organization.
// This file comprises the Core module.
// Declare global namespace and assign version number.
window.Ply = {
VERSION: '0.3.4'
};
// Define `core` module.
Ply.core = (function ($) {
// ## Private Variables
// Create private variables. `listeners` is an associative array holding arrays of
// notification listeners keyed on the notification name.
var listeners = {},
// id used to uniquely identify listeners for use in ignore.
id = 0,
debug = false;
// ## Public Methods/Properties
return {
// ### Notify
// Notifies listeners of an event. Notifiers should send themselves
// and optional data as arguments.
notify: function (note, sender, data) {
// Cache listeners array or create a new array, assign, and cache it.
var list = listeners[note] || (listeners[note] = []),
// Create loop variables.
i = 0,
len = list.length;
// Loop over listeners and notify each.
for (; i < len; i++) {
list[i].handler.call(list[i].listener, note, sender, data);
}
},
// ### Listen
// Listens for a particular notification or set of notifications.
// Clients should pass in a handler function and themselves as arguments.
// When the handler function is called, it will be applied to the `listener`'s
// scope, ensuring that `this` refers to what the client expects.
listen: function (notification, handler, listener) {
// Cache the notification's listeners if it exists or create and cache
// a new array otherwise.
var list = listeners[notification] || (listeners[notification] = []),
// Split the notification on whitespace. Clients can listen to
// multiple notifications by passing in a string with the notification
// names split by whitespace.
notes = notification.split(/\s/),
// Create loop variables.
len = notes.length,
i = 0;
// If the notification name contains whitespace,
// listen on each particular notification (segment).
if (len > 1) {
for (; i < len; i++) {
this.listen(notes[i], handler, listener);
}
return;
}
// Add the listener and handler function to the notifications array.
list.push({
id: id += 1,
handler: handler,
listener: listener
});
// return handle used to ignore.
return [notification, id];
},
// ### Ignore
// Removes a particular notification from listeners object.
// Clients should pass in the returned handle from `Ply.core.listen`.
ignore: function (handle) {
var note = handle[0];
listeners[note] && $.each(listeners[note], function(i) {
if(this.id == handle[1]){
listeners[note].splice(i, 1);
}
});
return;
},
// ### Log
// Lightweight logging wrapper around `console`. Useful less so
// for debugging than for posting notices of interest.
log: function (msg, type) {
// Do nothing if debug mode is disabled.
if (!debug) {
return;
}
// Use the correct logging mechanism based
// on the parameter type.
if (window.console) {
switch (type) {
case 'warn':
console.warn(msg);
break;
case 'info':
console.info(msg);
break;
default:
console.log(msg);
break;
}
}
},
// ### Error
// Method to catch JavaScript errors. All Ply methods are run in a `try/catch` block,
// with exceptions being passed to this method.
error: function (ex, sev) {
// If an `onError` callback function is defined in `Ply.config.core`, call it.
// Note that implementing this callback is highly recommended. See the sample implementation
// in `Ply.config`.
if (Ply.config.core.onError && typeof Ply.config.core.onError === 'function') {
// Pass in the exception and the severity to the handler.
Ply.config.core.onError(ex, sev);
return;
}
// If no error handler is defined, simply throw the error.
throw ex;
},
// ### Debug
// Enabled debugging when called with no argues or with any truthy value. To disable debug mode
// send `false` or another falsy value to this function, e.g.: `Ply.core.debug(0)`.
debug: function (val) {
// Default to `true` if no arguments.
debug = typeof val === 'undefined' ? true : val;
if (debug) {
// Print debug notice.
this.log('debugging...', 'info');
// Manually set `this.debugOn` in case debug is set after Ply has been initialized.
// `debugOn` will still be cached to the old value.
this.debugOn = true;
}
else {
// Do the opposite.
this.debugOn = false;
}
},
// ### Debug on
// Cache the value of debug. This is used by clients to determine
// if debug mode is currently enabled.
debugOn: function () {
// Coerce the value to a boolean.
return !!debug;
}()
};
// Alias `jQuery` to `$` in module scope.
})(jQuery);
// ↪ [Ajax](ajax.html)
</script>
<script type="text/javascript">
/*
Copyright (c) 2010,2011,2012 Morgan Roderick http://roderick.dk
License: MIT - http://mrgnrdrck.mit-license.org
https://github.com/mroderick/PubSubJS
*/
/*global
setTimeout,
module,
exports,
define,
window
*/
(function (name, global, definition){
"use strict";
if (typeof module !== 'undefined'){
module.exports = definition(name, global);
} else if (typeof define === 'function' && typeof define.amd === 'object'){
define(definition);
} else {
global[name] = definition(name, global);
}
}('PubSub', ( typeof window !== 'undefined' && window ) || this, function definition(name, global){
"use strict";
var PubSub = {
name: 'PubSubJS',
version: '1.3.1-dev'
},
messages = {},
lastUid = -1;
/**
* Returns a function that throws the passed exception, for use as argument for setTimeout
* @param { Object } ex An Error object
*/
function throwException( ex ){
return function reThrowException(){
throw ex;
};
}
function callSubscriber( subscriber, message, data ){
try {
subscriber( message, data );
} catch( ex ){
setTimeout( throwException( ex ), 0);
}
}
function deliverMessage( originalMessage, matchedMessage, data ){
var subscribers = messages[matchedMessage],
i, j;
if ( !messages.hasOwnProperty( matchedMessage ) ) {
return;
}
for ( i = 0, j = subscribers.length; i < j; i++ ){
callSubscriber( subscribers[i].func, originalMessage, data );
}
}
function createDeliveryFunction( message, data ){
return function deliverNamespaced(){
var topic = String( message ),
position = topic.lastIndexOf( '.' );
// deliver the message as it is now
deliverMessage(message, message, data);
// trim the hierarchy and deliver message to each level
while( position !== -1 ){
topic = topic.substr( 0, position );
position = topic.lastIndexOf('.');
deliverMessage( message, topic, data );
}
};
}
function messageHasSubscribers( message ){
var topic = String( message ),
found = messages.hasOwnProperty( topic ),
position = topic.lastIndexOf( '.' );
while ( !found && position !== -1 ){
topic = topic.substr( 0, position );
position = topic.lastIndexOf('.');
found = messages.hasOwnProperty( topic );
}
return found;
}
function publish( message, data, sync ){
var deliver = createDeliveryFunction( message, data ),
hasSubscribers = messageHasSubscribers( message );
if ( !hasSubscribers ){
return false;
}
if ( sync === true ){
deliver();
} else {
setTimeout( deliver, 0 );
}
return true;
}
/**
* PubSub.publish( message[, data] ) -> Boolean
* - message (String): The message to publish
* - data: The data to pass to subscribers
* Publishes the the message, passing the data to it's subscribers
**/
PubSub.publish = function( message, data ){
return publish( message, data, false );
};
/**
* PubSub.publishSync( message[, data] ) -> Boolean
* - message (String): The message to publish
* - data: The data to pass to subscribers
* Publishes the the message synchronously, passing the data to it's subscribers
**/
PubSub.publishSync = function( message, data ){
return publish( message, data, true );
};
/**
* PubSub.subscribe( message, func ) -> String
* - message (String): The message to subscribe to
* - func (Function): The function to call when a new message is published
* Subscribes the passed function to the passed message. Every returned token is unique and should be stored if
* you need to unsubscribe
**/
PubSub.subscribe = function( message, func ){
// message is not registered yet
if ( !messages.hasOwnProperty( message ) ){
messages[message] = [];
}
// forcing token as String, to allow for future expansions without breaking usage
// and allow for easy use as key names for the 'messages' object
var token = String(++lastUid);
messages[message].push( { token : token, func : func } );
// return token for unsubscribing
return token;
};
/**
* PubSub.unsubscribe( tokenOrFunction ) -> String | Boolean
* - tokenOrFunction (String|Function): The token of the function to unsubscribe or func passed in on subscribe
* Unsubscribes a specific subscriber from a specific message using the unique token
* or if using Function as argument, it will remove all subscriptions with that function
**/
PubSub.unsubscribe = function( tokenOrFunction ){
var isToken = typeof tokenOrFunction === 'string',
key = isToken ? 'token' : 'func',
succesfulReturnValue = isToken ? tokenOrFunction : true,
result = false,
m, i, j;
for ( m in messages ){
if ( messages.hasOwnProperty( m ) ){
for ( i = messages[m].length-1 ; i >= 0; i-- ){
if ( messages[m][i][key] === tokenOrFunction ){
messages[m].splice( i, 1 );
result = succesfulReturnValue;
// tokens are unique, so we can just return here
if ( isToken ){
return result;
}
}
}
}
}
return result;
};
return PubSub;
}));
</script>
<script type="text/javascript">
window.iter = 0
window.callback = function() {
iter++
};
window.payload = {
somekey: 'some value'
};
jQuery(function() {
jQuery(window).on('app', callback);
jQuery(window).on('app/region', callback);
jQuery(window).on('app/region/module', callback);
jQuery(window).on('app/region/module/event', callback);
PubSub.subscribe('app', callback);
PubSub.subscribe('app.region', callback);
PubSub.subscribe('app.region.module', callback);
PubSub.subscribe('app.region.module.event', callback);
subtopic.subscribe('app', callback);
subtopic.subscribe('app/region', callback);
subtopic.subscribe('app/region/module', callback);
subtopic.subscribe('app/region/module/event', callback);
Events.subscribe('app', callback);
Events.subscribe('app/region', callback);
Events.subscribe('app/region/module', callback);
Events.subscribe('app/region/module/event', callback);
amplify.subscribe('app', callback);
amplify.subscribe('app/region', callback);
amplify.subscribe('app/region/module', callback);
amplify.subscribe('app/region/module/event', callback);
Spine.bind('app', callback);
Spine.bind('app/region', callback);
Spine.bind('app/region/module', callback);
Spine.bind('app/region/module/event', callback);
Ply.core.listen('app', callback);
Ply.core.listen('app/region', callback);
Ply.core.listen('app/region/module', callback);
Ply.core.listen('app/region/module/event', callback);
BSI.topic.subscribe('app', callback);
BSI.topic.subscribe('app.region', callback);
BSI.topic.subscribe('app.region.module', callback);
BSI.topic.subscribe('app.region.module.event', callback);
});
</script>
window.iter = 0
Ready to run.
Test | Ops/sec | |
---|---|---|
jQuery Events |
| ready |
PubSubJS - async |
| ready |
PubSubJS - sync |
| ready |
Pure JS PubSub |
| ready |
Amplify Pub/Sub |
| ready |
Spine Events |
| ready |
Ply Notify/Listen |
| ready |
Subtopic |
| ready |
BSI topic |
| ready |
You can edit these tests or add more tests to this page by appending /edit to the URL.