blob: 3b10b574f2f7a68dcca4404d46d121df1b5c880f [file] [log] [blame]
/* Copyright 2013 Twitter, Inc. Licensed under The MIT License. http://opensource.org/licenses/MIT */
define(
['./debug'],
function(debug) {
'use strict';
var DEFAULT_INTERVAL = 100;
function canWriteProtect() {
var writeProtectSupported = debug.enabled && !Object.propertyIsEnumerable('getOwnPropertyDescriptor');
if (writeProtectSupported) {
//IE8 getOwnPropertyDescriptor is built-in but throws exeption on non DOM objects
try {
Object.getOwnPropertyDescriptor(Object, 'keys');
} catch (e) {
return false;
}
}
return writeProtectSupported;
}
var utils = {
isDomObj: function(obj) {
return !!(obj.nodeType || (obj === window));
},
toArray: function(obj, from) {
from = from || 0;
var len = obj.length, arr = new Array(len - from);
for (var i = from; i < len; i++) {
arr[i - from] = obj[i];
}
return arr;
},
// returns new object representing multiple objects merged together
// optional final argument is boolean which specifies if merge is recursive
// original objects are unmodified
//
// usage:
// var base = {a:2, b:6};
// var extra = {b:3, c:4};
// merge(base, extra); //{a:2, b:3, c:4}
// base; //{a:2, b:6}
//
// var base = {a:2, b:6};
// var extra = {b:3, c:4};
// var extraExtra = {a:4, d:9};
// merge(base, extra, extraExtra); //{a:4, b:3, c:4. d: 9}
// base; //{a:2, b:6}
//
// var base = {a:2, b:{bb:4, cc:5}};
// var extra = {a:4, b:{cc:7, dd:1}};
// merge(base, extra, true); //{a:4, b:{bb:4, cc:7, dd:1}}
// base; //{a:2, b:6}
merge: function(/*obj1, obj2,....deepCopy*/) {
// unpacking arguments by hand benchmarked faster
var l = arguments.length,
args = new Array(l + 1);
if (l === 0) {
return {};
}
for (var i = 0; i < l; i++) {
args[i + 1] = arguments[i];
}
//start with empty object so a copy is created
args[0] = {};
if (args[args.length - 1] === true) {
//jquery extend requires deep copy as first arg
args.pop();
args.unshift(true);
}
return $.extend.apply(undefined, args);
},
// updates base in place by copying properties of extra to it
// optionally clobber protected
// usage:
// var base = {a:2, b:6};
// var extra = {c:4};
// push(base, extra); //{a:2, b:6, c:4}
// base; //{a:2, b:6, c:4}
//
// var base = {a:2, b:6};
// var extra = {b: 4 c:4};
// push(base, extra, true); //Error ("utils.push attempted to overwrite 'b' while running in protected mode")
// base; //{a:2, b:6}
//
// objects with the same key will merge recursively when protect is false
// eg:
// var base = {a:16, b:{bb:4, cc:10}};
// var extra = {b:{cc:25, dd:19}, c:5};
// push(base, extra); //{a:16, {bb:4, cc:25, dd:19}, c:5}
//
push: function(base, extra, protect) {
if (base) {
Object.keys(extra || {}).forEach(function(key) {
if (base[key] && protect) {
throw new Error('utils.push attempted to overwrite "' + key + '" while running in protected mode');
}
if (typeof base[key] == 'object' && typeof extra[key] == 'object') {
// recurse
this.push(base[key], extra[key]);
} else {
// no protect, so extra wins
base[key] = extra[key];
}
}, this);
}
return base;
},
// If obj.key points to an enumerable property, return its value
// If obj.key points to a non-enumerable property, return undefined
getEnumerableProperty: function(obj, key) {
return obj.propertyIsEnumerable(key) ? obj[key] : undefined;
},
// build a function from other function(s)
// utils.compose(a,b,c) -> a(b(c()));
// implementation lifted from underscore.js (c) 2009-2012 Jeremy Ashkenas
compose: function() {
var funcs = arguments;
return function() {
var args = arguments;
for (var i = funcs.length - 1; i >= 0; i--) {
args = [funcs[i].apply(this, args)];
}
return args[0];
};
},
// Can only unique arrays of homogeneous primitives, e.g. an array of only strings, an array of only booleans, or an array of only numerics
uniqueArray: function(array) {
var u = {}, a = [];
for (var i = 0, l = array.length; i < l; ++i) {
if (u.hasOwnProperty(array[i])) {
continue;
}
a.push(array[i]);
u[array[i]] = 1;
}
return a;
},
debounce: function(func, wait, immediate) {
if (typeof wait != 'number') {
wait = DEFAULT_INTERVAL;
}
var timeout, result;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) {
result = func.apply(context, args);
}
};
var callNow = immediate && !timeout;
timeout && clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) {
result = func.apply(context, args);
}
return result;
};
},
throttle: function(func, wait) {
if (typeof wait != 'number') {
wait = DEFAULT_INTERVAL;
}
var context, args, timeout, throttling, more, result;
var whenDone = this.debounce(function() {
more = throttling = false;
}, wait);
return function() {
context = this; args = arguments;
var later = function() {
timeout = null;
if (more) {
result = func.apply(context, args);
}
whenDone();
};
if (!timeout) {
timeout = setTimeout(later, wait);
}
if (throttling) {
more = true;
} else {
throttling = true;
result = func.apply(context, args);
}
whenDone();
return result;
};
},
countThen: function(num, base) {
return function() {
if (!--num) { return base.apply(this, arguments); }
};
},
delegate: function(rules) {
return function(e, data) {
var target = $(e.target), parent;
Object.keys(rules).forEach(function(selector) {
if (!e.isPropagationStopped() && (parent = target.closest(selector)).length) {
data = data || {};
data.el = parent[0];
return rules[selector].apply(this, [e, data]);
}
}, this);
};
},
// ensures that a function will only be called once.
// usage:
// will only create the application once
// var initialize = utils.once(createApplication)
// initialize();
// initialize();
//
// will only delete a record once
// var myHanlder = function () {
// $.ajax({type: 'DELETE', url: 'someurl.com', data: {id: 1}});
// };
// this.on('click', utils.once(myHandler));
//
once: function(func) {
var ran, result;
return function() {
if (ran) {
return result;
}
ran = true;
result = func.apply(this, arguments);
return result;
};
},
propertyWritability: function(obj, prop, writable) {
if (canWriteProtect() && obj.hasOwnProperty(prop)) {
Object.defineProperty(obj, prop, { writable: writable });
}
},
// Property locking/unlocking
mutateProperty: function(obj, prop, op) {
var writable;
if (!canWriteProtect() || !obj.hasOwnProperty(prop)) {
op.call(obj);
return;
}
writable = Object.getOwnPropertyDescriptor(obj, prop).writable;
Object.defineProperty(obj, prop, { writable: true });
op.call(obj);
Object.defineProperty(obj, prop, { writable: writable });
}
};
return utils;
}
);