hyperion.ng/assets/webconfig/js/vendor/stapes.js

595 lines
18 KiB
JavaScript

//
// ____ _ _
// / ___|| |_ __ _ _ __ ___ ___ (_)___ (*)
// \___ \| __/ _` | '_ \ / _ \/ __| | / __|
// ___) | || (_| | |_) | __/\__ \_ | \__ \
// |____/ \__\__,_| .__/ \___||___(_)/ |___/
// |_| |__/
//
// (*) a (really) tiny Javascript MVC microframework
//
// (c) Hay Kranen < hay@bykr.org >
// 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 VERSION = "0.8.0";
// Global counter for all events in all modules (including mixed in objects)
var guid = 1;
// Makes _.create() faster
if (!Object.create) {
var CachedFunction = function(){};
}
// So we can use slice.call for arguments later on
var slice = Array.prototype.slice;
// Private attributes and helper functions, stored in an object so they
// are overwritable by plugins
var _ = {
// Properties
attributes : {},
eventHandlers : {
"-1" : {} // '-1' is used for the global event handling
},
guid : -1,
// Methods
addEvent : function(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 (!_.eventHandlers[event.guid][event.type]) {
_.eventHandlers[event.guid][event.type] = [];
}
// Push an event object
_.eventHandlers[event.guid][event.type].push({
"guid" : event.guid,
"handler" : event.handler,
"scope" : event.scope,
"type" : event.type
});
},
addEventHandler : function(argTypeOrMap, argHandlerOrScope, argScope) {
var eventMap = {},
scope;
if (typeof argTypeOrMap === "string") {
scope = argScope || false;
eventMap[ argTypeOrMap ] = argHandlerOrScope;
} else {
scope = argHandlerOrScope || false;
eventMap = argTypeOrMap;
}
for (var eventString in eventMap) {
var handler = eventMap[eventString];
var events = eventString.split(" ");
for (var i = 0, l = events.length; i < l; i++) {
var eventType = events[i];
_.addEvent.call(this, {
"guid" : this._guid || this._.guid,
"handler" : handler,
"scope" : scope,
"type" : eventType
});
}
}
},
addGuid : function(object, forceGuid) {
if (object._guid && !forceGuid) return;
object._guid = guid++;
_.attributes[object._guid] = {};
_.eventHandlers[object._guid] = {};
},
// This is a really small utility function to save typing and produce
// better optimized code
attr : function(guid) {
return _.attributes[guid];
},
clone : function(obj) {
var type = _.typeOf(obj);
if (type === 'object') {
return _.extend({}, obj);
}
if (type === 'array') {
return obj.slice(0);
}
},
create : function(proto) {
if (Object.create) {
return Object.create(proto);
} else {
CachedFunction.prototype = proto;
return new CachedFunction();
}
},
createSubclass : function(props, includeEvents) {
props = props || {};
includeEvents = includeEvents || false;
var superclass = props.superclass.prototype;
// Objects always have a constructor, so we need to be sure this is
// a property instead of something from the prototype
var realConstructor = props.hasOwnProperty('constructor') ? props.constructor : function(){};
function constructor() {
// Be kind to people forgetting new
if (!(this instanceof constructor)) {
throw new Error("Please use 'new' when initializing Stapes classes");
}
// If this class has events add a GUID as well
if (this.on) {
_.addGuid( this, true );
}
realConstructor.apply(this, arguments);
}
if (includeEvents) {
_.extend(superclass, Events);
}
constructor.prototype = _.create(superclass);
constructor.prototype.constructor = constructor;
_.extend(constructor, {
extend : function() {
return _.extendThis.apply(this, arguments);
},
// We can't call this 'super' because that's a reserved keyword
// and fails in IE8
'parent' : superclass,
proto : function() {
return _.extendThis.apply(this.prototype, arguments);
},
subclass : function(obj) {
obj = obj || {};
obj.superclass = this;
return _.createSubclass(obj);
}
});
// Copy all props given in the definition to the prototype
for (var key in props) {
if (key !== 'constructor' && key !== 'superclass') {
constructor.prototype[key] = props[key];
}
}
return constructor;
},
emitEvents : function(type, data, explicitType, explicitGuid) {
explicitType = explicitType || false;
explicitGuid = explicitGuid || this._guid;
// #30: make a local copy of handlers to prevent problems with
// unbinding the event while unwinding the loop
var handlers = slice.call(_.eventHandlers[explicitGuid][type]);
for (var i = 0, l = handlers.length; i < l; i++) {
// Clone the event to prevent issue #19
var event = _.extend({}, handlers[i]);
var scope = (event.scope) ? event.scope : this;
if (explicitType) {
event.type = explicitType;
}
event.scope = scope;
event.handler.call(event.scope, data, event);
}
},
// Extend an object with more objects
extend : function() {
var args = slice.call(arguments);
var object = args.shift();
for (var i = 0, l = args.length; i < l; i++) {
var props = args[i];
for (var key in props) {
object[key] = props[key];
}
}
return object;
},
// The same as extend, but uses the this value as the scope
extendThis : function() {
var args = slice.call(arguments);
args.unshift(this);
return _.extend.apply(this, args);
},
// 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);
});
},
removeAttribute : function(keys, silent) {
silent = silent || false;
// Split the key, maybe we want to remove more than one item
var attributes = _.trim(keys).split(" ");
// Actually delete the item
for (var i = 0, l = attributes.length; i < l; i++) {
var key = _.trim(attributes[i]);
if (key) {
delete _.attr(this._guid)[key];
// If 'silent' is set, do not throw any events
if (!silent) {
this.emit('change', key);
this.emit('change:' + key);
this.emit('remove', key);
this.emit('remove:' + key);
}
}
}
},
removeEventHandler : function(type, handler) {
var handlers = _.eventHandlers[this._guid];
if (type && handler) {
// Remove a specific handler
handlers = handlers[type];
if (!handlers) return;
for (var i = 0, l = handlers.length, h; i < l; i++) {
h = handlers[i].handler;
if (h && h === handler) {
handlers.splice(i--, 1);
l--;
}
}
} else if (type) {
// Remove all handlers for a specific type
delete handlers[type];
} else {
// Remove all handlers for this module
_.eventHandlers[this._guid] = {};
}
},
setAttribute : function(key, value, silent) {
silent = silent || false;
// We need to do this before we actually add the item :)
var itemExists = this.has(key);
var oldValue = _.attr(this._guid)[key];
// Is the value different than the oldValue? If not, ignore this call
if (value === oldValue) {
return;
}
// Actually add the item to the attributes
_.attr(this._guid)[key] = value;
// If 'silent' flag is set, do not throw any events
if (silent) {
return;
}
// Throw a generic event
this.emit('change', key);
// And a namespaced event as well, NOTE that we pass value instead of
// key here!
this.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
var mutateData = {
"key" : key,
"newValue" : value,
"oldValue" : oldValue || null
};
this.emit('mutate', mutateData);
this.emit('mutate:' + key, mutateData);
// Also throw a specific event for this type of set
var specificEvent = itemExists ? 'update' : 'create';
this.emit(specificEvent, key);
// And a namespaced event as well, NOTE that we pass value instead of key
this.emit(specificEvent + ':' + key, value);
},
trim : function(str) {
return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
},
typeOf : function(val) {
if (val === null || typeof val === "undefined") {
// This is a special exception for IE, in other browsers the
// method below works all the time
return String(val);
} else {
return Object.prototype.toString.call(val).replace(/\[object |\]/g, '').toLowerCase();
}
},
updateAttribute : function(key, fn, silent) {
var item = this.get(key);
// In previous versions of Stapes we didn't have the check for object,
// but still this worked. In 0.7.0 it suddenly doesn't work anymore and
// we need the check. Why? I have no clue.
var type = _.typeOf(item);
if (type === 'object' || type === 'array') {
item = _.clone(item);
}
var newValue = fn.call(this, item, key);
_.setAttribute.call(this, key, newValue, silent || false);
}
};
// Can be mixed in later using Stapes.mixinEvents(object);
var Events = {
emit : function(types, data) {
data = (typeof data === "undefined") ? null : data;
var splittedTypes = types.split(" ");
for (var i = 0, l = splittedTypes.length; i < l; i++) {
var type = splittedTypes[i];
// First 'all' type events: is there an 'all' handler in the
// global stack?
if (_.eventHandlers[-1].all) {
_.emitEvents.call(this, "all", data, type, -1);
}
// Catch all events for this type?
if (_.eventHandlers[-1][type]) {
_.emitEvents.call(this, type, data, type, -1);
}
if (typeof this._guid === 'number') {
// 'all' event for this specific module?
if (_.eventHandlers[this._guid].all) {
_.emitEvents.call(this, "all", data, type);
}
// Finally, normal events :)
if (_.eventHandlers[this._guid][type]) {
_.emitEvents.call(this, type, data);
}
}
}
},
off : function() {
_.removeEventHandler.apply(this, arguments);
},
on : function() {
_.addEventHandler.apply(this, arguments);
}
};
_.Module = function() {
};
_.Module.prototype = {
each : function(fn, ctx) {
var attr = _.attr(this._guid);
for (var key in attr) {
var value = attr[key];
fn.call(ctx || this, value, key);
}
},
extend : function() {
return _.extendThis.apply(this, arguments);
},
filter : function(fn) {
var filtered = [];
var attributes = _.attr(this._guid);
for (var key in attributes) {
if ( fn.call(this, attributes[key], key)) {
filtered.push( attributes[key] );
}
}
return filtered;
},
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 _.clone( _.attr(this._guid) );
},
getAllAsArray : function() {
var arr = [];
var attributes = _.attr(this._guid);
for (var key in attributes) {
var value = attributes[key];
if (_.typeOf(value) === "object" && !value.id) {
value.id = key;
}
arr.push(value);
}
return arr;
},
has : function(key) {
return (typeof _.attr(this._guid)[key] !== "undefined");
},
map : function(fn, ctx) {
var mapped = [];
this.each(function(value, key) {
mapped.push( fn.call(ctx || this, value, key) );
}, ctx || this);
return mapped;
},
// Akin to set(), but makes a unique id
push : function(input, silent) {
if (_.typeOf(input) === "array") {
for (var i = 0, l = input.length; i < l; i++) {
_.setAttribute.call(this, _.makeUuid(), input[i], silent || false);
}
} else {
_.setAttribute.call(this, _.makeUuid(), input, silent || false);
}
return this;
},
remove : function(input, silent) {
if (typeof input === 'undefined') {
// With no arguments, remove deletes all attributes
_.attributes[this._guid] = {};
this.emit('change remove');
} else if (typeof input === "function") {
this.each(function(item, key) {
if (input(item)) {
_.removeAttribute.call(this, key, silent);
}
});
} else {
// nb: checking for exists happens in removeAttribute
_.removeAttribute.call(this, input, silent || false);
}
return this;
},
set : function(objOrKey, valueOrSilent, silent) {
if (typeof objOrKey === "object") {
for (var key in objOrKey) {
_.setAttribute.call(this, key, objOrKey[key], valueOrSilent || false);
}
} else {
_.setAttribute.call(this, objOrKey, valueOrSilent, silent || false);
}
return this;
},
size : function() {
var size = 0;
var attr = _.attr(this._guid);
for (var key in attr) {
size++;
}
return size;
},
update : function(keyOrFn, fn, silent) {
if (typeof keyOrFn === "string") {
_.updateAttribute.call(this, keyOrFn, fn, silent || false);
} else if (typeof keyOrFn === "function") {
this.each(function(value, key) {
_.updateAttribute.call(this, key, keyOrFn);
});
}
return this;
}
};
var Stapes = {
"_" : _, // private helper functions and properties
"extend" : function() {
return _.extendThis.apply(_.Module.prototype, arguments);
},
"mixinEvents" : function(obj) {
obj = obj || {};
_.addGuid(obj);
return _.extend(obj, Events);
},
"on" : function() {
_.addEventHandler.apply(this, arguments);
},
"subclass" : function(obj, classOnly) {
classOnly = classOnly || false;
obj = obj || {};
obj.superclass = classOnly ? function(){} : _.Module;
return _.createSubclass(obj, !classOnly);
},
"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;
}
})();