src/core.js
// The container variable for our library instance. As you browse through the libraries code you'll see the `me`
// variable referenced throughout, this is simply short-hand for the library.
var me = {};
// Initialize our library. This function must be invoked before we start using the library.
me.init = function() {
// Reference the `head` element of our document and store it into memory. The if statement is for test coverage.
me.head = global.document ? global.document.getElementsByTagName('head')[0] : null;
// Reference aliases for the library into the `global` object for the user to directly access.
me.alias(global, me.alias.types);
// Initialize our loader object.
me.loader.init();
};
// Reference the library's aliases into the `global` `Object` for the user to directly access. If a alias that we're
// attempting to reference currently exists in our `global` `Object`, then we won't override it.
me.alias = function(container, input) {
// Loop through each of our aliases.
me.each(input, function(aliases, key) {
// Store the module name that we'll reference throughout our loop.
var moduleName = key;
// Store the factory for our module that we'll reference throughout our loop.
var factory = me;
if (key === 'me') {
// If the `key` is `me`, then we need to reference the library itself.
moduleName = 'fallback';
} else {
// Reference the function within our library.
factory = me[key];
}
// Define modules that reference back to the library so that they can be used within our `define` and `require`
// functions. For example in a `define` `factory` we might want to access the `fallback` library, or the `require`
// function, this will allow us to do that. Makes for good code encapsulation if the developer wants to take
// advantage of it.
me.define(moduleName, factory);
// Fetch the reference of our new module.
var module = me.module(moduleName, null, false);
// Flag our new module as invoked and loaded since it's internal.
module.invoked = module.loader.loaded = true;
// Map all of our aliases to our module.
me.each(aliases, function(alias) {
// If the alias is currently defined in the `container` object, skip it and throw a warning to the end user.
if (me.isDefined(container[alias])) {
me.log(2, 'core', 'init', 'aliases', 'The variable container["' + alias + '"] already exists. Overriding!');
}
// Map the alias to our module.
me.module.alias(moduleName, alias);
// Reference the alias of the module within the `container` reference.
container[alias] = factory;
});
});
// Reset any pending anonymous state.
me.define.anonymous.reset();
};
// This is where we hold all of our functional aliases for the library.
me.alias.types = {
// Referenecs for our `config` function.
'config': ['cfg', 'conf', 'config'],
// Referenecs for our `define` function.
'define': ['def', 'define'],
// Referenecs for the library.
'me': ['fallback', 'fbk'],
// Referenecs for our `require` function.
'require': ['req', 'require']
};
// Fetch the parameters that are passed into a `Function` and return them in an `Array` `String` series. For example:
// `function(a, b, c)` returns `['a', 'b', 'c']`. Thanks to @toddmotto who wrote the better part of this `Function`.
// @reference http://toddmotto.com/angular-js-dependency-injection-annotation-process/
me.args = function(reference) {
// If our `reference` is not a `Function`, then halt.
if (!me.isFunction(reference)) {
return [];
}
// Setup our regular expressions that we'll use to parse out the arguments of our `reference` `Function`.
var expessions = {
arg: /^\s*(_?)(.+?)\1\s*$/,
args: /^function\s*[^\(]*\(\s*([^\)]*)\)/m,
argsSplit: /,/,
comments: /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg
};
// The `Array` of our cleansed arguments from the `reference` `Function`.
var args = [];
// Strip out any comments that are listed in the `Function`.
var name = reference.toString().replace(expessions.comments, '');
// Explicity fetch the arguments out of the `Function`.
var declaration = name.match(expessions.args);
// Split on `,` and loop through each of the arguments.
me.each(declaration[1].split(expessions.argsSplit), function(arg) {
// Clean the dirty argument.
arg.replace(expessions.arg, function(all, underscore, name) {
// Push the cleansed argument to the `args` `Array`.
args.push(name);
});
});
// Return the cleansed `Array` of arguments from our `reference`.
return args;
};
// Clone an array. `Array.prototype.slice` appears to be the most efficient way of doing this.
// @reference http://jsperf.com/new-array-vs-splice-vs-slice/19
me.arrayClone = function(input) {
// If the `input` parameter is not an `Array` or `Object`, return an empty `Array`.
if (!me.isaArray(input) && !me.isObject(input) && typeof input !== 'object') {
return [];
}
// Clone the Array.
return Array.prototype.slice.call(input);
};
// Remove all duplicates from an array.
me.arrayUnique = function(input) {
// Store our normalized values.
var normalized = [];
// If an array was not passed in, halt the function.
if (!me.isaArray(normalized)) {
return normalized;
}
// Run through each of our `Array` values.
me.each(input, function(value) {
// Make sure the `value` doesn't already exist in our `normalized` `Array`.
if (me.indexOf(normalized, value) === -1) {
// Push our non-duplicated `value` off to our `normalized` `Array`.
normalized.push(value);
}
});
// Return our normalized set of unique values.
return normalized;
};
// Shorthand for a `for in` loop. Less code, easier readability. If `false` is returned, the loop will be halted.
me.each = function(input, callback) {
// If anything other than an `Array` or `Object` was passed in, halt the `Function`.
if (!me.isaArray(input) && !me.isObject(input) && typeof input !== 'object') {
return;
}
// Normalize our callback to a `Function`.
callback = me.normalizeFunction(callback);
// Run our loop.
for (var index in input) {
// If a `false` is returned during the loop, then halt the loo!.
if (callback(input[index], index) === false) {
break;
}
}
};
// The ability to fetch the decendant property of an object, even if it's in dot notation.
me.getProperty = function(reference, property) {
var properties = property.split('.');
while (properties.length) {
reference = reference[properties.shift()];
}
return reference;
};
// Legacy browsers don't support `Array.prototype.indexOf`, this function dubs as a polyfill for this browsers. In
// particular IE < 9, doesn't support it. @ie @ie6 @ie7 @ie8
// @reference https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
me.indexOf = function(input, value) {
// By default we'll return `-1` if nothing is found to simulate the native `indexOf` functionality.
var index = -1;
// If our `input` is not an `Array`, or our `value is not a `String` or `Number`, halt the `Function`.
if (!me.isaArray(input) || !me.isString(value) && !me.isNumber(value)) {
return index;
}
// Search through our `Array` for our `value`.
me.each(input, function(iterationValue, iterationIndex) {
// As soon as we find our first value, halt the loop.
if (iterationValue === value) {
// Set the index of our result.
index = parseInt(iterationIndex, 10);
// Halt the loop.
return false;
}
});
// Return the index of our `value`.
return index;
};
// Check whether or not a variable is defined.
me.isDefined = function(variable) {
return variable !== void 0;
};
// Check a `String` to see if it contains a prefix.
me.isPrefixed = function(reference, prefixes) {
// Flag whether or not a URL should be ignore from being prepended with the base URL.
var isPrefixed = false;
// Loop through our ignore list.
me.each(prefixes, function(prefix) {
// Check to see if the prefix of our URL has a match in our ignore list.
if (reference.substr(0, prefix.length) === prefix) {
// Flag the URL as ignored.
isPrefixed = true;
// Halt the loop.
return false;
}
});
// Return whether or not we found a match.
return isPrefixed;
};
// Check if a variable is a specific type.
me.isType = function(variable, type) {
// Special check for `null` for legacy browsers. @ie
if (type === 'Object' && variable === null) {
return false;
}
// Special check for `undefined` for legacy browsers. @ie
if (!me.isDefined(variable)) {
return false;
}
// Run our global check.
var valid = Object.prototype.toString.call(variable) === '[object ' + type + ']';
// Newer browsers give the proper types for these, whereas legacy browsers don't. Instead of writing separate
// functions and test for each, we can simply accept them all as being an object.
if (valid === false && (type === 'HTMLCollection' || type === 'HTMLHeadElement' || type === 'HTMLScriptElement')) {
// Special patch for Safari. @safari
if (type === 'HTMLCollection') {
valid = me.isType(variable, 'NodeList');
}
// Fallback on an `Object` for legacy browsers.
if (!valid) {
valid = me.isType(variable, 'Object');
}
}
// Return whether or not our type is valid.
return valid;
};
// Constrain an object to only contain a specific set of keys. All other keys are discarded, and a warning is thrown.
me.objectConstrain = function(input, whitelist, reference) {
// Store our normalized `Object`.
var normalized = {};
// If our `input` is not an `Object` return an empty `Object`.
if (!me.isObject(input)) {
return normalized;
}
// If we don't have a `whitelist` or if it's not an `Array`, return our `input`.
if (!me.isaArray(whitelist)) {
return input;
}
// Loop through our `Object`.
me.each(input, function(value, key) {
// If the `key` is not defined in the `whitelist`, then discard it.
if (me.indexOf(whitelist, key) === -1) {
// Throw a warning to the user that we've discarded the `key` in question.
if (reference) {
me.log(2, 'core', 'objectConstrain', 'The key `' + key + '` is not allowed in `' + reference + '`, discarding.', input);
}
return;
}
// Set our normalized value.
normalized[key] = value;
});
// Return our normalized `Object`.
return normalized;
};
// Merge an `Object` with a set of default values. If the `defaults` parameter is an `Array`, it will treat whatever
// the value is for `fallback` as it's value.
me.objectMerge = function(input, defaults, fallback) {
// Our normalized/merged `Object`.
var normalized = {};
// If our `input` is not an `Object` return an empty `Object`.
if (!me.isObject(input)) {
return normalized;
}
// The defaults to merge with.
var defaultsIsArray = me.isaArray(defaults);
// If our defaults isn't an Array or Object, then return our `input`.
if (!me.isObject(defaults) && !defaultsIsArray) {
return input;
}
// Loop through our defaults.
me.each(defaults, function(value, key) {
// If our `defaults` is an `Array` we need to swap out the key/values.
if (defaultsIsArray === true) {
key = value;
value = fallback;
}
// If the `key` is defined in our `input` object, then don't override it, reference it.
if (me.isDefined(input[key])) {
normalized[key] = input[key];
return;
}
// Set the value of our default `key`.
normalized[key] = value;
});
// Return our merged `Object`.
return normalized;
};
// A function which simply pads a `String` with whatever `String` is supplied.
me.stringPad = function(input, pad, left) {
if (!me.isDefined(pad)) {
return input;
}
if (left) {
return (pad + input).slice(-pad.length);
}
return (input + pad).substr(0, pad.length);
};