Skip to content

Instantly share code, notes, and snippets.

@sweikenb
Created July 17, 2018 12:26
Show Gist options
  • Save sweikenb/27eea47d4f4a719c2e8d88e5957d4b2a to your computer and use it in GitHub Desktop.
Save sweikenb/27eea47d4f4a719c2e8d88e5957d4b2a to your computer and use it in GitHub Desktop.
/**
* strap.js by Simon Schroeer | http://strapjs.org | License: MIT
*/
var strapjs_root = {},
strapjs_eventstack = {},
strapjs = {};
/**
* Get the global root object
*/
(function () {
strapjs_root = this;
strapjs_root.strapjs = strapjs;
})();
/**
* Foreach wrapper for objects and arrays
*
* @param loopable
* @param callback
* @constructor
*/
strapjs.Foreach = function (loopable, callback) {
if (loopable instanceof Array) {
var count = loopable.length, i;
for (i = 0; i < count; ++i) {
if (typeof loopable[i] !== 'undefined') {
if (false === callback(i, loopable[i])) {
return;
}
}
}
}
else {
for (var index in loopable) {
if (loopable.hasOwnProperty(index)) {
if (false === callback(index, loopable[index])) {
return;
}
}
}
}
};
/**
* Detects the length of the given variable.
* If an array or object is given, the elements-count will be returned.
*
* @param countable
* @constructor
*/
strapjs.Length = function (countable) {
var isObject = (typeof countable === 'object');
if (!isObject || countable instanceof Array) {
return countable.length;
}
else {
var count = 0;
strapjs.Foreach(countable, function () {
++count;
});
return count;
}
};
/**
* Fork the script execution for the given callback (in fact it's an parallelisation, not a 'real' fork)
*
* @param callback
* @constructor
*/
strapjs.Fork = function (callback) {
strapjs_root.setTimeout(callback, 0);
};
/**
* Executes the given callbacks in parallel
*
* @param callbacks
* @param callbackFinished
* @param timeout
* @param provideDoneHandler
* @constructor
*/
strapjs.Parallelize = function (callbacks, callbackFinished, timeout, provideDoneHandler) {
var Service = strapjs.Class({
// resolver setup
initialize: function (callbacks, callbackFinished, timeout, provideDoneHandler) {
// set attributes
this.callbackFinished = callbackFinished || null;
this.processesTimeout = timeout ? (parseInt(timeout) + this.getTime()) : 0;
this.processesAll = callbacks.length;
this.processesDone = 0;
this.watchTimer = false;
this.provideDoneHandler = provideDoneHandler || false;
var watchTimeoutNeeded = (this.processesTimeout > 0 && this.callbackFinished);
strapjs.Foreach(callbacks, function (index, callback) {
if (watchTimeoutNeeded) {
strapjs.Fork(this.callbackWrapper.bind(this, callback))
}
else {
strapjs.Fork(callback);
}
}.bind(this));
if (watchTimeoutNeeded) {
this.watchCallbacks();
}
},
getTime: function () {
return Math.floor((new Date()).getTime() / 1000);
},
callbackWrapper: function (callback) {
if (false !== this.provideDoneHandler) {
// run the callback with the custom handler
callback.apply(null, [this._incrementFinished.bind(this)]);
}
else {
// run the callback
callback.apply(null);
// increment finished
this._incrementFinished();
}
},
_incrementFinished: function () {
// increment 'done'-count
++this.processesDone;
// all processes done?
if (this.processesDone >= this.processesAll) {
this.markAsDone();
}
},
watchCallbacks: function () {
// all processes done?
if (this.processesDone >= this.processesAll || this.processesTimeout < this.getTime()) {
this.markAsDone();
}
else {
this.watchTimer = strapjs_root.setTimeout(this.watchCallbacks.bind(this), 10);
}
},
markAsDone: function () {
if (this.watchTimer) {
strapjs_root.clearTimeout(this.watchTimer);
this.watchTimer = false;
}
// need to trigger the finish callback?
if (this.callbackFinished) {
var finished = this.callbackFinished;
this.callbackFinished = null;
// trigger callback
finished(this.processesAll, this.processesDone);
}
}
});
return new Service(callbacks, (callbackFinished || null), (timeout || 0), (provideDoneHandler || false));
};
/**
* Creates a new class with the given implementation and optional parent inheritance
*
* @param parent
* @param implementation
* @returns object
* @constructor
*/
strapjs.Class = function (parent, implementation) {
parent = parent || null;
implementation = implementation || null;
if (!implementation) {
implementation = parent;
parent = null;
}
// create a protoclass
var protoclass = function () {
this.initialize.apply(this, arguments);
};
if (parent) {
if (typeof parent.prototype !== 'undefined') {
parent = parent.prototype;
}
strapjs.Foreach(parent, function (prop, method) {
protoclass.prototype[prop] = method;
});
}
if (implementation) {
strapjs.Foreach(implementation, function (prop, method) {
protoclass.prototype[prop] = method;
});
}
return protoclass;
};
/**
* Registers the given definitions within the namespace
* @param ns
* @param definitions
* @returns {*}
* @constructor
*/
strapjs.Namespace = function (ns, definitions) {
var pointer = null,
path = '',
frags = ns.split('.');
frags.unshift('strapjs_root');
strapjs.Foreach(frags, function (index, space) {
path += (index ? '.' : '') + space;
pointer = eval(path);
if (typeof pointer === 'undefined') {
eval(path + " = {};");
}
});
strapjs.Foreach(definitions, function (prop, definition) {
eval(path + "[prop] = definition;");
});
return eval(path);
};
/**
* Fire the given event with the optional payload
*
* @param eventName
* @param payload
* @constructor
*/
strapjs.EventFire = function (eventName, payload) {
if (typeof eventName !== 'string') {
return;
}
if (typeof strapjs_eventstack[eventName] !== 'undefined') {
var event = {
'name': eventName,
'payload': (payload || null)
};
strapjs.Foreach(strapjs_eventstack[eventName], function (index, callback) {
return callback(event);
});
}
};
/**
* Remove the whole event with all registred subscribers
*
* @param eventName
* @constructor
*/
strapjs.EventErase = function (eventName) {
if (typeof eventName !== 'string') {
return;
}
if (typeof strapjs_eventstack[eventName] !== 'undefined') {
delete strapjs_eventstack[eventName];
}
};
/**
* Subscribe to the given event
*
* @param eventName
* @param callback
* @constructor
*/
strapjs.EventSubscribe = function (eventName, callback) {
if (typeof eventName !== 'string') {
return;
}
if (typeof strapjs_eventstack[eventName] === 'undefined') {
strapjs_eventstack[eventName] = [];
}
strapjs_eventstack[eventName].push(callback);
};
/**
* Unsubscribe to the given event
*
* @param eventName
* @param callback
* @constructor
*/
strapjs.EventUnsubscribe = function (eventName, callback) {
if (typeof eventName !== 'string') {
return;
}
if (typeof strapjs_eventstack[eventName] !== 'undefined') {
strapjs.Foreach(strapjs_eventstack[eventName], function (index, current) {
if (current === callback) {
delete strapjs_eventstack[eventName][index];
if (1 > strapjs_eventstack[eventName].length) {
delete strapjs_eventstack[eventName];
}
}
});
}
};
/**
* Creates a namespace resolver instance
*
* @returns {strapjs.Class}
* @constructor
*/
strapjs.Use = function () {
// create a new namespace resolver
var Resolver = strapjs.Class({
// resolver setup
initialize: function () {
this.namespaces = {};
},
// register the given namespace
registerNamespace: function (ns, alias) {
alias = alias || ns;
if (typeof this.namespaces[alias] !== 'undefined') {
throw "Can't use alias '" + alias + "' for '" + ns + "': already defined.";
}
this.namespaces[alias] = ns;
},
// use (register) the given namespaces
use: function () {
strapjs.Foreach(arguments, function (index, argument) {
switch (typeof argument) {
case 'object':
if (argument instanceof Array) {
strapjs.Foreach(argument, function (subIndex, value) {
this.use(value);
}.bind(this));
}
else {
strapjs.Foreach(argument, function (subIndex, value) {
if (typeof value === 'object') {
this.use(value);
}
else {
this.registerNamespace(value, subIndex);
}
}.bind(this));
}
break;
case 'string':
if (isNaN(parseInt(argument))) {
this.registerNamespace(argument);
}
}
}.bind(this));
// return current instance for chaining
return this;
},
// resolve the given classname and return its definition
get: function (className) {
// keep the matching class
var matchingClass;
// function passed?
if (typeof className === 'function') {
matchingClass = className;
}
if (!matchingClass) {
// direct alias/namespace found?
if (typeof this.namespaces[className] !== 'undefined') {
matchingClass = eval(this.namespaces[className]);
}
if (!matchingClass) {
// check registered namespaces with classes
var pattern = new RegExp('(.*)\\.' + className + '$', 'g');
strapjs.Foreach(this.namespaces, function (alias, ns) {
if (-1 !== alias.search(pattern)) {
matchingClass = eval(ns);
return false;
}
return true;
});
if (!matchingClass) {
// check registered namespaces without classes
strapjs.Foreach(this.namespaces, function (alias, ns) {
matchingClass = eval("strapjs_root." + ns + "." + className);
return (typeof matchingClass === 'undefined');
});
if (!matchingClass) {
// not found
throw "strap.js: Class not found: " + className;
}
}
}
}
return matchingClass;
},
// create an new instance of the given class
create: function (className, params, callback) {
// create instance
var instance = this.get(className).apply(null, params || []);
// trigger callback if required
if (callback) {
callback(instance);
}
// return instance
return instance;
}
});
// pass the given arguments to the resolver and return it
var loader = new Resolver();
loader.use(arguments);
return loader;
};
/**
* Merges two or more objects/arrays into each other and returns the result.
*
* NOTICE: The original objects/arrays WON'T be changed!
*
* If the last parameter is set to 'true', a recursive deep-merge will be done.
*
* @param base
* @param ext
* @param deep
* @constructor
*/
strapjs.Merge = function (base, ext, deep) {
// validate parameter count
var argc = arguments.length;
if (argc < 2) {
return base;
}
// prepare the affected object
var affected = arguments,
deepMerge = (true === deep),
lastPost = (argc - 1);
if (true !== deep && typeof affected[lastPost] === 'boolean') {
deepMerge = (true === affected[lastPost]);
delete affected[lastPost];
--argc;
}
// merge objects
var target = {};
for (var i = 0; i < argc; i++) {
// get the current object
var current = affected[i];
// skipp unprocessable types
if (typeof current !== 'object') {
continue;
}
// merge
strapjs.Foreach(current, function (key, value) {
if (deepMerge && typeof value === 'object') {
target[key] = strapjs.Merge(target[key], value, true);
}
else {
target[key] = value;
}
});
}
// return the merged target
return target;
};
// add shortcuts
if (typeof strapjs_isolation === 'undefined' || strapjs_isolation !== true) {
// support module loaders and define shortcut functions
var exprotEnabled = (typeof module !== 'undefined' && typeof module.exports !== 'undefined');
// export strap.js itself
if (exprotEnabled) {
module.exports = strapjs_root.strapjs;
}
// add/export shortcuts
strapjs.Foreach({
'foreach_': 'Foreach',
'length_': 'Length',
'fork_': 'Fork',
'parallelize_': 'Parallelize',
'namespace_': 'Namespace',
'class_': 'Class',
'use_': 'Use',
'fire_': 'EventFire',
'erase_': 'EventErase',
'subscribe_': 'EventSubscribe',
'unsubscribe_': 'EventUnsubscribe',
'merge_': 'Merge'
}, function (shortcut, origin) {
strapjs_root[shortcut] = strapjs[origin];
if (exprotEnabled) {
module.exports[shortcut] = strapjs[origin];
}
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment