// todo: check http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/ /* Ox = function(val) { }; */ Ox = { version: '0.1.2' }; /* ================================================================================ Constants ================================================================================ */ Ox.AMPM = ["AM", "PM"]; Ox.DAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; Ox.EARTH_RADIUS = 6378137; Ox.EARTH_CIRCUMFERENCE = Ox.EARTH_RADIUS * 2 * Math.PI; Ox.MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; Ox.WEEKDAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]; /* ================================================================================ Core functions ================================================================================ */ Ox.getset = function(obj, args, callback, context) { /*** Generic getter and setter function Ox.getset(obj) returns obj Ox.getset(obj, [key]) returns obj.key Ox.getset(obj, [key, val], callback, context) Ox.getset(obj, [{key: val, ...}], callback, context) sets obj.key to val, calls callback(key, val), returns context ***/ var obj_ = obj, args_ = {}, args = args || [], callback = callback || {}, context = context || {}, length = args.length, ret; if (length == 0) { // getset() ret = obj; } else if (length == 1 && Ox.isString(args[0])) { // getset(str) ret = obj[args[0]] } else { // getset(str, val) or getset({str: val, ...}) if (length == 1) { args = args[0]; } else { args_[args[0]] = args[1]; args = args_; } obj = $.extend(obj, args); $.each(args, function(key, value) { if (!obj_ || !obj_[key] || obj_[key] !== value) { callback(key, value); } }); ret = context; } return ret; } Ox.print = function() { /* */ if (typeof console != "undefined") { var args = $.makeArray(arguments), date = new Date; args.unshift(Ox.formatDate(date, "%H:%M:%S") + "." + (Ox.pad(+date % 1000, 3))); console.log.apply(console, args); } } Ox.uid = function() { /*** returns a unique id >>> Ox.uid() != Ox.uid() true ***/ var uid = 0; return function() { return uid++; }; }(); Ox.user = function() { // fixme: move to ox.ui $.get("http://www.maxmind.com/app/locate_my_ip", function(data) { var arr = data.split("tblProduct1"), re = />(.+?)<\/td>\n\n(.*?)\n/, results = {}; arr.shift(); $.each(arr, function(i, v) { var result = re(v); results[result[1].replace(/Your |\*/, "")] = result[2]; }); Ox.print(results) }); return { document: { referrer: document.referrer }, history: { length: history.length }, navigator: navigator, innerHeight: innerHeight, innerWidth: innerWidth, screen: screen, outerHeight: outerHeight, outerWidth: outerWidth } }; /* ================================================================================ Array and Object functions ================================================================================ */ Ox.avg = function(obj) { /*** returns the average of an array's values, or an object's properties >>> Ox.avg([-1, 0, 1]) 0 >>> Ox.avg({"a": 1, "b": 2, "c": 3}) 2 ***/ return Ox.sum(obj) / Ox.length(obj); }; Ox.clone = function(obj) { return Ox.isArray(obj) ? obj.slice() : Ox.extend({}, obj); }; Ox.each = function(obj, fn) { /* Ox.each() works for arrays, objects and strings, like $.each(), unlike [].forEach() >>> Ox.each([0, 1, 2], function(i, v) {}) [0, 1, 2] >>> Ox.each({a: 1, b: 2, c: 3}, function(k, v) {}).a 1 >>> Ox.each("foo", function(i, v) {}) "foo" */ var i; for (i in obj) { // fixme: should be (v, k), like [].forEach() if (fn(i, obj[i]) === false) { break; } } return obj; }; Ox.equals = function(obj0, obj1) { /* ... core? type? */ var ret = false; if (obj0 === obj1) { ret = true; } else if (typeof(obj0) == typeof(obj1)) { if (obj0 == obj1) { ret = true; } else if (Ox.isArray(obj0) && obj0.length == obj1.length) { Ox.each(obj0, function(i, v) { ret = Ox.equals(v, obj1[i]); return ret; }); } else if (Ox.isDate(obj0)) { ret = obj0.getTime() == obj1.getTime(); } else if (Ox.isObject(obj0)) { ret = Ox.equals(Ox.keys(obj0), Ox.keys(obj1)) && Ox.equals(Ox.values(obj0), Ox.values(obj1)); } else if (Ox.isFunction(obj0)) { ret = obj0.toString() == obj1.toString(); } } Ox.print('Ox.equals', obj0, obj1, ret) return ret; } Ox.every = function(obj, fn) { /* Ox.every() works for arrays, objects and strings, unlike [].every() >>> Ox.every([0, 1, 2], function(v, i) { return i == v; }) true >>> Ox.every({a: 1, b: 2, c: 3}, function(v) { return v == 1; }) false >>> Ox.every("foo", function(v) { return v == "f"; }) false >>> Ox.every([true, true, true]) true */ return Ox.filter(Ox.values(obj), fn || function(v) { return v; }).length == Ox.length(obj); }; Ox.extend = function(obj) { Ox.each(Array.prototype.slice.call(arguments, 1), function(i, arg) { Ox.each(arg, function(key, val) { obj0[key] = val; }); }); return obj0; }; Ox.filter = function(arr, fn) { /* Ox.filter works for arrays and strings, like $.grep(), unlike [].filter() >>> Ox.filter([2, 1, 0], function(v, i) { return v == i; }) [1] >>> Ox.filter("210", function(v, i) { return v == i; }) [1] */ var i, len = arr.length, ret = []; for (i = 0; i < len; i++) { if (fn(arr[i], i)) { ret.push(arr[i]); } } return ret; }; Ox.flatten = function(arr) { /* >>> Ox.flatten([1, [2, [3], 4], 5]) [1, 2, 3, 4, 5] */ var ret = []; arr.forEach(function(v) { if (Ox.isArray(v)) { Ox.flatten(v).forEach(function(v) { ret.push(v); }); } else { ret.push(v); } }); return ret; } Ox.find = function(arr, str) { /* returns an array with two arrays as elements: an array of elements of arr that begin with str, and an array of elements of arr that contain, but do not begin with str >>> Ox.find(["foo", "bar", "foobar", "barfoo"], "foo") [["foo", "foobar"], ["barfoo"]] */ var arrLowerCase = arr.map(function(v) { return v.toLowerCase(); }), ret = [[], []]; str && arrLowerCase.forEach(function(v, i) { var index = v.indexOf(str.toLowerCase()); index > -1 && ret[index == 0 ? 0 : 1].push(arr[i]); }); return ret; } Ox.getObjectById = function(arr, id) { /*** >>> Ox.getObjectById([{id: "foo", title: "Foo"}, {id: "bar", title: "Bar"}], "foo").title "Foo" ***/ var ret = null; Ox.each(arr, function(i, v) { if (v.id == id) { ret = v; return false; } }); return ret; }; Ox.getPositionById = function(arr, id) { /*** >>> Ox.getPositionById([{id: "foo", title: "Foo"}, {id: "bar", title: "Bar"}], "bar") 1 ***/ var ret = -1; Ox.each(arr, function(i, v) { if (v.id == id) { ret = i; return false; } }); return ret; }; Ox.keys = function(obj) { /* >>> Ox.keys({"a": 1, "b": 2, "c": 3}) ["a", "b", "c"] */ var keys = []; Ox.each(obj, function(k) { keys.push(k); }); return keys; }; Ox.length = function(obj) { /* >>> Ox.length({"a": 1, "b": 2, "c": 3}) 3 */ var length = 0; Ox.each(obj, function() { length++; }); return length; }; Ox.makeArray = function(arr) { /* like $.makeArray() >>> Ox.makeArray("foo") ["foo"] >>> Ox.makeArray(["foo"]) ["foo"] >>> (function() { return Ox.makeArray(arguments); })("foo") ["foo"] >>> (function() { return Ox.makeArray(arguments); })(["foo"]) ["foo"] */ // fixme: this doesn't work for numbers var ret = [], i = 0, len = arr.length; if (Ox.isString(arr)) { ret = [arr]; } else { for (i = 0; i < len; i++) { ret.push(arr[i]); } } return ret; }; Ox.makeObject = function(arr) { /* >>> Ox.makeObject("foo", "bar").foo "bar" >>> Ox.makeObject({foo: "bar"}).foo "bar" >/>> (function() { return Ox.makeObject(arguments); })("foo", "bar").foo // fixme "bar" >/>> (function() { return Ox.makeObject(arguments); })({foo: "bar"}).foo "bar" */ var obj = {}; if (arguments.length == 1) { obj = arguments[0]; } else { obj[arguments[0]] = arguments[1]; } return obj; }; Ox.map = function(arr, fn) { /* Ox.map() works for arrays and strings, like $.map(), unlike [].map() >>> Ox.map([1, 1, 1], function(v, i) { return v == i; }) [false, true, false] >>> Ox.map("111", function(v, i) { return v == i; }) [false, true, false] >>> Ox.map(new Array(3), function(v, i) { return i; }) [0, 1, 2] */ // fixme: why not [].map.call(str, fn)? var i, len = arr.length, val, ret = []; for (i = 0; i < len; i++) { if ((val = fn(arr[i], i)) !== null) { ret.push(val); } } return ret; }; Ox.max = function(obj) { /* >>> Ox.max([-1, 0, 1]) 1 >>> Ox.max({"a": 1, "b": 2, "c": 3}) 3 */ return Math.max.apply(Math, Ox.values(obj)); }; Ox.min = function(obj) { /* >>> Ox.min([-1, 0, 1]) -1 >>> Ox.min({"a": 1, "b": 2, "c": 3}) 1 */ return Math.min.apply(Math, Ox.values(obj)); }; Ox.merge = function(arr) { Ox.each(Array.prototype.slice.call(arguments, 1), function(i, arg) { Ox.each(arg, function(i, val) { arr.push(val); }); }); return arr; }; Ox.range = function(start, stop, step) { /* >>> Ox.range(3) [0, 1, 2] >>> Ox.range(3, 0) [3, 2, 1] >>> Ox.range(1, 2, 0.5) [1, 1.5] */ stop = arguments.length > 1 ? stop : arguments[0]; start = arguments.length > 1 ? start : 0; step = step || (start <= stop ? 1 : -1); var range = [], i; for (i = start; step > 0 ? i < stop : i > stop; i += step) { range.push(i); } return range; }; Ox.serialize = function(obj) { /* >>> Ox.serialize({a: 0, b: 1}) a=0&b=1 */ var arr = []; Ox.each(obj, function(k, v) { v !== '' && arr.push(k + '=' + v); }); return arr.join('&'); }; Ox.setPropertyOnce = function(arr, str) { var pos = -1; Ox.each(arr, function(i, v) { if (pos == -1 && arr[i][str]) { pos = i; } else if (pos > -1 && arr[i][str]) { delete arr[i][str]; } }); if (pos == -1) { arr[0][str] = true; pos = 0; } return pos; } Ox.shuffle = function(arr) { /* >>> Ox.shuffle([1, 2, 3]).length 3 */ var shuffle = arr; return shuffle.sort(function() { return Math.random() - 0.5; }); }; Ox.some = function(obj, fn) { /* Ox.some() works for arrays, objects and strings, unlike [].some() >>> Ox.some([2, 1, 0], function(i, v) { return i == v; }) true >>> Ox.some({a: 1, b: 2, c: 3}, function(v) { return v == 1; }) true >>> Ox.some("foo", function(v) { return v == "f"; }) true */ return Ox.filter(Ox.values(obj), fn).length > 0; }; Ox.sum = function(obj) { /* >>> Ox.sum([-1, 0, 1]) 0 >>> Ox.sum({"a": 1, "b": 2, "c": 3}) 6 */ var sum = 0; Ox.each(obj, function(k, v) { sum += v; }); return sum; }; Ox.unique = function(arr) { /* >>> Ox.unique([1, 2, 3, 1]) [1, 2, 3] */ var unique = []; $.each(arr, function(i, v) { unique.indexOf(v) == -1 && unique.push(v); }); return unique; }; Ox.unserialize = function(str) { var arr, obj = {}; Ox.each(str.split('&'), function(i, v) { arr = v.split('='); obj[arr[0]] = arr[1]; }); return obj; }; Ox.values = function(obj) { /* >>> Ox.values({"a": 1, "b": 2, "c": 3}).join(",") [1, 2, 3] >>> Ox.values([1, 2, 3]).join(",") [1, 2, 3] */ var values = []; Ox.each(obj, function(k, v) { values.push(v); }); return values; }; Ox.zip = function() { /* // fixme: Ox.each doesn't work here >>> Ox.zip([[0, 1], [2, 3], [4, 5]]) [[0, 2, 4], [1, 3, 5]] >>> Ox.zip([0, 1, 2], [3, 4, 5]) [[0, 3], [1, 4], [2, 5]] */ var args = arguments.length == 1 ? arguments[0] : Ox.makeArray(arguments), arr = []; args[0].forEach(function(v, i) { arr[i] = []; args.forEach(function(v_, i_) { arr[i].push(v_[i]); }); }); return arr; }; /* ================================================================================ Color functions ================================================================================ */ Ox.hsl = function(rgb) { /* >>> Ox.hsl([0, 0, 0]) [0, 0, 0] >>> Ox.hsl([255, 255, 255]) [0, 0, 1] >>> Ox.hsl([0, 255, 0]) [120, 1, 0.5] */ rgb = rgb.map(function(v) { return v / 255; }); var max = Ox.max(rgb), min = Ox.min(rgb), hsl = [0, 0, 0]; hsl[2] = 0.5 * (max + min); if (max == min) { hsl[0] = 0; hsl[1] = 0; } else { if (max == rgb[0]) { hsl[0] = (60 * (rgb[1] - rgb[2]) / (max - min) + 360) % 360; } else if (max == rgb[1]) { hsl[0] = 60 * (rgb[2] - rgb[0]) / (max - min) + 120; } else if (max == rgb[2]) { hsl[0] = 60 * (rgb[0] - rgb[1]) / (max - min) + 240; } if (hsl[2] <= 0.5) { hsl[1] = (max - min) / (2 * hsl[2]); } else { hsl[1] = (max - min) / (2 - 2 * hsl[2]); } } return hsl; }; Ox.rgb = function(hsl) { /* >>> Ox.rgb([0, 0, 0]) [0, 0, 0] >>> Ox.rgb([0, 0, 1]) [255, 255, 255] >>> Ox.rgb([120, 1, 0.5]) [0, 255, 0] */ hsl[0] /= 360; var rgb = [0, 0, 0], v1, v2, v3; if (hsl[1] == 0) { rgb = [hsl[2], hsl[2], hsl[2]]; } else { if (hsl[2] < 0.5) { v2 = hsl[2] * (1 + hsl[1]); } else { v2 = hsl[1] + hsl[2] - (hsl[1] * hsl[2]); } v1 = 2 * hsl[2] - v2; rgb.forEach(function(v, i) { v3 = hsl[0] + (1 - i) * 1/3; if (v3 < 0) { v3++; } else if (v3 > 1) { v3--; } if (v3 < 1/6) { rgb[i] = v1 + ((v2 - v1) * 6 * v3); } else if (v3 < 0.5) { rgb[i] = v2; } else if (v3 < 2/3) { rgb[i] = v1 + ((v2 - v1) * 6 * (2/3 - v3)); } else { rgb[i] = v1; } }); } return rgb.map(function(v) { return v * 255; }); }; /* ================================================================================ Date functions ================================================================================ */ Ox.getDateInWeek = function(date, weekday) { /* >>> Ox.formatDate(Ox.getDateInWeek(new Date("January 1 2000"), "Sunday"), "%A, %B %e, %Y") "Sunday, January 2, 2000" >>> Ox.formatDate(Ox.getDateInWeek(new Date("Jan 1 2000"), "Fri"), "%A, %B %e, %Y") "Friday, December 31, 1999" >>> Ox.formatDate(Ox.getDateInWeek(new Date("1/1/2000"), 1), "%A, %B %e, %Y") "Monday, December 27, 1999" */ Ox.print("getDateInWeek", date.toString(), weekday) var date = date || new Date(), sourceWeekday = Ox.formatDate(date, "%u"); targetWeekday = Ox.isNumber(weekday) ? weekday : Ox.map(Ox.WEEKDAYS, function(v, i) { return v.substr(0, 3) == weekday.substr(0, 3) ? i + 1 : null; })[0]; date.setDate(date.getDate() - sourceWeekday + targetWeekday); return date; } Ox.getDayOfTheYear = function(date) { /* >>> Ox.getDayOfTheYear(new Date("12/31/2004")) 366 */ var days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; return function(date) { date = date || new Date(); var day = date.getDate(), month = date.getMonth(), i; for (i = 0; i < month; i++) { day += days[i]; } if (month >= 2 && Ox.isLeapYear(date.getFullYear())) { day++; } return day; }; }(); Ox.getDaysInMonth = function(year, month) { /* >>> Ox.getDaysInMonth(2000, 2) 28 >>> Ox.getDaysInMonth("2002", "Feb") 28 >>> Ox.getDaysInMonth("2004", "February") 29 */ Ox.print("getDaysInMonth", year, month) var year = parseInt(year), month = Ox.isNumber(month) ? month : Ox.map(Ox.MONTHS, function(v, i) { return v.substr(0, 3) == month.substr(0, 3) ? i + 1 : null; })[0]; return Ox.DAYS[month - 1] + (month == 2 && Ox.isLeapYear(year)); } Ox.getFirstDayOfTheYear = function(date) { /* Decimal weekday of January 1 (0-6, Sunday as first day) >>> Ox.getFirstDayOfTheYear(new Date("01/01/00")) 6 */ var date_ = date ? new Date(date.valueOf()) : new Date(); date_.setMonth(0); date_.setDate(1); return date_.getDay(); }; Ox.getISODate = function(date) { /* >>> Ox.getISODate(new Date("01/01/2000")) "2000-01-01T00:00:00Z" */ return Ox.formatDate(date || new Date(), '%FT%TZ'); }; Ox.getISODay = function(date) { /* Decimal weekday (1-7, Monday as first day) >>> Ox.getISODay(new Date("01/01/2000")) 6 >>> Ox.getISODay(new Date("01/02/2000")) 7 >>> Ox.getISODay(new Date("01/03/2000")) 1 */ return (date || new Date()).getDay() || 7; }; Ox.getISOWeek = function(date) { /* see http://en.wikipedia.org/wiki/ISO_8601 >>> Ox.getISOWeek(new Date("01/01/2000")) 52 >>> Ox.getISOWeek(new Date("01/02/2000")) 52 >>> Ox.getISOWeek(new Date("01/03/2000")) 1 */ date = date || new Date(); var date_ = new Date(date.valueOf()); // set date to Thursday of the same week date_.setDate(date.getDate() - Ox.getISODay(date) + 4); return Math.floor((Ox.getDayOfTheYear(date_) - 1) / 7) + 1; }; Ox.getISOYear = function(date) { /* see http://en.wikipedia.org/wiki/ISO_8601 >>> Ox.getISOYear(new Date("01/01/2000")) 1999 >>> Ox.getISOYear(new Date("01/02/2000")) 1999 >>> Ox.getISOYear(new Date("01/03/2000")) 2000 */ date = date || new Date(); var date_ = new Date(date.valueOf()); // set date to Thursday of the same week date_.setDate(date.getDate() - Ox.getISODay(date) + 4); return date_.getFullYear(); }; Ox.getTime = function() { return +new Date(); } Ox.getTimezoneOffsetString = function(date) { /* Time zone offset string (-1200 - +1200) >>> Ox.getTimezoneOffsetString(new Date("01/01/2000")) "+0100" */ var offset = (date || new Date()).getTimezoneOffset(); return (offset < 0 ? '+' : '-') + Ox.pad(Math.floor(Math.abs(offset) / 60), 2) + Ox.pad(Math.abs(offset) % 60, 2); }; Ox.getWeek = function(date) { /* Week of the year (0-53, Sunday as first day) >>> Ox.getWeek(new Date("01/01/2000")) 0 >>> Ox.getWeek(new Date("01/02/2000")) 1 >>> Ox.getWeek(new Date("01/03/2000")) 1 */ date = date || new Date(); return Math.floor((Ox.getDayOfTheYear(date) + Ox.getFirstDayOfTheYear(date) - 1) / 7); }; Ox.isLeapYear = function(year) { /* >>> Ox.isLeapYear(2000) false */ return year % 4 == 0 && year % 400 != 0; }; /* ================================================================================ DOM functions ================================================================================ */ Ox.canvas = function() { // Ox.canvas(img) or Ox.canvas(width, height) var c = {}, isImage = arguments.length == 1, image = isImage ? arguments[0] : { width: arguments[0], height: arguments[1] }; c.context = (c.canvas = Ox.element("canvas").attr({ width: image.width, height: image.height })[0]).getContext("2d"); isImage && c.context.drawImage(image, 0, 0); c.data = (c.imageData = c.context.getImageData(0, 0, image.width, image.height)).data; return c; }; Ox.element = function(str) { /* >>> Ox.element("div").attr({id: "foo"}).attr("id") "foo" >>> Ox.element("div").html("foo").html() "foo" */ return { 0: str[0] == "#" ? document.getElementById(str.substr(1)) : document.createElement(str), attr: function() { var args, ret, that = this; if (arguments.length == 1 && Ox.isString(arguments[0])) { ret = this[0].getAttribute(arguments[0]); } else { Ox.each(Ox.makeObject.apply(this, arguments), function(k, v) { that[0].setAttribute(k, v); }); ret = this; } return ret; }, html: function(str) { var ret; if (Ox.isUndefined(str)) { ret = this[0].innerHTML; } else { this[0].innerHTML = str; ret = this; } return ret; } } }; /* ================================================================================ Encoding functions ================================================================================ */ (function() { var aliases = {"I": "1", "L": "1", "O": "0", "U": "V"}, digits = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; function max(width, height) { // returns maximum encoding capacity of an image return parseInt(width * height * 3/8) - 4; } function seek(data, px) { // returns this, or the next, opaque pixel while (data[px * 4 + 3] < 255) { if (++px * 4 == data.length) { throwPNGError("de"); } } return px; } function xor(byte) { // returns "1"-bits-in-byte % 2 var xor = 0; $.each(new Array(8), function(i) { // fixme: Ox.each doesn't work xor ^= byte >> i & 1; }); return xor; } function throwPNGError(str) { throw new RangeError("PNG codec can't " + (str == "en" ? "encode data" : "decode image")); } function throwUTF8Error(byte, pos) { throw new RangeError("UTF-8 codec can't decode byte 0x" + byte.toString(16).toUpperCase() + " at position " + pos); } Ox.encodeBase32 = function(num) { // see http://www.crockford.com/wrmg/base32.html /* >>> Ox.encodeBase32(15360) "F00" >>> Ox.encodeBase32(33819) "110V" */ return Ox.map(num.toString(32), function(char) { return digits[parseInt(char, 32)]; }).join(""); } Ox.decodeBase32 = function(str) { /* >>> Ox.decodeBase32("foo") 15360 >>> Ox.decodeBase32("ilou") 33819 >>> Ox.decodeBase32("?").toString() // fixme: toString shouldn't be necessary here "NaN" */ return parseInt($.map(str.toUpperCase(), function(char) { var index = digits.indexOf(aliases[char] || char); return (index == -1 ? " " : index).toString(32); }).join(""), 32); } Ox.encodeBase64 = function(num) { /* >>> Ox.encodeBase64(32394) "foo" */ return btoa(Ox.encodeBase256(num)).replace(/=/g, ""); } Ox.decodeBase64 = function(str) { /* >>> Ox.decodeBase64("foo") 32394 */ return Ox.decodeBase256(atob(str)); } Ox.encodeBase128 = function(num) { /* >>> Ox.encodeBase128(1685487) "foo" */ var str = ""; while (num) { str = Ox.char(num & 127) + str; num >>= 7; } return str; } Ox.decodeBase128 = function(str) { /* >>> Ox.decodeBase128("foo") 1685487 */ var num = 0, len = str.length; Ox.each(str, function(i, char) { num += char.charCodeAt(0) << (len - i - 1) * 7; }); return num; } Ox.encodeBase256 = function(num) { /* >>> Ox.encodeBase256(6713199) "foo" */ var str = ""; while (num) { str = Ox.char(num & 255) + str; num >>= 8; } return str; } Ox.decodeBase256 = function(str) { /* >>> Ox.decodeBase256("foo") 6713199 */ var num = 0, len = str.length; Ox.each(str, function(i, char) { num += char.charCodeAt(0) << (len - i - 1) * 8; }); return num; } Ox.encodeDeflate = function(str) { // encodes string, using deflate /* in fact, the string is written to the rgb channels of a canvas element, then the dataURL is decoded from base64, and some head and tail cut off */ str = Ox.encodeUTF8(str); var len = str.length, c = Ox.canvas(Math.ceil((4 + len) / 3), 1), data; str = Ox.pad(Ox.encodeBase256(len), 4, Ox.char(0)) + str + Ox.repeat("\u00FF", (4 - len % 4) % 4); // simpler? Ox.pad()? /* fixme: why does map not work here? c.data = $.map(c.data, function(v, i) { return i % 4 < 3 ? str.charCodeAt(i - parseInt(i / 4)) : 255; }); */ for (i = 0; i < c.data.length; i += 1) { c.data[i] = i % 4 < 3 ? str.charCodeAt(i - parseInt(i / 4)) : 255; } c.context.putImageData(c.imageData, 0, 0); data = atob(c.canvas.toDataURL().split(",")[1]); //Ox.print("deflate", len, "->", data.length - 20); return data.substr(8, data.length - 20); } Ox.decodeDeflate = function(str) { var image = new Image(); image.src = "data:image/png;base64," + btoa("\u0089PNG\r\n\u001A\n" + str + Ox.repeat("\u0000", 4) + "IEND\u00AEB`\u0082"); while (!image.width) {} // wait for image data str = Ox.map(Ox.canvas(image).data, function(v, i) { return i % 4 < 3 ? Ox.char(v) : ""; }).join(""); //Ox.print(str.length, "len", Ox.decodeBase256(str.substr(0, 4)), str) return Ox.decodeUTF8(str.substr(4, Ox.decodeBase256(str.substr(0, 4)))); } Ox.encodeHTML = function() { /* >>> Ox.encodeHTML("'<\"&\">'") "'<"&">'" >>> Ox.encodeHTML("äbçdê") "äbçdê" */ var entities = { '"': """, "&": "&", "'": "'", "<": "<", ">": ">" }; return function(str) { return $.map(Array.prototype.slice.call(str), function(v) { var code = v.charCodeAt(0); return code < 128 ? (v in entities ? entities[v] : v) : "&#x" + Ox.pad(code.toString(16).toUpperCase(), 4) + ";"; }).join(""); }; }(); Ox.decodeHTML = function(str) { /* >>> Ox.decodeHTML("'<"&">'") "'<\"&\">'" >>> Ox.decodeHTML("'<"&">'") "'<\"&\">'" >>> Ox.decodeHTML("äbçdê") "äbçdê" >>> Ox.decodeHTML("äbçdê") "äbçdê" */ // relies on dom, but shorter than using this: // http://www.w3.org/TR/html5/named-character-references.html return $("
").html(str)[0].childNodes[0].nodeValue; }; Ox.encodePNG = function(img, str) { // encodes string into image, returns new image url /* the message is compressed with deflate (by proxy of canvas), then the string (four bytes length) + (length bytes message) is encoded bitwise into the r/g/b bytes of all opaque pixels by flipping, if necessary, the least significant bit, so that (number of "1"-bits of the byte) % 2 is the bit of the string wishlist: - only use deflate if it actually shortens the message - in deflate, strip and later re-insert the chunk types - encode a decoy message into the least significant bit (and flip the second least significant bit, if at all) - write an extra png chunk containing some key */ str = Ox.encodeDeflate(str); var c = Ox.canvas(img), len = str.length, px = 0; if (len == 0 || len > max(img.width, img.height)) { throwPNGError("en") } len = Ox.pad(Ox.encodeBase256(len), 4, Ox.char(0)); Ox.each(Ox.map(len + str, function(byte) { return Ox.map(new Array(8), function(v, i) { return byte.charCodeAt(0) >> 7 - i & 1; }).join(""); }).join(""), function(i, bit) { var index = parseInt((px = seek(c.data, px)) * 4 + i % 3), byte = c.data[index]; c.data[index] = bit == xor(byte) ? byte : byte & 254 | !(byte & 1); px += i % 3 == 2; }); c.context.putImageData(c.imageData, 0, 0); return c.canvas.toDataURL(); } Ox.decodePNG = function(img) { // decodes image, returns string var data = Ox.canvas(img).data, bits = "", str = "", px = 0, i = 0; len = 4, flag = false; do { bits += xor(data[parseInt((px = seek(data, px)) * 4 + i % 3)]); px += i % 3 == 2; if (++i % 8 == 0) { str += Ox.char(parseInt(bits, 2)); bits = ""; len--; if (len == 0 && !flag) { len = Ox.decodeBase256(str); if (len <= 0 || len > max(img.width, img.height)) { Ox.print(len); throwPNGError("de"); } str = ""; flag = true; } } } while (len); try { return Ox.decodeDeflate(str); } catch(e) { Ox.print(e.toString()); throwPNGError("de"); } } Ox.encodeUTF8 = function(str) { /* see http://en.wikipedia.org/wiki/UTF-8 >>> Ox.encodeUTF8("foo") "foo" >>> Ox.encodeUTF8("¥€$") "\u00C2\u00A5\u00E2\u0082\u00AC\u0024" */ return $.map(Array.prototype.slice.call(str), function(char) { // fixme: why not str? var code = char.charCodeAt(0), str = ""; if (code < 128) { str = char; } else if (code < 2048) { str = String.fromCharCode(code >> 6 | 192) + String.fromCharCode(code & 63 | 128); } else { str = String.fromCharCode(code >> 12 | 224) + String.fromCharCode(code >> 6 & 63 | 128) + String.fromCharCode(code & 63 | 128); } return str; }).join(""); } Ox.decodeUTF8 = function(str) { /* >>> Ox.decodeUTF8("foo") "foo" >>> Ox.decodeUTF8("\u00C2\u00A5\u00E2\u0082\u00AC\u0024") "¥€$" */ var bytes = $.map(str, function(v) { return v.charCodeAt(0); }), i = 0, len = str.length, str = ""; while (i < len) { if (bytes[i] <= 128) { str += String.fromCharCode(bytes[i]); i++; } else if (bytes[i] >= 192 && bytes[i] < 240 && i < len - (bytes[i] < 224 ? 1 : 2)) { if (bytes[i + 1] >= 128 && bytes[i + 1] < 192) { if (bytes[i] < 224) { str += String.fromCharCode((bytes[i] & 31) << 6 | bytes[i + 1] & 63); i += 2; } else if (bytes[i + 2] >= 128 && bytes[i + 2] < 192) { str += String.fromCharCode((bytes[i] & 15) << 12 | (bytes[i + 1] & 63) << 6 | bytes[i + 2] & 63); i += 3; } else { throwUTF8Error(bytes[i + 2], i + 2); } } else { throwUTF8Error(bytes[i + 1], i + 1); } } else { throwUTF8Error(bytes[i], i); } } return str; }; })(); /* ================================================================================ Format functions ================================================================================ */ Ox.formatColor = function() { }; Ox.formatCurrency = function(num, str, dec) { return str + Ox.formatNumber(num, dec); }; Ox.formatDate = function() { /* See http://developer.apple.com/documentation/Darwin/Reference/ManPages/man3/strftime.3.html and http://en.wikipedia.org/wiki/ISO_8601 >>> _date = new Date("01/02/05 00:03:04") "Sun Jan 02 2005 00:03:04 GMT+0100 (CET)" >>> Ox.formatDate(_date, "%A") // Full weekday "Sunday" >>> Ox.formatDate(_date, "%a") // Abbreviated weekday "Sun" >>> Ox.formatDate(_date, "%B") // Full month "January" >>> Ox.formatDate(_date, "%b") // Abbreviated month "Jan" >>> Ox.formatDate(_date, "%C") // Century "20" >>> Ox.formatDate(_date, "%c") // US time and date "01/02/05 12:03:04 AM" >>> Ox.formatDate(_date, "%D") // US date "01/02/05" >>> Ox.formatDate(_date, "%d") // Zero-padded day of the month "02" >>> Ox.formatDate(_date, "%e") // Space-padded day of the month " 2" >>> Ox.formatDate(_date, "%F") // Date "2005-01-02" >>> Ox.formatDate(_date, "%G") // Full ISO-8601 year "2004" >>> Ox.formatDate(_date, "%g") // Abbreviated ISO-8601 year "04" >>> Ox.formatDate(_date, "%H") // Zero-padded hour (24-hour clock) "00" >>> Ox.formatDate(_date, "%h") // Abbreviated month "Jan" >>> Ox.formatDate(_date, "%I") // Zero-padded hour (12-hour clock) "12" >>> Ox.formatDate(_date, "%j") // Zero-padded day of the year "002" >>> Ox.formatDate(_date, "%k") // Space-padded hour (24-hour clock) " 0" >>> Ox.formatDate(_date, "%l") // Space-padded hour (12-hour clock) "12" >>> Ox.formatDate(_date, "%M") // Zero-padded minute "03" >>> Ox.formatDate(_date, "%m") // Zero-padded month "01" >>> Ox.formatDate(_date, "%n") // Newline "\n" >>> Ox.formatDate(_date, "%p") // AM or PM "AM" >>> Ox.formatDate(_date, "%Q") // Quarter of the year "1" >>> Ox.formatDate(_date, "%R") // Zero-padded hour and minute "00:03" >>> Ox.formatDate(_date, "%r") // US time "12:03:04 AM" >>> Ox.formatDate(_date, "%S") // Zero-padded second "04" >>> Ox.formatDate(_date, "%s") // Number of seconds since the Epoch "1104620584" >>> Ox.formatDate(_date, "%T") // Time "00:03:04" >>> Ox.formatDate(_date, "%t") // Tab "\t" >>> Ox.formatDate(_date, "%U") // Zero-padded week of the year (00-53, Sunday as first day) "01" >>> Ox.formatDate(_date, "%u") // Decimal weekday (1-7, Monday as first day) "7" >>> Ox.formatDate(_date, "%V") // Zero-padded ISO-8601 week of the year "53" >>> Ox.formatDate(_date, "%v") // Formatted date " 2-Jan-2005" >>> Ox.formatDate(_date, "%W") // Zero-padded week of the year (00-53, Monday as first day) "00" >>> Ox.formatDate(_date, "%w") // Decimal weekday (0-6, Sunday as first day) "0" >>> Ox.formatDate(_date, "%X") // US time "12:03:04 AM" >>> Ox.formatDate(_date, "%x") // US date "01/02/05" >>> Ox.formatDate(_date, "%Y") // Full year "2005" >>> Ox.formatDate(_date, "%y") // Abbreviated year "05" >>> Ox.formatDate(_date, "%Z") // Time zone name "CET" >>> Ox.formatDate(_date, "%z") // Time zone offset "+0100" >>> Ox.formatDate(_date, "%+") // Formatted date and time "Sun Jan 2 00:03:04 CET 2005" >>> Ox.formatDate(_date, "%%") "%" >>> delete _date true >>> Ox.formatDate(new Date("01/01/2000"), "%W") "00" >>> Ox.formatDate(new Date("01/02/2000"), "%W") "00" >>> Ox.formatDate(new Date("01/03/2000"), "%W") "01" */ var ampm = ["AM", "PM"], days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], format = [ ["%", function() {return "%{%}";}], ["c", function() {return "%x %X";}], ["X", function() {return "%r";}], ["x", function() {return "%D";}], ["D", function() {return "%m/%d/%y";}], ["F", function() {return "%Y-%m-%d";}], ["h", function() {return "%b";}], ["R", function() {return "%H:%M";}], ["r", function() {return "%I:%M:%S %p";}], ["T", function() {return "%H:%M:%S";}], ["v", function() {return "%e-%b-%Y";}], ["\\+", function() {return "%a %b %e %H:%M:%S %Z %Y";}], ["A", function(d) {return days[(d.getDay() + 6) % 7];}], ["a", function(d) {return days[(d.getDay() + 6) % 7].toString().substr(0, 3);}], ["B", function(d) {return months[d.getMonth()];}], ["b", function(d) {return months[d.getMonth()].toString().substr(0, 3);}], ["C", function(d) {return d.getFullYear().toString().substr(0, 2);}], ["d", function(d) {return Ox.pad(d.getDate(), 2);}], ["e", function(d) {return Ox.pad(d.getDate(), 2, " ");}], ["G", function(d) {return Ox.getISOYear(d);}], ["g", function(d) {return Ox.getISOYear(d).toString().substr(-2);}], ["H", function(d) {return Ox.pad(d.getHours(), 2);}], ["I", function(d) {return Ox.pad((d.getHours() + 11) % 12 + 1, 2);}], ["j", function(d) {return Ox.pad(Ox.getDayOfTheYear(d), 3);}], ["k", function(d) {return Ox.pad(d.getHours(), 2, " ");}], ["l", function(d) {return Ox.pad(((d.getHours() + 11) % 12 + 1), 2, " ");}], ["M", function(d) {return Ox.pad(d.getMinutes(), 2);}], ["m", function(d) {return Ox.pad((d.getMonth() + 1), 2);}], ["p", function(d) {return ampm[Math.floor(d.getHours() / 12)];}], ["Q", function(d) {return Math.floor(d.getMonth() / 4) + 1;}], ["S", function(d) {return Ox.pad(d.getSeconds(), 2);}], ["s", function(d) {return Math.floor(d.getTime() / 1000);}], ["U", function(d) {return Ox.pad(Ox.getWeek(d), 2);}], ["u", function(d) {return Ox.getISODay(d);}], ["V", function(d) {return Ox.pad(Ox.getISOWeek(d), 2);}], ["W", function(d) {return Ox.pad(Math.floor((Ox.getDayOfTheYear(d) + (Ox.getFirstDayOfTheYear(d) || 7) - 2) / 7), 2);}], ["w", function(d) {return d.getDay();}], ["Y", function(d) {return d.getFullYear();}], ["y", function(d) {return d.getFullYear().toString().substr(-2);}], ["Z", function(d) {return d.toString().split("(")[1].replace(")", "");}], ["z", function(d) {return Ox.getTimezoneOffsetString(d);}], ["n", function() {return "\n";}], ["t", function() {return "\t";}], ["\\{%\\}", function() {return "%";}] ]; $.each(format, function(i, v) { format[i][0] = new RegExp("%" + v[0] + "", "g"); }); return function(date, str) { str = str || date; date = arguments.length == 2 ? date : new Date(); var split; if (typeof date == 'string') { // support YYYY-MM-DD split = date.substr(0, 10).split('-'); if (split.length == 3) { date = [split[1], split[2], split[0]].join('/') + date.substr(10); } } if (Ox.isNumber(date) || Ox.isString(date)) { date = new Date(date); } if (Ox.isDate(date) && date.toString() != 'Invalid Date') { $.each(format, function(i, v) { str = str.replace(v[0], v[1](date)); }); } else { str = ''; } return str; }; }(); Ox.formatDuration = function(sec, dec, format) { /* >>> Ox.formatDuration(123456.789, 3) 1:10:17:36.789 >>> Ox.formatDuration(12345.6789) 03:25:46 >>> Ox.formatDuration(12345.6789, true) 0:03:25:46 >>> Ox.formatDuration(3599.999, 3) 00:59:59.999 >>> Ox.formatDuration(3599.999) 01:00:00 */ var format = arguments.length == 3 ? format : (Ox.isString(dec) ? dec : "short"), dec = (arguments.length == 3 || Ox.isNumber(dec)) ? dec : 0, sec = dec ? sec : Math.round(sec), val = [ Math.floor(sec / 31536000), Math.floor(sec % 31536000 / 86400), Math.floor(sec % 86400 / 3600), Math.floor(sec % 3600 / 60), format == "short" ? Ox.formatNumber(sec % 60, dec) : sec % 60 ], str = { medium: ["y", "d", "h", "m", "s"], long: ["year", "day", "hour", "minute", "second"] }, pad = [0, 3, 2, 2, dec ? dec + 3 : 2]; while (!val[0] && val.length > (format == "short" ? 3 : 1)) { val.shift(); str.medium.shift(); str.long.shift(); pad.shift(); } while (format != "short" && !val[val.length - 1] && val.length > 1) { val.pop(); str.medium.pop(); str.long.pop(); } return $.map(val, function(v, i) { return format == "short" ? Ox.pad(v, pad[i]) : v + (format == "long" ? " " : "") + str[format][i] + (format == "long" && v != 1 ? "s" : ""); }).join(format == "short" ? ":" : " "); }; Ox.formatNumber = function(num, dec) { /* >>> Ox.formatNumber(123456789, 3) "123,456,789.000" >>> Ox.formatNumber(-2000000 / 3, 3) "-666,666.667" >>> Ox.formatNumber(666666.666) "666,667" */ var str = Math.abs(num).toFixed(dec || 0), spl = str.split('.'), arr = []; while (spl[0]) { arr.unshift(spl[0].substr(-3)); spl[0] = spl[0].substr(0, spl[0].length - 3); } spl[0] = arr.join(','); return (num < 0 ? '-' : '') + spl.join('.'); }; Ox.formatPercent = function(num, total, dec) { return Ox.formatNumber(num / total * 100, dec) + '%' }; Ox.formatResolution = function(arr, str) { return arr[0] + ' x ' + arr[1] + ' ' + str; } Ox.formatString = function (s, args) { /* Python(ish) string formatting: * >>> format('{0}', ['zzz']) * "zzz" * >>> format('{x}', {x: 1}) * "1" */ var re = /\{([^}]+)\}/g; return s.replace(re, function(_, match){ return args[match]; }); } Ox.formatValue = function(num, str) { /* >>> Ox.formatValue(0, "B") ??? >>> Ox.formatValue(123456789, "B") ??? */ var arr = ["K", "M", "G", "T", "P"], len = arr.length, val = ""; $.each(arr, function(i, v) { if (num < Math.pow(1024, i + 2) || i == len - 1) { val = Ox.formatNumber(num / Math.pow(1024, i + 1), i) + " " + v + str; return false; } }); return val; }; Ox.formatUnit = function(num, str) { return num + ' ' + str; }; /* ================================================================================ Geo functions ================================================================================ */ Ox.getArea = function(point0, point1) { }; Ox.getCenter = function(point0, point1) { }; Ox.getDistance = function(point0, point1) { point0 = point0.map(function(deg) { return Ox.rad(deg); }); point1 = point1.map(function(deg) { return Ox.rad(deg); }); } Ox.getMetersPerDegree = function(point) { return Math.cos(point[0] * Math.PI / 180) * Ox.EARTH_CIRCUMFERENCE / 360; }; /* ================================================================================ Math functions ================================================================================ */ Ox.asinh = function(x) { /* fixme: no test */ return Math.log(x + Math.sqrt(x * x + 1)); }; Ox.deg = function(rad) { /* >>> Ox.deg(2 * Math.PI) 360 */ return rad * 180 / Math.PI; }; Ox.divideInt = function(num, by) { /* >>> Ox.divideInt(100, 3) [33, 33, 34] >>> Ox.divideInt(100, 6) [16, 16, 17, 17, 17, 17] */ var arr = [], div = parseInt(num / by), mod = num % by, i; for (i = 0; i < by; i++) { arr[i] = div + (i > by - 1 - mod); } return arr; } Ox.limit = function(num, min, max) { /* >>> Ox.limit(1, 2, 3) 2 >>> Ox.limit(2, 1) 1 */ var len = arguments.length; max = arguments[len - 1]; min = len == 3 ? min : 0; return Math.min(Math.max(num, min), max); }; Ox.log = function(x, base) { /* >>> Ox.log(100, 10) 2 >>> Ox.log(Math.E) 1 */ return Math.log(x) / Math.log(base || Math.E); }; Ox.rad = function(deg) { /* >>> Ox.rad(360) 2 * Math.PI */ return deg * Math.PI / 180; }; Ox.random = function() { /* >>> Ox.random(3) in {0: 0, 1: 0, 2: 0, 3: 0} true >>> Ox.random(1, 2) in {1: 0, 2: 0} true */ var len = arguments.length, min = len == 1 ? 0 : arguments[0], max = arguments[len - 1]; return min + parseInt(Math.random() * (max - min + 1)); }; Ox.round = function(num, dec) { /* >>> Ox.round(2 / 3, 6) 0.666667 >>> Ox.round(1 / 2, 3) 0.5 */ var pow = Math.pow(10, dec || 0); return Math.round(num * pow) / pow; }; Ox.sinh = function(x) { /* fixme: no test */ return (Math.exp(x) - Math.exp(-x)) / 2; }; /* ================================================================================ RegExp functions ================================================================================ */ Ox.regexp = { 'accents': '¨´`ˆ˜', 'letters': 'a-z¨´`ˆ˜äåáàâãæçëéèèñïíìîöóòôõøœßúùûÿ' }; /* ================================================================================ String functions ================================================================================ */ Ox.basename = function(str) { /* fixme: this should go into Path functions >>> Ox.basename("foo/bar/foo.bar") "foo.bar" >>> Ox.basename("foo.bar") "foo.bar" */ return str.replace(/^.*[\/\\]/g, ""); }; Ox.char = String.fromCharCode; Ox.clean = function(str) { /* >>> Ox.clean("foo bar") "foo bar" >>> Ox.clean(" foo bar ") "foo bar" */ return Ox.trim(str.replace(/\s+/g, " ")); }; Ox.contains = function(str, chr) { /* >>> Ox.contains("foo", "bar") false >>> Ox.contains("foobar", "bar") true */ return str.indexOf(chr) > -1; }; Ox.endsWith = function(str, sub) { /* >>> Ox.endsWith("foobar", "bar") true */ return str.substr(-sub.length) === sub; }; Ox.highlight = function(txt, str) { // fixme: move to ox.ui return str ? txt.replace( new RegExp('(' + str + ')', 'ig'), '$1' ) : txt; }; Ox.isValidEmail = function(str) { return !!/^[0-9A-Z\.\+\-_]+@(?:[0-9A-Z\-]+\.)+[A-Z]{2,6}$/i(str); } Ox.pad = function(str, len, pad, pos) { /* >>> Ox.pad(1, 2) "01" >>> Ox.pad("abc", 6, ".", "right") "abc..." >>> Ox.pad("foobar", 3, ".", "right") "foo" >>> Ox.pad("abc", 6, "123456", "right") "abc123" >>> Ox.pad("abc", 6, "123456", "left") "456abc" */ str = str.toString().substr(0, len); pad = Ox.repeat(pad || "0", len - str.length); pos = pos || "left"; str = pos == "left" ? pad + str : str + pad; str = pos == "left" ? str.substr(str.length - len, str.length) : str.substr(0, len); return str; }; Ox.repeat = function(str, num) { /* >>> Ox.repeat(1, 3) "111" >>> Ox.repeat("foo", 3) "foofoofoo" */ return num >= 1 ? new Array(num + 1).join(str.toString()) : ''; }; Ox.reverse = function(str) { /* Ox.reverse("foo") oof */ return str.split("").reverse().join(""); }; Ox.startsWith = function(str, sub) { /* >>> Ox.startsWith("foobar", "foo") true */ return str.substr(0, sub.length) === sub; }; Ox.stripTags = function(str) { /* >>> Ox.stripTags("foo") foo */ return str.replace(/(<.*?>)/gi, ""); }; Ox.toCamelCase = function(str) { /* >>> Ox.toCamelCase("foo-bar-baz") "fooBarBaz" >>> Ox.toCamelCase("foo/bar/baz") "fooBarBaz" >>> Ox.toCamelCase("foo_bar_baz") "fooBarBaz" */ return str.replace(/[\-_\/][a-z]/g, function(str) { return str[1].toUpperCase(); }); }; Ox.toDashes = function(str) { /* >>> Ox.toDashes("fooBarBaz") "foo-bar-baz" */ return str.replace(/[A-Z]/g, function(str) { return '-' + str.toLowerCase(); }); }; Ox.toSlashes = function(str) { /* >>> Ox.toSlashes("fooBarBaz") "foo/bar/baz" */ return str.replace(/[A-Z]/g, function(str) { return '/' + str.toLowerCase(); }); }; Ox.toTitleCase = function(str) { /* >>> Ox.toTitleCase("foo") "Foo" >>> Ox.toTitleCase("Apple releases iPhone, IBM stock plummets") "Apple Releases iPhone, IBM Stock Plummets" */ return $.map(str.split(' '), function(v) { var sub = v.substr(1), low = sub.toLowerCase(); if (sub == low) { v = v.substr(0, 1).toUpperCase() + low; } return v; }).join(" "); }; Ox.toUnderscores = function(str) { /* >>> Ox.toUnderscores("fooBarBaz") "foo_bar_baz" */ return str.replace(/[A-Z]/g, function(str) { return '_' + str.toLowerCase(); }); }; Ox.trim = function(str) { // is in jQuery /* Ox.trim(" foo ") "foo" */ return str.replace(/^\s+|\s+$/g, ""); }; Ox.truncate = function(str, len, pad, pos) { /* >>> Ox.truncate("anticonstitutionellement", 16, "...", "center") anticon...lement */ var pad = pad || {}, pos = pos || "right", strlen = str.length, padlen = pad.length, left, right; if (strlen > len) { if (pos == "left") { str = pad + str.substr(padlen + strlen - len); } else if (pos == "center") { left = Math.ceil((len - padlen) / 2); right = Math.floor((len - padlen) / 2); str = str.substr(0, left) + pad + str.substr(-right); } else if (pos == "right") { str = str.substr(0, len - padlen) + pad; } } return str; }; Ox.wordwrap = function(str, len, sep, bal, spa) { /* >>> Ox.wordwrap("Anticonstitutionellement, Paris s'eveille", 25, "
"") Anticonstitutionellement,
Paris s'eveille >>> Ox.wordwrap("Anticonstitutionellement, Paris s'eveille", 16, "
") Anticonstitution
ellement, Paris
s'eveille >>> Ox.wordwrap("These are short words", 16, "
", true) These are
short words */ var str = str === null ? '' : str.toString(), len = len || 80, sep = sep || "
", bal = bal || false, spa = Ox.isUndefined(spa) ? true : spa, words = str.split(" "), lines; if (bal) { // balance lines: test if same number of lines // can be achieved with a shorter line length lines = Ox.wordwrap(str, len, sep, false).split(sep); if (lines.length > 1) { // test shorter line, unless // that means cutting a word var max = Ox.max($.map(words, function(word) { return word.length; })); while (len > max) { len--; if (Ox.wordwrap(str, len, sep, false).split(sep).length > lines.length) { len++; break; } } } } lines = [""]; $.each(words, function(i, word) { if ((lines[lines.length - 1] + word + " ").length <= len + 1) { // word fits in current line lines[lines.length - 1] += word + " "; } else { if (word.length <= len) { // word fits in next line lines.push(word + " "); } else { // word is longer than line var chr = len - lines[lines.length - 1].length; lines[lines.length - 1] += word.substr(0, chr); for (var pos = chr; pos < word.length; pos += len) { lines.push(word.substr(pos, len)); } lines[lines.length - 1] += " "; } } }); if (!spa) { lines = $.map(lines, function(line) { return Ox.trim(line); }); } return Ox.trim(lines.join(sep)); }; /* ================================================================================ Type functions ================================================================================ */ Ox.isArray = function(val) { // is in jQuery /* >>> Ox.isArray([]) true */ return val instanceof Array; } Ox.isBoolean = function(val) { /* >>> Ox.isBoolean(false) true */ return typeof val == 'boolean'; }; Ox.isDate = function(val) { /* >>> Ox.isDate(new Date()) true */ return val instanceof Date; }; Ox.isElement = function(val) { return !!(val && val.nodeType == 1); } Ox.isFunction = function(val) { // is in jQuery /* >>> Ox.isFunction(function() {}) true >>> Ox.isFunction(/ /) false */ return typeof val == 'function' && !Ox.isRegExp(val); }; Ox.isNaN = function(val) { /* >>> Ox.isNaN(NaN) true */ return val !== val; } Ox.isNull = function(val) { /* >>> Ox.isNull(null) true */ return val === null; }; Ox.isNumber = function(val) { /* >>> Ox.isNumber(0) true >>> Ox.isNumber(Infinity) false >>> Ox.isNumber(NaN) false */ return typeof val == 'number' && isFinite(val); }; Ox.isObject = function(val) { /* >>> Ox.isObject({}) true >>> Ox.isObject([]) false >>> Ox.isObject(new Date()) false >>> Ox.isObject(null) false */ return typeof val == 'object' && !$.isArray(val) && !Ox.isDate(val) && !Ox.isNull(val); }; Ox.isRegExp = function(val) { /* >>> Ox.isRegExp(/ /) true */ return val instanceof RegExp; }; Ox.isString = function(val) { /* >>> Ox.isString('') true */ return typeof val == 'string'; }; Ox.isUndefined = function(val) { /* >>> Ox.isUndefined() true */ return typeof val == 'undefined'; };