Skip to content

Instantly share code, notes, and snippets.

@tubalmartin
Created May 11, 2012 10:52
Show Gist options
  • Save tubalmartin/2658948 to your computer and use it in GitHub Desktop.
Save tubalmartin/2658948 to your computer and use it in GitHub Desktop.
A tinier stapes.js - 406 bytes smaller when compressed with UglifyJS - It can be further reduced in size
//
// ____ _ _
// / ___|| |_ __ _ _ __ ___ ___ (_)___ (*)
// \___ \| __/ _` | '_ \ / _ \/ __| | / __|
// ___) | || (_| | |_) | __/\__ \_ | \__ \
// |____/ \__\__,_| .__/ \___||___(_)/ |___/
// |_| |__/
//
// (*) a (really) tiny Javascript MVC microframework
//
// (c) Hay Kranen < [email protected] >
// Released under the terms of the MIT license
// < http://en.wikipedia.org/wiki/MIT_License >
//
// Stapes.js : http://hay.github.com/stapes
(function() {
'use strict';
var FALSE = !1,
NULL = null,
EMIT = "emit",
EACH = "each",
CALL = "call",
_GUID = "_guid",
APPLY = "apply",
LENGTH = "length",
CREATE = "create",
IS_ARRAY = "isArray",
FUNCTION = "function",
IS_OBJECT = "isObject",
PROTOTYPE = "prototype",
UNDEFINED = "undefined",
_ATTRIBUTES = "_attributes",
_EVENTHANDLERS = "_eventHandlers",
ARRAY_PROTOTYPE = ARRAY[PROTOTYPE],
VERSION = "0.4",
/** Utility functions
*
* These are more or less modelled on the ones used in Underscore.js,
* but might not be as extensive or failproof.
* However, they are pretty damn useful and can be accessed by using
* the Stapes.util global
*/
util = {
"bind" : function(fn, ctx) {
if (Function[PROTOTYPE].bind) {
// Native
return fn.bind(ctx);
} else {
// Non-native
return function() {
return fn[APPLY](ctx, arguments);
};
}
},
"clone" : function(obj) {
if (util[IS_ARRAY](obj)) {
return obj.slice();
} else if (util[IS_OBJECT](obj)) {
var newObj = {};
util[EACH](obj, function(value, key) {
newObj[key] = value;
});
return newObj;
} else {
return obj;
}
},
"create" : function(context) {
var instance;
if (typeof Object[CREATE] === FUNCTION) {
// Native
instance = Object[CREATE](context);
} else {
// Non-native
var F = function(){};
F[PROTOTYPE] = context;
instance = new F();
}
return instance;
},
"each" : function(list, fn, context) {
if (util[IS_ARRAY](list)) {
if (ARRAY_PROTOTYPE.forEach) {
// Native forEach
list.forEach( fn, context );
} else {
for (var i = 0, l = list[LENGTH]; i < l; i++) {
fn[CALL](context, list[i], i);
}
}
} else {
for (var key in list) {
fn[CALL](context, list[key], key);
}
}
},
"filter" : function(list, fn, context) {
var results = [];
if (util[IS_ARRAY](list) && ARRAY_PROTOTYPE.filter) {
return list.filter(fn, context);
}
util[EACH](list, function(value) {
if (fn[CALL](context, value)) {
results.push(value);
}
});
return results;
},
"isArray" : function(val) {
return util.typeOf(val) === "array";
},
"isObject" : function(val) {
return util.typeOf(val) === "object";
},
"keys" : function(list) {
return util.map(list, function(value, key) {
return key;
});
},
// from http://stackoverflow.com/a/2117523/152809
"makeUuid" : function() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
},
"map" : function(list, fn, context) {
var results = [];
if (util[IS_ARRAY](list) && ARRAY_PROTOTYPE.map) {
return list.map(fn, context);
}
util[EACH](list, function(value, index) {
results.push( fn[CALL](context, value, index) );
});
return results;
},
"size" : function(list) {
return (util[IS_ARRAY](list)) ? list[LENGTH] : util.keys(list)[LENGTH];
},
"toArray" : function(val) {
if (util[IS_OBJECT](val)) {
return util.values(val);
} else {
return ARRAY_PROTOTYPE.slice[CALL](val, 0);
}
},
"typeOf" : function(val) {
return Object[PROTOTYPE].toString[CALL](val).replace(/\[object |\]/g, '').toLowerCase();
},
"values" : function(list) {
return util.map(list, function(value, key) {
return value;
});
}
};
/** Private helper functions */
function addEvent(event) {
// If we don't have any handlers for this type of event, add a new
// array we can use to push new handlers
if (!Stapes[_EVENTHANDLERS][event.guid][event.type]) {
Stapes[_EVENTHANDLERS][event.guid][event.type] = [];
}
// Push an event object
Stapes[_EVENTHANDLERS][event.guid][event.type].push({
"guid" : event.guid,
"handler" : event.handler,
"scope" : event.scope,
"type" : event.type
});
}
function addEventHandler(argTypeOrMap, argHandlerOrScope, argScope) {
var eventMap = {},
scope;
if (typeof argTypeOrMap === "string") {
scope = argScope || FALSE;
eventMap[ argTypeOrMap ] = argHandlerOrScope;
} else {
scope = argHandlerOrScope || FALSE;
eventMap = argTypeOrMap;
}
util[EACH](eventMap, function(handler, eventString) {
var events = eventString.split(" ");
util[EACH](events, function(eventType) {
addEvent[CALL](this, {
"guid" : this[_GUID],
"handler" : handler,
"scope" : scope,
"type" : eventType
});
}, this);
}, this);
}
// This is a really small utility function to save typing and producing
// better optimized code
function attr(guid) {
return Stapes[_ATTRIBUTES][guid];
}
// Stapes objects have some extra properties that are set on creation
function createModule( context ) {
var instance = util[CREATE]( context );
instance[_GUID] = guid++;
Stapes[_ATTRIBUTES][instance[_GUID]] = {};
Stapes[_EVENTHANDLERS][instance[_GUID]] = {};
return instance;
}
function emitEvents(type, data, explicitType, explicitGuid) {
explicitType = explicitType || FALSE;
explicitGuid = explicitGuid || this[_GUID];
util[EACH](Stapes[_EVENTHANDLERS][explicitGuid][type], function(event) {
var scope = (event.scope) ? event.scope : this;
if (explicitType) {
event.type = explicitType;
}
event.scope = scope;
event.handler[CALL](event.scope, data, event);
}, this);
}
function setAttribute(key, value) {
// We need to do this before we actually add the item :)
var module = this,
itemExists = module.has(key),
oldValue = attr(module[_GUID])[key],
mutateData = {
"key" : key,
"newValue" : value,
"oldValue" : oldValue || NULL
},
specificEvent = itemExists ? 'update' : CREATE;
// Is the value different than the oldValue? If not, ignore this call
if (value === oldValue) {
return;
}
// Actually add the item to the attributes
attr(module[_GUID])[key] = value;
// Throw a generic event
module[EMIT]('change', key);
// And a namespaced event as well, NOTE that we pass value instead of
// key here!
module[EMIT]('change:' + key, value);
// Throw namespaced and non-namespaced 'mutate' events as well with
// the old value data as well and some extra metadata such as the key
module[EMIT]('mutate', mutateData);
module[EMIT]('mutate:' + key, mutateData);
// Also throw a specific event for this type of set
module[EMIT](specificEvent, key);
// And a namespaced event as well, NOTE that we pass value instead of key
module[EMIT](specificEvent + ':' + key, value);
}
function updateAttribute(key, fn) {
var item = this.get(key),
newValue = fn( util.clone(item) );
setAttribute[CALL](this, key, newValue);
}
var guid = 1,
Module = {
create : function() {
return createModule( this );
},
each : function(fn, ctx) {
util[EACH](attr(this[_GUID]), fn, ctx || this);
},
emit : function(types, data) {
data = (typeof data === UNDEFINED) ? NULL : data;
util[EACH](types.split(" "), function(type) {
// First 'all' type events: is there an 'all' handler in the
// global stack?
if (Stapes[_EVENTHANDLERS][-1].all) {
emitEvents[CALL](this, "all", data, type, -1);
}
// Catch all events for this type?
if (Stapes[_EVENTHANDLERS][-1][type]) {
emitEvents[CALL](this, type, data, type, -1);
}
if (typeof this[_GUID] === 'number') {
// 'all' event for this specific module?
if (Stapes[_EVENTHANDLERS][this[_GUID]].all) {
emitEvents[CALL](this, "all", data, type);
}
// Finally, normal events :)
if (Stapes[_EVENTHANDLERS][this[_GUID]][type]) {
emitEvents[CALL](this, type, data);
}
}
}, this);
},
extend : function(objectOrValues, valuesIfObject) {
var object = (valuesIfObject) ? objectOrValues : this,
values = (valuesIfObject) ? valuesIfObject : objectOrValues;
util[EACH](values, function(value, key) {
object[key] = value;
});
return this;
},
filter : function(fn) {
return util.filter(attr(this[_GUID]), fn);
},
get : function(input) {
if (typeof input === "string") {
return this.has(input) ? attr(this[_GUID])[input] : NULL;
} else if (typeof input === FUNCTION) {
var items = this.filter(input);
return (items[LENGTH]) ? items[0] : NULL;
}
},
getAll : function() {
return util.clone( attr(this[_GUID]) );
},
getAllAsArray : function() {
var arr = util.map(attr(this[_GUID]), function(value, key) {
if (util[IS_OBJECT](value)) {
value.id = key;
}
return value;
});
return util.clone( arr );
},
has : function(key) {
return (typeof attr(this[_GUID])[key] !== UNDEFINED);
},
on : function() {
addEventHandler[APPLY](this, arguments);
},
// Akin to set(), but makes a unique id
push : function(input) {
if (util[IS_ARRAY](input)) {
util[EACH](input, function(value) {
setAttribute[CALL](this, util.makeUuid(), value);
}, this);
} else {
setAttribute[CALL](this, util.makeUuid(), input);
}
},
remove : function(input) {
if (typeof input === FUNCTION) {
this[EACH](function(item, key) {
if (input(item)) {
delete attr(this[_GUID])[key];
this[EMIT]('remove change');
}
});
} else {
if (this.has(input)) {
delete attr(this[_GUID])[input];
this[EMIT]('remove change');
}
}
},
set : function(objOrKey, value) {
if (util[IS_OBJECT](objOrKey)) {
util[EACH](objOrKey, function(value, key) {
setAttribute[CALL](this, key, value);
}, this);
} else {
setAttribute[CALL](this, objOrKey, value);
}
},
size : function() {
return util.size( Stapes[_ATTRIBUTES][this[_GUID]] );
},
update : function(keyOrFn, fn) {
if (typeof keyOrFn === "string") {
updateAttribute[CALL](this, keyOrFn, fn);
} else if (typeof keyOrFn === FUNCTION) {
this[EACH](function(value, key) {
updateAttribute[CALL](this, key, keyOrFn);
});
}
}
},
Stapes = {
"_attributes" : {},
"_eventHandlers" : {
"-1" : {} // '-1' is used for the global event handling
},
"_guid" : -1,
"create" : function() {
return createModule( Module );
},
"extend" : function(obj) {
util[EACH](obj, function(value, key) {
Module[key] = value;
});
},
"on" : function() {
addEventHandler[APPLY](this, arguments);
},
"util" : util,
"version" : VERSION
};
// This library can be used as an AMD module, a Node.js module, or an
// old fashioned global
if (typeof exports !== UNDEFINED) {
// Server
if (typeof module !== UNDEFINED && module.exports) {
exports = module.exports = Stapes;
}
exports.Stapes = Stapes;
} else if (typeof define === FUNCTION && define.amd) {
// AMD
define(function() {
return Stapes;
});
} else {
// Global scope
window.Stapes = Stapes;
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment