oxjs/build/js/ox.js
2011-02-09 17:56:35 +00:00

2293 lines
62 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// todo: check http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
/*
Ox = function(val) {
};
*/
if (!window.Ox) {
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.HTML_ENTITIES = {'"': '"', '&': '&', "'": ''',
'<': '&lt;', '>': '&gt;'};
Ox.MONTHS = ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'];
Ox.PREFIXES = ['K', 'M', 'G', 'T', 'P'];
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 (window.console) {
var args = $.makeArray(arguments),
date = new Date;
args.unshift(Ox.formatDate(date, '%H:%M:%S') + '.' +
(Ox.pad(+date % 1000, 3)));
window.console.log.apply(window.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<td class=output align="center">\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.count = function(arr) {
/*
Ox.count(['foo', 'bar', 'foo']).foo
2
*/
var obj = {};
arr.forEach(function(v) {
obj[v] = (obj[v] || 0) + 1;
});
return obj;
};
Ox.each = function(obj, fn) {
// fixme: deprecate!
/*
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, isArray = Ox.isArray(obj);
for (i in obj) {
i = isArray ? parseInt(i) : i;
// fixme: should be (v, k), like [].forEach()
if (fn(i, obj[i]) === false) {
break;
}
}
return obj;
};
Ox.equals = function(obj0, obj1) {
/*
>>> Ox.equals([1, 2, 3], [1, 2, 3])
true
>>> Ox.equals({a: 1, b: [2, 3], c: {d: '4'}}, {a: 1, b: [2, 3], c: {d: '4'}})
true
>>> Ox.equals(function() { return; }, function() { return; });
true
*/
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.forEach(obj0, function(v, i) {
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();
}
}
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() {
/*
>>> Ox.extend({a: 1}, {b: 2}, {c: 3}).c
3
*/
var obj = {};
Ox.forEach(Array.prototype.slice.call(arguments, 1), function(arg, i) {
Ox.forEach(arg, function(val, key) {
obj[key] = val;
});
});
return obj;
};
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.forEach = function(obj, fn) {
/*
Ox.forEach() works for arrays, objects and strings,
like $.each(), unlike [].forEach()
The arguments of the iterator function are (value, key),
like [].forEach(), unlike $.each()
>>> Ox.forEach([0, 1, 2], function(v, i) {})
[0, 1, 2]
>>> Ox.forEach({a: 1, b: 2, c: 3}, function(v, k) {}).a
1
>>> Ox.forEach('foo', function(v, i) {})
'foo'
*/
var key, isArray = Ox.isArray(obj);
for (key in obj) {
key = isArray ? parseInt(key) : key;
if (fn(obj[key], key) === false) {
break;
}
}
return obj;
};
Ox.getObjectById = function(arr, id) {
/***
>>> Ox.getObjectById([{id: "foo", title: "Foo"}, {id: "bar", title: "Bar"}], "foo").title
"Foo"
***/
var ret = null;
Ox.forEach(arr, function(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.forEach(arr, function(v, i) {
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.forEach(obj, function(v, k) {
keys.push(k);
});
return keys;
};
Ox.length = function(obj) {
/*
>>> Ox.length({"a": 1, "b": 2, "c": 3})
3
*/
var length = 0;
Ox.forEach(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.merge(['foo'], ['bar'], ['baz'])
['foo', 'bar', 'baz']
*/
Ox.forEach(Array.prototype.slice.call(arguments, 1), function(arg) {
Ox.forEach(arg, function(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: 1, b: 2, c: 3})
'a=1&b=2&c=3'
*/
var arr = [];
Ox.forEach(obj, function(val, key) {
val !== '' && arr.push(key + '=' + val);
});
return arr.join('&');
};
Ox.setPropertyOnce = function(arr, str) {
/*
>>> Ox.setPropertyOnce([{selected: false}, {selected: false}])
0
>>> Ox.setPropertyOnce([{selected: true}, {selected: true}])
0
*/
var pos = -1;
Ox.forEach(arr, function(v, i) {
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.forEach(obj, function(val) {
sum += val;
});
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) {
/*
>>> Ox.unserialize('a=1&b=2&c=3').c
'3'
*/
var arr, obj = {};
Ox.forEach(str.split('&'), function(val) {
arr = val.split('=');
obj[arr[0]] = arr[1];
});
return obj;
};
Ox.values = function(obj) {
/*
>>> Ox.values({a: 1, b: 2, c: 3})
[1, 2, 3]
>>> Ox.values([1, 2, 3])
[1, 2, 3]
*/
var values = [];
Ox.forEach(obj, function(val) {
values.push(val);
});
return values;
};
Ox.zip = function() {
/*
>>> 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.forEach(Ox.makeObject.apply(this, arguments), function(v, k) {
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 cap(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;
Ox.range(8).forEach(function(i) {
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()
'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.forEach(str, function(char, i) {
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.forEach(str, function(char, i) {
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);
Ox.print(c.canvas.toDataURL())
data = atob(c.canvas.toDataURL().split(',')[1]);
Ox.print('data', data);
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');
Ox.print(image.src);
while (!image.width) {} // block until image data is available
str = Ox.map(Ox.canvas(image).data, function(v, i) {
return i % 4 < 3 ? Ox.char(v) : '';
}).join('');
return Ox.decodeUTF8(str.substr(4, Ox.decodeBase256(str.substr(0, 4))));
}
Ox.encodeHTML = function(str) {
/*
>>> Ox.encodeHTML('\'<"&">\'')
'&apos;&lt;&quot;&amp;&quot;&gt;&apos;'
>>> Ox.encodeHTML('äbçdê')
'&#x00E4;b&#x00E7;d&#x00EA;'
*/
return $.map(str, function(v) {
var code = v.charCodeAt(0);
return code < 128 ? (v in Ox.HTML_ENTITIES ? Ox.HTML_ENTITIES[v] : v) :
'&#x' + Ox.pad(code.toString(16).toUpperCase(), 4) + ';';
}).join('');
};
Ox.decodeHTML = function(str) {
/*
>>> Ox.decodeHTML('&apos;&lt;&quot;&amp;&quot;&gt;&apos;')
'\'<"&">\''
>>> Ox.decodeHTML('&#x0027;&#x003C;&#x0022;&#x0026;&#x0022;&#x003E;&#x0027;')
'\'<"&">\''
>>> Ox.decodeHTML('&auml;b&ccedil;d&ecirc;')
'äbçdê'
>>> Ox.decodeHTML('&#x00E4;b&#x00E7;d&#x00EA;')
'äbçdê'
*/
// relies on dom, but shorter than using this:
// http://www.w3.org/TR/html5/named-character-references.html
return $('<div/>').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); currently broken
str = Ox.encodeUTF8(str);
var c = Ox.canvas(img), len = str.length, px = 0;
if (len == 0 || len > cap(img.width, img.height)) {
throwPNGError('en')
}
len = Ox.pad(Ox.encodeBase256(len), 4, Ox.char(0));
Ox.forEach(Ox.map(len + str, function(byte) {
return Ox.map(Ox.range(8), function(i) {
return byte.charCodeAt(0) >> 7 - i & 1;
}).join('');
}).join(''), function(bit, i) {
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 bits = '', data = Ox.canvas(img).data, flag = false, i = 0,
len = 4, max = cap(img.width, img.height), px = 0, str = '';
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) {
Ox.print(len);
throwPNGError('de');
}
str = '';
flag = true;
}
}
} while (len);
try {
//return Ox.decodeDeflate(str); currently broken
return Ox.decodeUTF8(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(chr) {
var code = chr.charCodeAt(0),
str = '';
if (code < 128) {
str = chr;
} 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) {
/*
>>> Ox.formatPercent(1, 1000, 2)
"0.10%"
*/
return Ox.formatNumber(num / total * 100, dec) + '%'
};
Ox.formatResolution = function(arr, str) {
/*
>>> Ox.formatResolution([1920, 1080], 'px')
"1920 x 1080 px"
*/
return arr[0] + ' x ' + arr[1] + (str ? ' ' + str : '');
}
Ox.formatString = function (str, obj) {
/*
>>> Ox.formatString('{0}{1}', ['foo', 'bar'])
"foobar"
>>> Ox.formatString('{a}{b}', {a: 'foo', b: 'bar'})
"foobar"
*/
return str.replace(/\{([^}]+)\}/g, function(str, match) {
return obj[match];
});
}
Ox.formatValue = function(num, str, bin) {
/*
>>> Ox.formatValue(0, "B")
"0 KB"
>>> Ox.formatValue(123456789, "B")
"123.5 MB"
>>> Ox.formatValue(1234567890, "B", true)
"1.15 GiB"
*/
var base = bin ? 1024 : 1000,
len = Ox.PREFIXES.length,
val;
Ox.forEach(Ox.PREFIXES, function(chr, i) {
if (num < Math.pow(base, i + 2) || i == len - 1) {
val = Ox.formatNumber(num / Math.pow(base, i + 1), i) +
' ' + chr + (bin ? 'i' : '') + 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;
};
/*
================================================================================
HTML functions
================================================================================
*/
Ox.parseEmailAddresses = function(html) {
return html.replace(
/\b([0-9A-Z\.\+\-_]+@(?:[0-9A-Z\-]+\.)+[A-Z]{2,6})\b/gi,
'<a href="mailto:$1" title="mailto:$1">$1</a>'
);
};
Ox.parseHTML = (function() {
/*
>>> Ox.parseHTML('http://foo.com, bar')
'<a href="http://foo.com" title="http://foo.com">foo.com</a>, bar'
>>> Ox.parseHTML('(see: www.foo.com)')
'(see: <a href="http://www.foo.com" title="http://www.foo.com">www.foo.com</a>)'
>>> Ox.parseHTML('foo@bar.com')
'<a href="mailto:foo@bar.com" title="mailto:foo@bar.com">foo@bar.com</a>'
>>> Ox.parseHTML('<a href="http://foo.com" onmouseover="alert()">foo</a>')
'<a href="http://foo.com" title="http://foo.com">foo</a>'
>>> Ox.parseHTML('<a href="javascript:alert()">foo</a>')
'&lt;a href=&quot;javascript:alert()&quot;&gt;foo&lt;/a&gt;'
>>> Ox.parseHTML('[http://foo.com foo]')
'<a href="http://foo.com" title="http://foo.com">foo</a>'
>>> Ox.parseHTML('<rtl>foo</rtl>')
'<div style="direction: rtl">foo</div>'
>>> Ox.parseHTML('<script>alert()</script>')
'&lt;script&gt;alert()&lt;/script&gt;'
*/
var defaultTags = [
'a', 'b', 'blockquote', 'cite', 'code', 'del',
'em', 'i', 'img', 'ins', 'li', 'ol', 'q', 'rtl',
's', 'strong', 'sub', 'sup', 'ul', '[]'
],
parse = {
a: {
'<a [^<>]*?href="(https?:\/\/.+?)".*?>': '<a href="{1}" title="{1}">',
'<\/a>': '</a>'
},
img: {
'<img [^<>]*?src="(https?:\/\/.+?)".*?>': '<img src="{1}">'
},
rtl: {
'<rtl>': '<div style="direction: rtl">',
'<\/rtl>': '</div>'
},
'*': function(tag) {
var ret = {};
ret['<(/?' + tag + ')>'] = '<{1}>';
return ret;
}
},
tab = '\t';
return function(html, tags, wikilinks) {
var matches = [],
tags = tags || defaultTags;
html = Ox.clean(html);
if (tags.indexOf('[]') > -1) {
html = html.replace(/\[(https?:\/\/.+?) (.+?)\]/gi, '<a href="$1">$2</a>');
tags = tags.filter(function(tag) {
return tag != '[]';
});
}
tags.forEach(function(tag) {
var p = parse[tag] || parse['*'](tag);
Ox.forEach(p, function(replace, regexp) {
html = html.replace(new RegExp(regexp, 'gi'), function() {
matches.push(Ox.formatString(replace, arguments));
return tab + (matches.length - 1) + tab;
});
});
});
html = Ox.encodeHTML(html);
html = Ox.parseURLs(html);
html = Ox.parseEmailAddresses(html);
matches.forEach(function(match, i) {
html = html.replace(new RegExp(tab + i + tab, 'gi'), match);
});
html = html.replace(/\n/g, '<br/>\n')
// close extra opening (and remove extra closing) tags
return $('<div>').html(html).html();
}
}());
Ox.parseURL = (function() {
// fixme: leak memory, like now, or create every time? ... benchmark??
var a = document.createElement('a'),
keys = ['hash', 'host', 'hostname', 'origin',
'pathname', 'port', 'protocol', 'search'];
return function(str) {
var ret = {};
a.href = str;
keys.forEach(function(key) {
ret[key] = a[key];
});
return ret;
};
}());
Ox.parseURLs = function(html) {
return html.replace(
/\b((https?:\/\/|www\.).+?)([\.,:;!\?\)\]]*?(\s|$))/gi,
function(str, url, pre, end) {
url = (pre == 'www.' ? 'http://' : '' ) + url;
return Ox.formatString(
'<a href="{url}" title="{url}">{host}</a>{end}',
{
end: end,
host: Ox.parseURL(url).hostname,
url: url
}
);
}
);
};
/*
================================================================================
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"
>>> Ox.clean(" foo \n bar ")
"foo\nbar"
*/
return Ox.map(str.split('\n'), function(str) {
return Ox.trim(str.replace(/\s+/g, ' '));
}).join('\n');
};
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'),
'<span class="OxHighlight">$1</span>'
) : txt;
};
Ox.isValidEmail = function(str) {
/*
>>> Ox.isValidEmail("foo@bar.com")
true
>>> Ox.isValidEmail("foo.bar@foobar.co.uk")
true
>>> Ox.isValidEmail("foo@bar")
false
*/
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("f<span>o</span>o")
"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.words = function(str) {
/*
>>> Ox.words('He\'s referring to the "ill-conceived" AOL/TimeWarner merger--didn\'t you know?')
"he's,referring,to,the,ill-conceived,aol,timewarner,merger,didn't,you,know"
*/
var arr = str.toLowerCase().split(/\b/),
chr = "-'",
len = arr.length,
startsWithWord = !!/\w/(arr[0]);
arr.forEach(function(v, i) {
// find single occurrences of chars in chr
// that are not at the beginning or end of str
// and join the surrounding words with them
if (
i > 0 && i < len - 1 &&
v.length == 1 && chr.indexOf(v) > -1
) {
arr[i + 1] = arr[i - 1] + arr[i] + arr[i + 1];
arr[i - 1] = arr[i] = '';
}
});
// remove elements that have been emptied above
arr = arr.filter(function(v) {
return v.length;
});
// return words, not spaces or punctuation
return arr.filter(function(v, i) {
return i % 2 == !startsWithWord;
});
}
Ox.wordwrap = function(str, len, sep, bal, spa) {
/*
>>> Ox.wordwrap("Anticonstitutionellement, Paris s'eveille", 25, "<br/>")
"Anticonstitutionellement, <br/>Paris s'eveille"
>>> Ox.wordwrap("Anticonstitutionellement, Paris s'eveille", 16, "<br/>")
"Anticonstitution<br/>ellement, Paris <br/>s'eveille"
>>> Ox.wordwrap("These are short words", 16, "<br/>", true)
"These are <br/>short words"
*/
var str = str === null ? '' : str.toString(),
len = len || 80,
sep = sep || "<br/>",
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';
};