Created
May 11, 2012 10:52
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ____ _ _ | |
// / ___|| |_ __ _ _ __ ___ ___ (_)___ (*) | |
// \___ \| __/ _` | '_ \ / _ \/ __| | / __| | |
// ___) | || (_| | |_) | __/\__ \_ | \__ \ | |
// |____/ \__\__,_| .__/ \___||___(_)/ |___/ | |
// |_| |__/ | |
// | |
// (*) 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