sugar
Version:
A Javascript library for working with native objects.
366 lines (295 loc) • 10.5 kB
JavaScript
/***
* @package Core
* @description Internal utility and common methods.
***/
// A few optimizations for Google Closure Compiler will save us a couple kb in the release script.
var object = Object, array = Array, regexp = RegExp, date = Date, string = String, number = Number, math = Math, Undefined;
// The global context
var globalContext = typeof global !== 'undefined' ? global : this;
// defineProperty exists in IE8 but will error when trying to define a property on
// native objects. IE8 does not have defineProperies, however, so this check saves a try/catch block.
var definePropertySupport = object.defineProperty && object.defineProperties;
// Class initializers and class helpers
var ClassNames = 'Array,Boolean,Date,Function,Number,String,RegExp'.split(',');
var isArray = buildClassCheck(ClassNames[0]);
var isBoolean = buildClassCheck(ClassNames[1]);
var isDate = buildClassCheck(ClassNames[2]);
var isFunction = buildClassCheck(ClassNames[3]);
var isNumber = buildClassCheck(ClassNames[4]);
var isString = buildClassCheck(ClassNames[5]);
var isRegExp = buildClassCheck(ClassNames[6]);
function buildClassCheck(type) {
return function(obj) {
return className(obj) === '[object '+type+']';
}
}
function className(obj) {
return object.prototype.toString.call(obj);
}
function initializeClasses() {
initializeClass(object);
iterateOverObject(ClassNames, function(i,name) {
initializeClass(globalContext[name]);
});
}
function initializeClass(klass) {
if(klass['SugarMethods']) return;
defineProperty(klass, 'SugarMethods', {});
extend(klass, false, false, {
'restore': function() {
var all = arguments.length === 0, methods = multiArgs(arguments);
iterateOverObject(klass['SugarMethods'], function(name, m) {
if(all || methods.indexOf(name) > -1) {
defineProperty(m.instance ? klass.prototype : klass, name, m.method);
}
});
},
'extend': function(methods, override, instance) {
extend(klass, instance !== false, override, methods);
}
});
}
// Class extending methods
function extend(klass, instance, override, methods) {
var extendee = instance ? klass.prototype : klass, original;
initializeClass(klass);
iterateOverObject(methods, function(name, method) {
original = extendee[name];
if(typeof override === 'function') {
method = wrapNative(extendee[name], method, override);
}
if(override !== false || !extendee[name]) {
defineProperty(extendee, name, method);
}
// If the method is internal to Sugar, then store a reference so it can be restored later.
klass['SugarMethods'][name] = { instance: instance, method: method, original: original };
});
}
function extendSimilar(klass, instance, override, set, fn) {
var methods = {};
set = isString(set) ? set.split(',') : set;
set.forEach(function(name, i) {
fn(methods, name, i);
});
extend(klass, instance, override, methods);
}
function wrapNative(nativeFn, extendedFn, condition) {
return function() {
if(nativeFn && (condition === true || !condition.apply(this, arguments))) {
return nativeFn.apply(this, arguments);
} else {
return extendedFn.apply(this, arguments);
}
}
}
function defineProperty(target, name, method) {
if(definePropertySupport) {
object.defineProperty(target, name, { 'value': method, 'configurable': true, 'enumerable': false, 'writable': true });
} else {
target[name] = method;
}
}
// Argument helpers
function multiArgs(args, fn) {
var result = [], i;
for(i = 0; i < args.length; i++) {
result.push(args[i]);
if(fn) fn.call(args, args[i], i);
}
return result;
}
function flattenedArgs(obj, fn, from) {
multiArgs(array.prototype.concat.apply([], array.prototype.slice.call(obj, from || 0)), fn);
}
function checkCallback(fn) {
if(!fn || !fn.call) {
throw new TypeError('Callback is not callable');
}
}
// General helpers
function isDefined(o) {
return o !== Undefined;
}
function isUndefined(o) {
return o === Undefined;
}
// Object helpers
function isObjectPrimitive(obj) {
// Check for null
return obj && typeof obj === 'object';
}
function isObject(obj) {
// === on the constructor is not safe across iframes
// 'hasOwnProperty' ensures that the object also inherits
// from Object, which is false for DOMElements in IE.
return !!obj && className(obj) === '[object Object]' && 'hasOwnProperty' in obj;
}
function hasOwnProperty(obj, key) {
return object['hasOwnProperty'].call(obj, key);
}
function iterateOverObject(obj, fn) {
var key;
for(key in obj) {
if(!hasOwnProperty(obj, key)) continue;
if(fn.call(obj, key, obj[key], obj) === false) break;
}
}
function simpleMerge(target, source) {
iterateOverObject(source, function(key) {
target[key] = source[key];
});
return target;
}
// Hash definition
function Hash(obj) {
simpleMerge(this, obj);
};
Hash.prototype.constructor = object;
// Number helpers
function getRange(start, stop, fn, step) {
var arr = [], i = parseInt(start), down = step < 0;
while((!down && i <= stop) || (down && i >= stop)) {
arr.push(i);
if(fn) fn.call(this, i);
i += step || 1;
}
return arr;
}
function round(val, precision, method) {
var fn = math[method || 'round'];
var multiplier = math.pow(10, math.abs(precision || 0));
if(precision < 0) multiplier = 1 / multiplier;
return fn(val * multiplier) / multiplier;
}
function ceil(val, precision) {
return round(val, precision, 'ceil');
}
function floor(val, precision) {
return round(val, precision, 'floor');
}
function padNumber(num, place, sign, base) {
var str = math.abs(num).toString(base || 10);
str = repeatString(place - str.replace(/\.\d+/, '').length, '0') + str;
if(sign || num < 0) {
str = (num < 0 ? '-' : '+') + str;
}
return str;
}
function getOrdinalizedSuffix(num) {
if(num >= 11 && num <= 13) {
return 'th';
} else {
switch(num % 10) {
case 1: return 'st';
case 2: return 'nd';
case 3: return 'rd';
default: return 'th';
}
}
}
// String helpers
// WhiteSpace/LineTerminator as defined in ES5.1 plus Unicode characters in the Space, Separator category.
function getTrimmableCharacters() {
return '\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF';
}
function repeatString(times, str) {
return array(math.max(0, isDefined(times) ? times : 1) + 1).join(str || '');
}
// RegExp helpers
function getRegExpFlags(reg, add) {
var flags = reg.toString().match(/[^/]*$/)[0];
if(add) {
flags = (flags + add).split('').sort().join('').replace(/([gimy])\1+/g, '$1');
}
return flags;
}
function escapeRegExp(str) {
if(!isString(str)) str = string(str);
return str.replace(/([\\/'*+?|()\[\]{}.^$])/g,'\\$1');
}
// Specialized helpers
// Used by Array#unique and Object.equal
function stringify(thing, stack) {
var type = typeof thing,
thingIsObject,
thingIsArray,
klass, value,
arr, key, i;
// Return quickly if string to save cycles
if(type === 'string') return thing;
klass = object.prototype.toString.call(thing)
thingIsObject = isObject(thing);
thingIsArray = klass === '[object Array]';
if(thing != null && thingIsObject || thingIsArray) {
// This method for checking for cyclic structures was egregiously stolen from
// the ingenious method by @kitcambridge from the Underscore script:
// https://github.com/documentcloud/underscore/issues/240
if(!stack) stack = [];
// Allowing a step into the structure before triggering this
// script to save cycles on standard JSON structures and also to
// try as hard as possible to catch basic properties that may have
// been modified.
if(stack.length > 1) {
i = stack.length;
while (i--) {
if (stack[i] === thing) {
return 'CYC';
}
}
}
stack.push(thing);
value = string(thing.constructor);
arr = thingIsArray ? thing : object.keys(thing).sort();
for(i = 0; i < arr.length; i++) {
key = thingIsArray ? i : arr[i];
value += key + stringify(thing[key], stack);
}
stack.pop();
} else if(1 / thing === -Infinity) {
value = '-0';
} else {
value = string(thing && thing.valueOf ? thing.valueOf() : thing);
}
return type + klass + value;
}
function isEqual(a, b) {
if(objectIsMatchedByValue(a) && objectIsMatchedByValue(b)) {
return stringify(a) === stringify(b);
} else {
return a === b;
}
}
function objectIsMatchedByValue(obj) {
var klass = className(obj);
return klass === '[object Date]' ||
klass === '[object Array]' ||
klass === '[object String]' ||
klass === '[object Number]' ||
klass === '[object RegExp]' ||
klass === '[object Boolean]' ||
klass === '[object Arguments]' ||
isObject(obj);
}
// Used by Array#at and String#at
function entryAtIndex(arr, args, str) {
var result = [], length = arr.length, loop = args[args.length - 1] !== false, r;
multiArgs(args, function(index) {
if(isBoolean(index)) return false;
if(loop) {
index = index % length;
if(index < 0) index = length + index;
}
r = str ? arr.charAt(index) || '' : arr[index];
result.push(r);
});
return result.length < 2 ? result[0] : result;
}
// Object class methods implemented as instance methods
function buildObjectInstanceMethods(set, target) {
extendSimilar(target, true, false, set, function(methods, name) {
methods[name + (name === 'equal' ? 's' : '')] = function() {
return object[name].apply(null, [this].concat(multiArgs(arguments)));
}
});
}
initializeClasses();