/** * Copyright 2014 Riccardo Attilio Galli * [http://www.sideralis.org] * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Apply the returnExports.js UMD pattern // See [https://github.com/umdjs/umd](https://github.com/umdjs/umd) (function (root, factory) { "use strict"; if (typeof exports === 'object') { // Node. Does not work with strict CommonJS, but // only CommonJS-like enviroments that support module.exports, // like Node. module.exports = factory(); } else if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(factory); } else { // Browser globals root.valib = factory(); } }(this, function (/*dependencies*/) { "use strict"; // Reset `undefined`, it may have been overwritten. var undefined = (function(){})(); // Detect a variable's type (refactoring of code from jQuery 1.7.1). var getType = (function() { var classes = ["Boolean", "Number", "String", "Function", "Array", "Date", "RegExp", "Object"], class2type = {}; for (var i=0,il=classes.length; i min : n >= min) && (options.r_exc ? n < max : n <= max); }, lt : function(n, max) { return n < max; }, lte : function(n, max) { return n <= max; }, gt : function(n, min) { return n > min; }, gte : function(n, min) { return n >= min; }, isPositive: function(n) { return n > 0; }, isNegative: function(n) { return n < 0; } }, // String Functions // ---------------- String: { /* Test if `str` represents a number. * `options` is an optional object and can contain the following fields: * - canBeSigned, boolean, defaults `true` * - simple, boolean, defaults `false` * When the `simple` option is `true` then only decimal notation is * accepted (so 2e3, 0x7, etc. are not allowed). */ isNumeric : function(str, options) { options = options || {}; // Defaults to {canBeSigned: true} if (options.canBeSigned === undefined) options.canBeSigned = true; if (options.simple === undefined) options.simple = false; str = valib.String.trim(str); // Below isFinite() may choke with signed hexadecimals (Firefox // return NaN as the standard requires, others parse them, so // we remove the sign alltogether). var sign = null; if (str.length && str.charAt(0) === '-' || str.charAt(0) === '+') { sign = str.charAt(0); str = str.slice(1); } // see http://stackoverflow.com/questions/18082 var isNumeric = !isNaN(parseFloat(str)) && isFinite(str); if (!isNumeric) return false; if (sign && !options.canBeSigned) return false; if (options.simple) return /^(0|[1-9][0-9]*)(\.[0-9]+)?$/.test(str); return true; }, /* Convert a string to a number. `null` is returned if the string * cannot be converted. * `options` is optional and has the same format of `String.isNumeric()`. */ toNumber : function(str, options) { // NOTE: this function returns null instead of NaN in case of errors // because practicality beats purity. Most people would wrongly // use === NaN on the returned element. if (!this.isNumeric(str, options)) return null; var res = NaN; if (str.toUpperCase().indexOf('X') !== -1) { res = parseInt(str,16); } // hex number else if (/^\s*[+-]?0[1-9]/.test(str)) { res = parseInt(str,8); } // oct number else { res = parseFloat(str); } return valib.Type.isNaN(res) ? null : res; }, /* Check if the provided string is an url. The only protocols supported * are http(s) and ftp. */ isUrl : (function() { // Javascript version of the regexp found at // http://stackoverflow.com/questions/161738 var reg = new RegExp( "^(https?|ftp)://" + // protocol "(([a-z0-9$_\\.\\+!\\*\\'\\(\\),;\\?&=-]|%[0-9a-f]{2})+" + // username "(:([a-z0-9$_\\.\\+!\\*\\'\\(\\),;\\?&=-]|%[0-9a-f]{2})+)?" + // password "@)?" + // auth requires @ "((([a-z0-9][a-z0-9-]*[a-z0-9]\\.)*" + // domain segments AND "[a-z][a-z0-9-]*[a-z0-9]" + // top level domain OR "|((\\d|[1-9]\\d|1\\d{2}|2[0-4][0-9]|25[0-5])\\.){3}" + "(\\d|[1-9]\\d|1\\d{2}|2[0-4][0-9]|25[0-5])" + // IP address ")(:\\d+)?" + // port ")(((/+([a-z0-9$_\\.\\+!\\*\\'\\(\\),;:@&=-]|%[0-9a-f]{2})*)*" + // path "(\\?([a-z0-9$_\\.\\+!\\*\\'\\(\\),;:@&=-]|%[0-9a-f]{2})*)" + // query string "?)?)?" + // path and query string optional "(#([a-z0-9$_\\.\\+!\\*\\'\\(\\),;:@&=-]|%[0-9a-f]{2})*)?" + // fragment "$" ,"i"); return function(str) { return reg.test(str); }; })(), isMD5 : function(str) { return /^[0-9a-f]{32}$/i.test(str); }, isSHA1 : function(str) { return /^[0-9a-f]{40}$/i.test(str); }, /* Check if `str` is similar to an e-mail (no hope to comply * with the RFC and the mess that's the real world). */ isEmailLike : function(str) { // something@something with no spaces, one and only one @ return /^[^\s@]+@[^\s@]{3,}$/.test(str); }, /* If regOrString is a regular espression check if `str` matches it. * If regOrString is a string check if it's equal to `str`. * * `options` is an optional object. It can have the following keys: * - trim: boolean (default `false`). * If true then trailing whitespaces will be ignored on `str`. */ match: function(str, regOrString, options) { options = options || {trim:false}; if (options.trim) str = valib.String.trim(str); if (valib.Type.isRegExp(regOrString)) { return regOrString.test(str); } else return regOrString === str; }, startsWith: function(str, prefix) { return str.length >= prefix.length && str.substring(0, prefix.length) === prefix; }, endsWith: function(str, suffix) { return str.length >= suffix.length && str.substring(str.length - suffix.length) === suffix; }, isEmpty : function(str) { return !str; }, trim : (function(str) { // Even if they have String.prototype.trim, browsers may discord // on the whitespace characters, so we use a custom trim. // For Ecma-262 5th edition and Ecma-262 6th draft rev23 trim will // remove white spaces and line terminators. // White spaces are defined as: // - // \u0009 \u000B \u000C \u0020 \u00A0 \uFEFF // - symbols in the General category Zs in Unicode >= 5.1 (here 6.3) // \u0020 \u00A0 \u1680 \u2000-\u200A \u202F \u205F \u3000 // Line terminars are defined as: // - // \u000A \u000D \u2028 \u2029 var ws = '[\u0009\u000A-\u000D\u0020\u00A0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]', regAnyWS = new RegExp(ws), regInitialWS = new RegExp('^' + ws + '+'); return function(str) { if (str == null) return ''; // null and undefined /* modified version of trim12 from http://blog.stevenlevithan.com/archives/faster-trim-javascript */ str = str.replace(regInitialWS, ''); var i = str.length; while (regAnyWS.test(str.charAt(--i))); return str.slice(0, i + 1); }; })(), length: { eq : function(str, n) { return str.length === n; }, gt : function(str, n) { return str.length > n; }, gte : function(str, n) { return str.length >= n; }, lt : function(str, n) { return str.length < n; }, lte : function(str, n) { return str.length <= n; } } }, // Array Functions // --------------- Array : { /* Test if `value` is inside `array`. * If the optional parameter `fromIndex` is provided * then the search start from that index. * * Return the index found or -1. */ indexOf : function(array, value, fromIndex) { // O(n) if (array == null) return -1; // null and undefined if (array.length === 0) return -1; if (!Array.prototype.indexOf) { if (!fromIndex) fromIndex = 0; if (fromIndex < 0) fromIndex = array.length - 1; for (var i=fromIndex,il=array.length; i n; }, gte : function(array, n) { return array.length >= n; }, lt : function(array, n) { return array.length < n; }, lte : function(array, n) { return array.length <= n; } } }, // Object Functions // ---------------- Object : { hasKey : function(object, key) { if (object == null) return false; return Object.prototype.hasOwnProperty.call(object, key); }, hasValue : function(object, value) { for (var key in object) { if (object.hasOwnProperty(key) && object[key] === value) return true; } return false; }, isEmpty : function(object) { if (object == null) return true; for (var key in object) if (this.hasKey(object, key)) return false; return true; }, /* Returns the number of properties inside an object. */ countKeys: function(object) { var k = 0; for (var key in object) { if (object.hasOwnProperty(key)) k++; } return k; } }, // Date Functions // -------------- Date : { isToday : function(d) { return d > this.yesterday() && d < this.tomorrow(); }, isTomorrow :function(d) { var tomorrow = this.tomorrow(); return d.getFullYear() === tomorrow.getFullYear() && d.getMonth() === tomorrow.getMonth() && d.getDate() === tomorrow.getDate(); }, isYesterday : function(d) { var yesterday = this.yesterday(); return d.getFullYear() === yesterday.getFullYear() && d.getMonth() === yesterday.getMonth() && d.getDate() === yesterday.getDate(); }, isNextDay : function(d, future) { return this.nDaysFromDate(1, this.toStartOfTheDay(d)).getTime() == this.toStartOfTheDay(future).getTime(); }, isPreviousDay : function(d, past) { return this.nDaysFromDate(-1, this.toStartOfTheDay(d)).getTime() == this.toStartOfTheDay(past).getTime(); }, /* Return the number of calendar days passed (so if more than 24 hours passed * it may be still a difference of 1 day. e.g. from 01:00 to 03:00 of the * next day). */ elapsedDays : function(d1, d2) { return Math.abs(Math.round(( // round because of daylight saving this.toStartOfTheDay(d1).getTime() - this.toStartOfTheDay(d2).getTime()) / (24 * 60 * 60 * 1000))); }, toStartOfTheDay : function(d) { return new Date(d.getFullYear(), d.getMonth(), d.getDate()); }, today : function() { return this.toStartOfTheDay(new Date()); }, tomorrow : function() { return this.nDaysFromDate(1); }, yesterday : function() { return this.nDaysFromDate(-1); }, nDaysFromDate : function(n, d) { if (!d) d = this.today(); else d = this.clone(d); d.setDate(d.getDate() + n); return d; }, /* Test if `d` will occur at most `nDays` after `startFrom`. * If `startFrom` is missing the current date is used. * If `d` is an earlier date than `startFrom` the function * will always return `false`. */ isWithinDays : function(d, nDays, startFrom) { if (!startFrom) startFrom = this.today(); else startFrom = this.toStartOfTheDay(startFrom); return this.toStartOfTheDay(d) >= this.toStartOfTheDay(startFrom) && this.elapsedDays(startFrom, d) <= nDays; }, clone : function(d) { return new Date(d.getTime()); }, isEqual : function(d1, d2) { return d1.getTime() === d2.getTime(); }, /* Check if two dates are the same day of the same year * (the dates must be in the same timezone). */ isSameDay : function(d1, d2) { return this.isEqual(this.toStartOfTheDay(d1), this.toStartOfTheDay(d2)); }, /* Check if two dates are in the same year/month * (the dates must be in the same timezone). */ isSameMonth : function(d1, d2) { return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth(); }, /* Check if two dates are in the same week and the same year * (the dates must be in the same timezone). */ isSameWeek : function(d1, d2, weekStartsAtSunday/*=true*/) { if (d2 < d1) { var tmp = d1; d1 = d2; d2 = tmp; } // swap if (weekStartsAtSunday !== false) weekStartsAtSunday = true; return this.isSameMonth(d1, d2) && Math.abs(d1.getDate() - d2.getDate()) < 7 && ( (!weekStartsAtSunday && d1.getDay() === 0 ? 7 : d1.getDay()) <= (!weekStartsAtSunday && d2.getDay() === 0 ? 7 : d2.getDay()) ) ; } } }; return valib; }));