/**
* @requires _ Underscore.js
*/
(function (window) {
window._ = window._ || {};
var StringProto = String.prototype;
var nativeTrim = StringProto.trim,
nativeTrimLeft = StringProto.trimLeft,
nativeTrimRight = StringProto.trimRight;
var getTrimRegExp = function (chars, left, right) {
chars = chars ? '' + chars : '';
chars = (chars)
? '[' + chars.replace(/([-^$.+*?|(){}[\]\/\\])/g, '\\$1') + ']'
: '\\s';
var parts = [];
if (left) {
parts.push('^' + chars + '+');
}
if (right) {
parts.push(chars + '+$');
}
return new RegExp(parts.join('|'), 'g');
};
_.obj = {
/**
* Get property specified by complex name from Object or Array
* @param {Object|Array} source Source data
* @param {String} field Parts of complex names should be separated with dot
* @param {*} [defaultValue]
* @return {*|undefined}
*/
get: function (source, field, defaultValue) {
var data = source;
var path = ('' + field).split('.');
for (var i = 0, count = path.length; i < count && null != data; ++i) {
data = data[path[i]];
}
return (undefined === data) ? defaultValue : data;
},
/**
* Set property specified by complex name in Object or Array
* @param {Object|Array} target
* @param {String} field Parts of complex names should be separated with dot
* @param {*} value
* @return {Boolean}
*/
set: function (target, field, value) {
var isObject = function (obj) {
var type = Object.prototype.toString.call(obj);
return ('[object Object]' === type || '[object Array]' === type);
};
if (isObject(target)) {
var data = target;
var path = ('' + field).split('.');
for (var i = 0, count = path.length - 1; i < count; ++i) {
if ('undefined' === typeof data[path[i]]) {
data[path[i]] = {};
}
if (isObject(data[path[i]])) {
data = data[path[i]];
} else {
return false;
}
}
data[path[path.length - 1]] = value;
return true;
}
return false;
}
};
_.str = {
toNumber: function (text) {
var number = +text;
return isFinite(number) ? number : 0;
},
/**
* @param {String} str
* @param {String} [chars]
* @return {String}
*/
trim: function (str, chars) {
str = '' + str;
return (nativeTrim && !chars)
? nativeTrim.call(str)
: (str == null ? '' : str.replace(getTrimRegExp(chars, true, true), ''));
},
/**
* @param {String} str
* @param {String} [chars]
* @return {String}
*/
trimLeft: function (str, chars) {
str = '' + str;
return (nativeTrimLeft && !chars)
? nativeTrimLeft.call(str)
: (str == null ? '' : str.replace(getTrimRegExp(chars, true, false), ''));
},
/**
* @param {String} str
* @param {String} [chars]
* @return {String}
*/
trimRight: function (str, chars) {
str = '' + str;
return (nativeTrimRight && !chars)
? nativeTrimRight.call(str)
: (str == null ? '' : str.replace(getTrimRegExp(chars, false, true), ''));
},
/**
* Converts string's first character to uppercase
* @param {String} str
* @return {String}
*/
capitalize: function (str) {
str = '' + str;
return str.charAt(0).toUpperCase() + str.substr(1);
},
/**
* Converts underscored or dasherized string to camelized
* @example
*
* _.str.camelize('-moz-box-shadow')
* // MozBoxShadow
*
* @param {String} str
* @return {String}
*/
camelize: function(str) {
return _.str.trim(str).replace(/[-_\s]+(.)?/g, function(match, c) {
return c ? c.toUpperCase() : '';
});
},
/**
*
* @param {String} str
* @param {Number} count
* @param {String} [separator]
* @return {String}
*/
repeat: function (str, count, separator) {
str = '' + str;
count = +count;
separator = separator ? '' + separator : '';
var result = '';
for (var i = 0; i < count; ++i) {
if (i) {
result += separator;
}
result += str;
}
return result;
},
/**
* Renders simple templates
* @example
*
* _.str.tpl('Hello {name}', {name: 'World'});
*
* @param {String} tpl
* @param {Object} data
* @return {String}
*/
tpl: function (tpl, data) {
return ('' + tpl).replace(/\{([\w\d_-]+)\}/g, function (match, key) {
return (key in data) ? data[key] : '';
});
},
/**
* Returns a word in the plural
* @param {Array|String} word
* @param {Number} count
* @return {String}
*/
plural: function (word, count) {
count = +count;
var result = '';
if (_.isArray(word)) {
result = (0 === count || 1 < count)
? word[1] || ''
: word[0];
} else if (_.isString(word)) {
result = word + (0 === count || 1 < count ? 's' : '');
}
return result;
}
};
_.num = {
/**
* Returns formatted number
*
* http://phpjs.org/functions/number_format:481
* Copyright 2011, Kevin van Zonneveld
* Released under the MIT and GPL licenses.
* More information: http://phpjs.org/pages/license
*
* @param {Number|String} number
* @param {Number} [decimals] Default '0'
* @param {String} [decPoint] Default '.'
* @param {String} [thousandsSep] Default ','
* @return {String}
*/
format: function (number, decimals, decPoint, thousandsSep) {
number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
var n = !isFinite(+number) ? 0 : +number,
prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
sep = (typeof thousandsSep === 'undefined') ? ',' : thousandsSep,
dec = (typeof decPoint === 'undefined') ? '.' : decPoint,
s,
toFixedFix = function (n, prec) {
var k = Math.pow(10, prec);
return '' + Math.round(n * k) / k;
};
// Fix for IE parseFloat(0.55).toFixed(0) = 0;
s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.');
if (s[0].length > 3) {
s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
}
if ((s[1] || '').length < prec) {
s[1] = s[1] || '';
s[1] += new Array(prec - s[1].length + 1).join('0');
}
return s.join(dec);
}
};
/**
*
* @param {String} url
* @param {String} [baseUrl]
* @return {_.Uri}
*/
_.Uri = function (url, baseUrl) { return this.initialize.apply(this, arguments); };
_.Uri.prototype = {
initialize: function (url, baseUrl) {
url = '' + url;
/**
* @readonly
* @type {Object}
*/
this.parts = this.parse(url);
var fields = {'source': '', 'protocol': '', 'user': '', 'password': '', 'host': '', 'port': '',
'path': '', 'query': '', 'anchor': ''};
for (var name in fields) {
if (fields.hasOwnProperty(name)) {
this[name] = this.parts[name] || '';
}
}
if (baseUrl) {
this.setBaseUrl(baseUrl);
}
return this;
},
setBaseUrl: function (baseUrl) {
baseUrl = '' + baseUrl;
baseUrl = _.str.trimRight(baseUrl, '/');
var baseParts = this.parse(baseUrl);
if (this.host) {
if (-1 !== baseUrl.indexOf(this.host)) {
if (this.host === baseParts.host
&& baseParts.path === this.path.substring(0, baseParts.path.length)
) {
this.baseUrl = baseUrl;
this.basePath = baseParts.path;
this.relativePath = this.path.substring(baseParts.path.length);
}
}
this.rebuild();
} else if ('/' !== this.path[0]) {
this.baseUrl = baseUrl;
this.basePath = baseParts.path;
this.relativePath = '/' + this.path;
var baseFields = ['protocol', 'user', 'password', 'host', 'port', 'path'];
for (var i = 0, length = baseFields.length; i < length; ++i) {
this[baseFields[i]] = baseParts[baseFields[i]];
}
this.path = this.path + this.relativePath;
this.rebuild();
}
return this;
},
/**
* Returns path without '..' and '.'.
* Works only with path, not entire url
* @param {String} path
* @return {String}
*/
realPath: function (path) {
path = '' + path;
if ((/(^|\/)\.\.?(\/|$)/).test(path)) {
var result = [], parts = path.split('/');
for (var i = 0, len = parts.length; i < len; ++i) {
if ('..' === parts[i]) {
result.pop();
} else if ('.' !== parts[i]) {
result.push(parts[i]);
}
}
path = result.join('/');
}
return path;
},
/**
* Returns object containing parts of parsed url
*
* Modified version of parseUri 1.2.2
* http://blog.stevenlevithan.com/archives/parseuri
* Copyright 2007, Steven Levithan
* MIT License
*
* @param {String} url
* @return {Object}
*/
parse: function (url) {
url = '' + url;
var o = _.Uri.prototype.parserOptions,
m = o.parser[o.strictMode ? "strict" : "loose"].exec(url),
uri = {},
i = 14;
while (i--) {
uri[o.key[i]] = m[i] || "";
}
uri[o.q.name] = {};
uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
if ($1) {
uri[o.q.name][$1] = $2;
}
});
return uri;
},
parserOptions: {
strictMode: true,
key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
q: {
name: "queryKey",
parser: /(?:^|&)([^&=]*)=?([^&]*)/g
},
parser: {
strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
}
},
rebuild: function () {
this.source = this.toString();
return this.source;
},
toString: function () {
var url = '';
if (this.protocol) {
url += this.protocol;
if (':' !== this.protocol[this.protocol.length - 1]) {
url += ':';
}
url += '//';
} else {
if (-1 !== this.source.indexOf('//') && this.host) {
url += '//';
}
}
if (this.host && (this.user || this.password)) {
url += this.user + (this.password ? ':' + this.password : '') + '@';
}
if (this.host) {
url += this.host;
if (this.port) {
url += ':' + this.port;
}
}
if (this.path) {
url += this.path;
} else {
if (this.host && (this.query || this.anchor)) {
url += '/';
}
}
if (this.query) {
if (0 !== this.query.indexOf('?')) {
url += '?';
}
url += this.query;
}
if (this.anchor) {
if (0 !== this.anchor.indexOf('#')) {
url += '#';
}
url += this.anchor;
}
return url;
}
};
/**
* Returns type of variable in original case
* @param {*} mixed
* @param {Boolean} [lowerCase]
* @return {String}
*/
window._.getType = function (mixed, lowerCase) {
var type = (null === mixed || undefined === mixed)
? '' + mixed
: Object.prototype.toString.call(mixed).slice(8, -1);
return (lowerCase) ? type.toLowerCase() : type;
};
})(window);