UNPKG

sugar

Version:

A Javascript library for working with native objects.

366 lines (295 loc) 10.5 kB
/*** * @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();