5137 lines
160 KiB
JavaScript
5137 lines
160 KiB
JavaScript
// vim: et:ts=4:sw=4:sts=4:ft=javascript
|
||
|
||
// OxJS (c) 2011 0x2620, dual-licensed GPL/MIT, see http://oxjs.org for details
|
||
|
||
/*
|
||
Some conventions:
|
||
Functions
|
||
- only one var statement, in the first line of the function
|
||
- return only once, from the last line of the function body
|
||
Variable names
|
||
arg argument
|
||
args arguments
|
||
arr array
|
||
callback callback function
|
||
col collection (array, string or object)
|
||
date date
|
||
fn function
|
||
hasFoo boolean
|
||
i index (integer key)
|
||
isFoo boolean
|
||
k key (of a key/value pair)
|
||
key key (of a key/value pair)
|
||
max maximum value
|
||
min minumum value
|
||
num number
|
||
obj object
|
||
re regexp
|
||
ret return value
|
||
v value (of a key/value pair)
|
||
val value (of a key/value pair)
|
||
Indentation
|
||
var a = 1,
|
||
b = 2,
|
||
c = 3;
|
||
Obj.fn1()
|
||
.fn2()
|
||
.fn3();
|
||
*/
|
||
|
||
// todo: check http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
|
||
// also see https://github.com/tlrobinson/narwhal/blob/master/lib/util.js
|
||
|
||
|
||
|
||
// see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/filter
|
||
if (!Array.prototype.filter) {
|
||
Array.prototype.filter = function(fn, that) {
|
||
if (this === void 0 || this === null || typeof fn !== 'function') {
|
||
throw new TypeError();
|
||
}
|
||
var arr = Object(this),
|
||
i,
|
||
len = arr.length >>> 0,
|
||
ret = [],
|
||
val;
|
||
for (i = 0; i < len; i++) {
|
||
// save val in case fn mutates it
|
||
if (i in arr && fn.call(that, val = arr[i], i, arr)) {
|
||
ret.push(val);
|
||
}
|
||
}
|
||
return ret;
|
||
};
|
||
}
|
||
|
||
// see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach
|
||
if (!Array.prototype.forEach) {
|
||
Array.prototype.forEach = function(fn, that) {
|
||
if (this === void 0 || this === null || typeof fn !== 'function') {
|
||
throw new TypeError();
|
||
}
|
||
var arr = Object(this),
|
||
i,
|
||
len = arr.length >>> 0;
|
||
for (i = 0; i < len; i++) {
|
||
if (i in arr) {
|
||
fn.call(that, arr[i], i, arr);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
|
||
if (!Array.prototype.indexOf) {
|
||
Array.prototype.indexOf = function(val) {
|
||
if (this === void 0 || this === null) {
|
||
throw new TypeError();
|
||
}
|
||
var arr = Object(this),
|
||
i,
|
||
len = arr.length >>> 0;
|
||
ret = -1;
|
||
for (i = 0; i < len; i++) {
|
||
if (i in arr && arr[i] === val) {
|
||
ret = val;
|
||
break
|
||
}
|
||
}
|
||
return ret;
|
||
}
|
||
}
|
||
|
||
// see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/map
|
||
if (!Array.prototype.map) {
|
||
Array.prototype.map = function(fn, that) {
|
||
if (this === void 0 || this === null || typeof fn !== 'function') {
|
||
throw new TypeError();
|
||
}
|
||
var arr = Object(this),
|
||
i,
|
||
len = arr.length >>> 0,
|
||
ret = new Array(len);
|
||
for (i = 0; i < len; i++) {
|
||
if (i in arr) {
|
||
ret[i] == fn.call(that, arr[i], i, arr);
|
||
}
|
||
}
|
||
return ret;
|
||
}
|
||
}
|
||
|
||
// see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/reduce
|
||
if (!Array.prototype.reduce) {
|
||
Array.prototype.reduce = function reduce(fn, ret) {
|
||
if (this === void 0 || this === null || typeof fn !== 'function') {
|
||
throw new TypeError();
|
||
}
|
||
var arr = Object(this),
|
||
i,
|
||
len = this.length;
|
||
if (!len && ret === void 0) {
|
||
throw new TypeError();
|
||
}
|
||
if (ret === void 0) {
|
||
ret = arr[0];
|
||
i = 1;
|
||
}
|
||
for (i = i || 0; i < len ; ++i) {
|
||
if (i in arr) {
|
||
ret = fn.call(void 0, ret, arr[i], i, arr);
|
||
}
|
||
}
|
||
return ret;
|
||
};
|
||
}
|
||
|
||
// see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys
|
||
if (!Object.keys) {
|
||
Object.keys = function(obj) {
|
||
if (obj !== Object(obj)) {
|
||
throw new TypeError();
|
||
}
|
||
var key,
|
||
ret = [];
|
||
for (key in obj) {
|
||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||
ret.push(key);
|
||
}
|
||
}
|
||
return ret;
|
||
}
|
||
}
|
||
|
||
//@ Core -----------------------------------------------------------------------
|
||
|
||
/*@
|
||
Ox <f> The <code>Ox</code> object
|
||
See <code>Ox.wrap</code> for details.
|
||
(value) -> <o> wrapped value
|
||
value <*> Any value
|
||
@*/
|
||
|
||
Ox = function(val) {
|
||
return Ox.wrap(val);
|
||
};
|
||
|
||
/*@
|
||
Ox.load <f> Loads a module
|
||
(module, callback) -> <u> undefined
|
||
(module, options, callback) -> <u> undefined
|
||
(modules, callback) -> <u> undefined
|
||
module <s> Module name
|
||
modules <o> Multiple modules {name: options}
|
||
options <o> Module options
|
||
callback <f> Callback function
|
||
success <b> If true, the module has been loaded successfully
|
||
@*/
|
||
|
||
Ox.load = function() {
|
||
var callback = arguments[arguments.length - 1],
|
||
counter = 0,
|
||
isObject = Ox.isObject(arguments[0]),
|
||
length,
|
||
modules = isObject ? arguments[0] : {},
|
||
success = 0;
|
||
if (!isObject) {
|
||
modules[arguments[0]] = Ox.isObject(arguments[1]) ? arguments[1] : {};
|
||
}
|
||
length = Ox.len(modules)
|
||
Ox.forEach(modules, function(options, module) {
|
||
Ox.loadFile(Ox.PATH + 'Ox.' + module + '/Ox.' + module + '.js', function() {
|
||
Ox.load[module](options, function(s) {
|
||
success += s;
|
||
++counter == length && callback(success == counter);
|
||
});
|
||
});
|
||
});
|
||
};
|
||
|
||
/*@
|
||
Ox.print <f> Prints its arguments to the console
|
||
(arg, ...) -> <s> String
|
||
The string contains the timestamp, the name of the caller function, and
|
||
any arguments, separated by spaces
|
||
arg <*> any value
|
||
> Ox.print('foo').split(' ').pop()
|
||
"foo"
|
||
@*/
|
||
|
||
Ox.print = function() {
|
||
var args = Ox.makeArray(arguments),
|
||
date = new Date();
|
||
args.unshift(
|
||
Ox.formatDate(date, '%H:%M:%S.') + (+date).toString().substr(-3),
|
||
(arguments.callee.caller && arguments.callee.caller.name)
|
||
|| '(anonymous)'
|
||
);
|
||
window.console && window.console.log.apply(window.console, args);
|
||
return args.join(' ');
|
||
};
|
||
|
||
/*@
|
||
Ox.uid <f> Returns a unique id
|
||
() -> <n> Unique id
|
||
> Ox.uid() != Ox.uid()
|
||
true
|
||
@*/
|
||
Ox.uid = (function() {
|
||
var uid = 0;
|
||
return function() {
|
||
return uid++;
|
||
};
|
||
}());
|
||
|
||
/*@
|
||
Ox.wrap <f> Wraps a value so that one can directly call any Ox function on it
|
||
(value) -> <o> wrapped value
|
||
chain <f> wrap the return value to allow chaining
|
||
value <f> unwrap the value wrapped by <code>chain()</chain>
|
||
value <*> Any value
|
||
> Ox.wrap("foobar").repeat(2)
|
||
"foobarfoobar"
|
||
> Ox.wrap("foobar").chain().reverse().toTitleCase().value()
|
||
"Raboof"
|
||
> Ox.wrap("foobar").value()
|
||
"foobar"
|
||
@*/
|
||
|
||
Ox.wrap = function(val, chained) {
|
||
// somewhat inspired by underscore.js
|
||
var wrapper = {
|
||
chain: function() {
|
||
wrapper.chained = true;
|
||
return wrapper;
|
||
},
|
||
chained: chained || false,
|
||
value: function() {
|
||
return val;
|
||
}
|
||
};
|
||
Object.getOwnPropertyNames(Ox).forEach(function(name) {
|
||
if (name[0] == name[0].toLowerCase() && Ox.isFunction(Ox[name])) {
|
||
wrapper[name] = function() {
|
||
var args = Array.prototype.slice.call(arguments), ret;
|
||
args.unshift(val);
|
||
ret = Ox[name].apply(Ox, args);
|
||
return wrapper.chained ? Ox.wrap(ret, true) : ret;
|
||
};
|
||
}
|
||
});
|
||
return wrapper;
|
||
};
|
||
|
||
//@ Array ----------------------------------------------------------------------
|
||
|
||
/*@
|
||
Ox.compact <f> Returns an array w/o <code>null</code> or <code>undefined</code>
|
||
> Ox.compact([null,,1,,2,,3])
|
||
[1, 2, 3]
|
||
@*/
|
||
|
||
Ox.compact = function(arr) {
|
||
return Ox.map(arr, function(val) {
|
||
return Ox.isUndefined(val) ? null : val;
|
||
});
|
||
};
|
||
|
||
/*@
|
||
Ox.flatten <f> Flattens an array
|
||
> Ox.flatten([1, [2, [3], 2], 1])
|
||
[1, 2, 3, 2, 1]
|
||
@*/
|
||
Ox.flatten = function(arr) {
|
||
// fixme: can this work for objects too?
|
||
var ret = [];
|
||
arr.forEach(function(val) {
|
||
if (Ox.isArray(val)) {
|
||
Ox.flatten(val).forEach(function(val) {
|
||
ret.push(val);
|
||
});
|
||
} else {
|
||
ret.push(val);
|
||
}
|
||
});
|
||
return ret;
|
||
};
|
||
|
||
/*@
|
||
Ox.merge <f> Merges an array with one or more other arrays
|
||
> Ox.merge([1], [2, 3, 2], [1])
|
||
[1, 2, 3, 2, 1]
|
||
> Ox.merge(1, [2, 3, 2], 1)
|
||
[1, 2, 3, 2, 1]
|
||
@*/
|
||
Ox.merge = function(arr) {
|
||
arr = Ox.isArray(arr) ? arr : [arr];
|
||
Ox.forEach(Array.prototype.slice.call(arguments, 1), function(arg) {
|
||
Ox.isArray(arg) ? Ox.forEach(arg, function(val) {
|
||
arr.push(val);
|
||
}) : arr.push(arg);
|
||
});
|
||
return arr;
|
||
};
|
||
|
||
/*@
|
||
Ox.sort <f> Sorts an array, handling leading digits and ignoring capitalization
|
||
arr <a> Array
|
||
fn <f|u> Optional map function that returns the value for the array element
|
||
> Ox.sort(['10', '9', 'B', 'a'])
|
||
['9', '10', 'a', 'B']
|
||
> Ox.sort([{id: 0, name: '80 Days'}, {id: 1, name: '8 Women'}], function(v) {return v.name});
|
||
[{id: 1, name: '8 Women'}, {id: 0, name: '80 Days'}]
|
||
@*/
|
||
Ox.sort = function(arr, fn) {
|
||
var len, matches = {}, sort = {},
|
||
values = fn ? arr.map(fn) : arr;
|
||
// find leading numbers
|
||
values.forEach(function(val, i) {
|
||
var match = /^\d+/.exec(val);
|
||
matches[val] = match ? match[0] : '';
|
||
});
|
||
// get length of longest leading number
|
||
len = Ox.max(Ox.map(matches, function(val) {
|
||
return val.length;
|
||
}));
|
||
// pad leading numbers, and make lower case
|
||
values.forEach(function(val) {
|
||
sort[val] = (
|
||
matches[val] ? Ox.pad(
|
||
matches[val], len
|
||
) + val.toString().substr(matches[val].length) : val
|
||
).toLowerCase();
|
||
});
|
||
return arr.sort(function(a, b) {
|
||
a = fn ? fn(a) : a;
|
||
b = fn ? fn(b) : b;
|
||
var ret = 0;
|
||
if (sort[a] < sort[b]) {
|
||
ret = -1;
|
||
} else if (sort[a] > sort[b]) {
|
||
ret = 1;
|
||
}
|
||
return ret;
|
||
});
|
||
};
|
||
|
||
/*@
|
||
Ox.unique <f> Returns an array without duplicate values
|
||
> Ox.unique([1, 2, 3, 2, 1])
|
||
[1, 2, 3]
|
||
> Ox.unique([NaN, NaN])
|
||
[]
|
||
@*/
|
||
|
||
Ox.unique = function(arr) {
|
||
return Ox.map(arr, function(val, i) {
|
||
return arr.indexOf(val) == i ? val : null;
|
||
});
|
||
};
|
||
|
||
/*@
|
||
Ox.zip <f> Zips an array of arrays
|
||
> 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]]
|
||
@*/
|
||
Ox.zip = function() {
|
||
var args = arguments.length == 1 ? arguments[0] : Ox.makeArray(arguments),
|
||
arr = [];
|
||
args[0].forEach(function(v, i) {
|
||
arr[i] = [];
|
||
args.forEach(function(v) {
|
||
arr[i].push(v[i]);
|
||
});
|
||
});
|
||
return arr;
|
||
};
|
||
|
||
//@ Collections ----------------------------------------------------------------
|
||
|
||
/*@
|
||
Ox.avg <f> Returns the average of an array's values, or an object's properties
|
||
(collection) -> <n> Average value
|
||
collection <[n]|o> Array or object with numerical values
|
||
> Ox.avg([-1, 0, 1])
|
||
0
|
||
> Ox.avg({a: 1, b: 2, c: 3})
|
||
2
|
||
> Ox.avg('avg is 0.1')
|
||
0.1
|
||
@*/
|
||
|
||
Ox.avg = function(obj) {
|
||
return Ox.sum(obj) / Ox.len(obj);
|
||
};
|
||
|
||
/*@
|
||
Ox.clone <f> Returns a (shallow or deep) copy of an object or array
|
||
> (function() { a = ['val']; b = Ox.clone(a); a[0] = null; return b[0]; }())
|
||
'val'
|
||
> (function() { a = {key: 'val'}; b = Ox.clone(a); a.key = null; return b.key; }())
|
||
'val'
|
||
@*/
|
||
|
||
Ox.clone = function(col, deep) {
|
||
var ret;
|
||
if (deep) {
|
||
ret = Ox.isArray(col) ? [] : {};
|
||
Ox.forEach(col, function(val, key) {
|
||
ret[key] = ['array', 'object'].indexOf(Ox.typeOf(val)) > -1
|
||
? Ox.clone(val) : val;
|
||
});
|
||
} else {
|
||
ret = Ox.isArray(col) ? col.slice() : Ox.extend({}, col);
|
||
}
|
||
return ret;
|
||
};
|
||
|
||
/*@
|
||
Ox.contains <f> Tests if a collection contains a value
|
||
> Ox.contains(['foo', 'bar'], 'foo')
|
||
true
|
||
> Ox.contains({foo: 'bar'}, 'bar')
|
||
true
|
||
> Ox.contains({foo: 'bar'}, 'foo')
|
||
false
|
||
> Ox.contains("foobar", "bar")
|
||
true
|
||
@*/
|
||
Ox.contains = function(col, val) {
|
||
/*
|
||
// fixme: rename to Ox.has or Ox.isIn?
|
||
// then it'd become convenient for arrays
|
||
*/
|
||
return (Ox.isObject(col) ? Ox.values(col) : col).indexOf(val) > -1;
|
||
};
|
||
|
||
/*@
|
||
Ox.count <f> Counts the occurences of values in a collection
|
||
> Ox.count(['f', 'o', 'o'])
|
||
{f: 1, o: 2}
|
||
> Ox.count({a: 'f', b: 'o', c: 'o'})
|
||
{f: 1, o: 2}
|
||
> Ox.count('foo')
|
||
{f: 1, o: 2}
|
||
@*/
|
||
Ox.count = function(arr) {
|
||
var obj = {};
|
||
Ox.forEach(arr, function(v) {
|
||
obj[v] = (obj[v] || 0) + 1;
|
||
});
|
||
return obj;
|
||
};
|
||
|
||
/*@
|
||
Ox.every <f> Tests if every element of a collection satisfies a given condition
|
||
Unlike <code>[].every()</code>, <code>Ox.every()</code> works for arrays,
|
||
objects and strings.
|
||
> 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
|
||
@*/
|
||
Ox.every = function(col, fn) {
|
||
return Ox.filter(Ox.values(col), fn || function(v) {
|
||
return v;
|
||
}).length == Ox.len(col);
|
||
};
|
||
|
||
/*@
|
||
Ox.filter <f> Filters a collection by a given condition
|
||
Unlike <code>[].filter()</code>, <code>Ox.filter()</code> works for arrays,
|
||
objects and strings.
|
||
> Ox.filter([2, 1, 0], function(v, i) { return v == i; })
|
||
[1]
|
||
> Ox.keys(Ox.filter({a: 'c', b: 'b', c: 'a'}, function(v, k) { return v == k; }))
|
||
['b']
|
||
> Ox.filter(' foo bar ', function(v) { return v != ' '; })
|
||
'foobar'
|
||
@*/
|
||
|
||
Ox.filter = function(col, fn) {
|
||
var type = Ox.typeOf(col),
|
||
ret = type == 'array' ? [] : type == 'object' ? {} : '';
|
||
Ox.forEach(col, function(v, k) {
|
||
if (fn(v, k)) {
|
||
if (type == 'array') {
|
||
ret.push(v);
|
||
} else if (type == 'object') {
|
||
ret[k] = v;
|
||
} else {
|
||
ret += v;
|
||
}
|
||
}
|
||
});
|
||
return ret;
|
||
};
|
||
|
||
/*@
|
||
Ox.find <f> Returns array elements that match a string
|
||
Returns an array of two arrays, the first containing leading matches
|
||
(exact match first), the second containing non-leading matches
|
||
> Ox.find(['foo', 'bar', 'foobar', 'barfoo'], 'foo')
|
||
[['foo', 'foobar'], ['barfoo']]
|
||
@*/
|
||
// fixme: wouldn't it make more sense to return just one array?
|
||
Ox.find = function(arr, str) {
|
||
var ret = [[], []];
|
||
str = str.toLowerCase();
|
||
arr.map(function(v) {
|
||
return v.toLowerCase();
|
||
}).forEach(function(v, i) {
|
||
var index = v.indexOf(str);
|
||
index > -1 && ret[index == 0 ? 0 : 1][v == str ? 'unshift' : 'push'](arr[i]);
|
||
});
|
||
return ret;
|
||
};
|
||
|
||
/*@
|
||
Ox.forEach <f> forEach loop
|
||
<code>Ox.forEach()</code> loops over arrays, objects and strings.
|
||
Returning <code>false</code> from the iterator function acts like a
|
||
<code>break</code> statement (unlike <code>[].forEach()</code>, like
|
||
<code>$.each()</code>). The arguments of the iterator function are
|
||
<code>(value, key)</code> (like <code>[].forEach()</code>, unlike
|
||
<code>$.each()</code>).
|
||
(collection, callback) <a|o|s> The collection
|
||
(collection, callback, includePrototype) <a|o|s> The collection
|
||
collection <a|o|s> An array, object or string
|
||
callback <f> Callback function
|
||
value <*> Value
|
||
key <n|s> Key
|
||
includePrototype <b|false> If true, include prototype properties
|
||
<script>
|
||
Ox.test.string = "";
|
||
Ox.forEach(["f", "o", "o"], function(v, i) { Ox.test.string += i; });
|
||
Ox.forEach({a: "f", b: "o", c: "o"}, function(v, k) { Ox.test.string += k; });
|
||
Ox.forEach("foo", function(v) { Ox.test.string += v; });
|
||
</script>
|
||
> Ox.test.string
|
||
"012abcfoo"
|
||
@*/
|
||
Ox.forEach = function(col, fn, includePrototype) {
|
||
var isObject = Ox.isObject(col), key;
|
||
// Safari will not loop through an arguments array
|
||
col = Ox.isArguments(col) ? Ox.makeArray(col) : col;
|
||
for (key in col) {
|
||
key = isObject ? key : parseInt(key);
|
||
// fixme: fn.call(context, obj[key], key, obj) may be more standard...
|
||
if ((
|
||
includePrototype || Object.hasOwnProperty.call(col, key)
|
||
) && fn(col[key], key) === false) {
|
||
break;
|
||
}
|
||
}
|
||
return col;
|
||
};
|
||
|
||
/*@
|
||
Ox.getObjectById <f> Returns an array element with a given id
|
||
> Ox.getObjectById([{id: "foo", title: "Foo"}, {id: "bar", title: "Bar"}], "foo")
|
||
{id: "foo", title: "Foo"}
|
||
@*/
|
||
// fixme: shouldn't this be getElementById() ?
|
||
Ox.getObjectById = function(arr, id) {
|
||
var ret = null;
|
||
Ox.forEach(arr, function(v) {
|
||
if (v.id == id) {
|
||
ret = v;
|
||
return false;
|
||
}
|
||
});
|
||
return ret;
|
||
};
|
||
|
||
/*@
|
||
Ox.getPositionById <f> Returns the index of an array element with a given id
|
||
> Ox.getPositionById([{id: "foo", title: "Foo"}, {id: "bar", title: "Bar"}], "foo")
|
||
0
|
||
@*/
|
||
// fixme: shouldn't this be getIndexById() ?
|
||
Ox.getPositionById = function(arr, id) {
|
||
var ret = -1;
|
||
Ox.forEach(arr, function(v, i) {
|
||
if (v.id == id) {
|
||
ret = i;
|
||
return false;
|
||
}
|
||
});
|
||
return ret;
|
||
};
|
||
|
||
// fixme: and what about getElementBy() and getIndexBy() ?
|
||
|
||
/*@
|
||
Ox.getset <f> Generic getter and setter function
|
||
See examples for details.
|
||
# Usage --------------------------------------------------------------------
|
||
Ox.getset(options, args=[]) -> <o> all options
|
||
Ox.getset(options, args=[key]) -> <*> options[key]
|
||
Ox.getset(options, args=[key, value], callback, context) -> <f|o> context
|
||
sets options[key] to value and calls fn(key, value)
|
||
if the key/value pair was added or modified
|
||
Ox.getset(options, args=[{key: value}], callback, context) -> <f|o> context
|
||
sets multiple options and calls fn(key, value)
|
||
for every key/value pair that was added or modified
|
||
# Arguments ----------------------------------------------------------------
|
||
options <obj> Options object (key/value pairs)
|
||
args <arr> The arguments "array" of the caller function
|
||
callback <fun> Callback function
|
||
The callback is called for every key/value pair that was added or
|
||
modified.
|
||
key <s> Key
|
||
value <*> Value
|
||
context <obj> The parent object of the caller function (for chaining)
|
||
# Examples -----------------------------------------------------------------
|
||
<script>
|
||
Ox.test.object = new function() {
|
||
var options = {},
|
||
setOption = function(key, value) {},
|
||
that = this;
|
||
that.options = function() {
|
||
return Ox.getset(options, arguments, setOption, that);
|
||
};
|
||
return that;
|
||
};
|
||
</script>
|
||
> Ox.test.object.options("key", "val").options("key")
|
||
"val"
|
||
> Ox.test.object.options({foo: "foo", bar: "bar"}).options()
|
||
{"key": "val", "foo": "foo", "bar": "bar"}
|
||
@*/
|
||
|
||
Ox.getset = function(obj, args, callback, context) {
|
||
var obj_ = Ox.clone(obj), ret;
|
||
if (args.length == 0) {
|
||
// getset([])
|
||
ret = obj;
|
||
} else if (args.length == 1 && !Ox.isObject(args[0])) {
|
||
// getset([key])
|
||
ret = obj[args[0]];
|
||
} else {
|
||
// getset([key, val]) or getset([{key: val, ...}])
|
||
args = Ox.makeObject(args);
|
||
obj = Ox.extend(obj, args);
|
||
Ox.forEach(args, function(val, key) {
|
||
if (!obj_ || !Ox.isEqual(obj_[key], val)) {
|
||
callback && callback(key, val);
|
||
}
|
||
});
|
||
ret = context;
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
/*@
|
||
Ox.isEmpty <f> Returns true if a collection is empty
|
||
> Ox.isEmpty([])
|
||
true
|
||
> Ox.isEmpty({})
|
||
true
|
||
> Ox.isEmpty('')
|
||
true
|
||
> Ox.isEmpty(function() {})
|
||
true
|
||
@*/
|
||
Ox.isEmpty = function(val) {
|
||
return Ox.len(val) == 0;
|
||
};
|
||
|
||
/*@
|
||
Ox.keys <f> Returns the keys of a collection
|
||
Unlike <code>Object.keys()</code>, <code>Ox.keys()</code> works for arrays,
|
||
objects and strings.
|
||
> Ox.keys([1, 2, 3])
|
||
[0, 1, 2]
|
||
> Ox.keys([1,,3])
|
||
[0, 2]
|
||
# fixme?
|
||
# > Ox.keys([,])
|
||
# [0]
|
||
> Ox.keys({a: 1, b: 2, c: 3})
|
||
['a', 'b', 'c']
|
||
> Ox.keys('abc')
|
||
[0, 1, 2]
|
||
@*/
|
||
|
||
// fixme: is this really needed? arrays... ok... but strings??
|
||
Ox.keys = function(obj) {
|
||
var keys = [];
|
||
Ox.forEach(obj, function(v, k) {
|
||
keys.push(k);
|
||
});
|
||
return keys.sort();
|
||
};
|
||
|
||
/*@
|
||
Ox.last <f> Gets or sets the last element of an array
|
||
Unlike <code>foobarbaz[foobarbaz.length - 1]</code>,
|
||
<code>Ox.last(foobarbaz)</code> is short.
|
||
<script>
|
||
Ox.test.array = [1, 2, 3];
|
||
</script>
|
||
> Ox.last(Ox.test.array)
|
||
3
|
||
> Ox.last(Ox.test.array, 4)
|
||
[1, 2, 4]
|
||
> Ox.test.array
|
||
[1, 2, 4]
|
||
@*/
|
||
Ox.last = function(arr, val) {
|
||
var ret;
|
||
if (arguments.length == 1) {
|
||
ret = arr[arr.length - 1];
|
||
} else {
|
||
arr[arr.length - 1] = val;
|
||
ret = arr;
|
||
}
|
||
return ret;
|
||
};
|
||
|
||
/*@
|
||
Ox.len <f> Returns the length of an array, function, object or string
|
||
Not to be confused with <code>Ox.length</code>, which is the
|
||
<code>length</code> property of the <code>Ox</code> function
|
||
(<code>1</code>).
|
||
> Ox.len([1, 2, 3])
|
||
3
|
||
> Ox.len([,])
|
||
1
|
||
> Ox.len({a: 1, b: 2, c: 3})
|
||
3
|
||
> Ox.len(function(a, b, c) {})
|
||
3
|
||
> Ox.len('abc')
|
||
3
|
||
@*/
|
||
Ox.len = function(obj) {
|
||
return (Ox.isObject(obj) ? Ox.values(obj) : obj).length;
|
||
};
|
||
|
||
/*@
|
||
Ox.loop <f> For-loop, functional-style
|
||
Returning <code>false</code> from the iterator function acts like a
|
||
<code>break</code> statement. Unlike a <code>for</code> loop,
|
||
<code>Ox.loop</code> doesn't leak its counter variable to the outer scope,
|
||
but returns it.
|
||
(stop, callback) -> <n> Next value
|
||
equivalent to <code>for (var i = 0; i < stop; i++)</code>
|
||
(start, stop, callback) -> <n> Next value
|
||
equivalent to <code>for (var i = start; i < stop; i++)</code> or,
|
||
if <code>start</code> is larger than <code>stop</code>,
|
||
<code>for (var i = start; i > stop; i--)</code>
|
||
(start, stop, step, callback) -> <n> Next value
|
||
equivalent to <code>for (var i = start; i < stop; i += step)</code> or,
|
||
if <code>step</code> is negative,
|
||
<code>for (var i = start; i > stop; i += step)</code>
|
||
start <n> Start value
|
||
stop <n> Stop value (exclusive)
|
||
step <n> Step value
|
||
callback <f> Iterator function
|
||
i <n> Counter value
|
||
> Ox.loop(0, 3, 2, function() {})
|
||
4
|
||
> Ox.loop(10, function(i) { return i != 4; })
|
||
4
|
||
@*/
|
||
Ox.loop = function() {
|
||
var len = arguments.length,
|
||
start = len > 2 ? arguments[0] : 0,
|
||
stop = arguments[len > 2 ? 1 : 0],
|
||
step = len == 4 ? arguments[2] : (start <= stop ? 1 : -1),
|
||
callback = arguments[len - 1],
|
||
i;
|
||
for (i = start; step > 0 ? i < stop : i > stop; i += step) {
|
||
if (callback(i) === false) {
|
||
break;
|
||
};
|
||
}
|
||
return i;
|
||
};
|
||
|
||
/*@
|
||
Ox.makeArray <f> Takes an array-like object and returns a true array
|
||
Alias for <code>Array.prototype.slice.call</code>
|
||
(value) -> <a> True array
|
||
value <*> Array-like object
|
||
> (function() { return Ox.makeArray(arguments); }("foo", "bar"))
|
||
["foo", "bar"]
|
||
> Ox.makeArray("foo")
|
||
["f", "o", "o"]
|
||
> Ox.makeArray({0: "f", 1: "o", 2: "o", length: 3})
|
||
["f", "o", "o"]
|
||
@*/
|
||
|
||
Ox.makeArray = /MSIE/.test(navigator.userAgent) ? function(col) {
|
||
var i, len, ret = [];
|
||
try {
|
||
ret = Array.prototype.slice.call(col);
|
||
} catch(e) {
|
||
// handle MSIE NodeLists
|
||
len = col.length;
|
||
for (i = 0; i < len; i++) {
|
||
ret[i] = col[i];
|
||
}
|
||
}
|
||
return ret;
|
||
} : function(col) {
|
||
return Array.prototype.slice.call(col);
|
||
}
|
||
|
||
/*@
|
||
Ox.makeObject <f> Takes an array and returns an object
|
||
<code>Ox.makeObject</code> is a helper for functions with two alternative
|
||
signatures like <code>('key', 'val')</code> and <code>({key: 'val'})</code>.
|
||
> (function() { return Ox.makeObject(arguments); }({foo: 1, bar: 2}))
|
||
{foo: 1, bar: 2}
|
||
> (function() { return Ox.makeObject(arguments); }('foo', 1))
|
||
{foo: 1}
|
||
> (function() { return Ox.makeObject(arguments); }('foo'))
|
||
{}
|
||
> (function() { return Ox.makeObject(arguments); }())
|
||
{}
|
||
@*/
|
||
Ox.makeObject = function(obj) {
|
||
var ret = {};
|
||
if (Ox.isObject(obj[0])) {
|
||
// ({foo: 'bar'})
|
||
ret = obj[0];
|
||
} else if (obj.length) {
|
||
// ('foo', 'bar')
|
||
ret[obj[0]] = obj[1]
|
||
}
|
||
return ret;
|
||
};
|
||
|
||
/*@
|
||
Ox.map <f> Transforms the values of an array, object or string
|
||
Unlike <code>[].map()</code>, <code>Ox.map()</code> works for arrays,
|
||
objects and strings. Returning <code>null</code> from the iterator
|
||
function will remove the element from the collection.
|
||
> Ox.map([0, 0, 0], function(v, i) { return v == i; })
|
||
[true, false, false]
|
||
> Ox.map({a: 'a', b: 'a', c: 'a'}, function(v, k) { return v == k; })
|
||
{a: true, b: false, c: false}
|
||
> Ox.map("000", function(v, i) { return v == i; })
|
||
[true, false, false]
|
||
> Ox.map([0, 1, 2, 4], function(v, i) { return v ? i == v : null; })
|
||
[true, true, false]
|
||
# fixme?
|
||
# > Ox.map([,], function(v, i) { return i; })
|
||
# [0]
|
||
@*/
|
||
|
||
Ox.map = function(obj, fn) {
|
||
// fixme: return null to filter out is a bit esoteric
|
||
var isObject = Ox.isObject(obj),
|
||
ret = isObject ? {} : [];
|
||
Ox.forEach(obj, function(val, key) {
|
||
var map;
|
||
if ((map = fn(val, key)) !== null) {
|
||
ret[isObject ? key : ret.length] = map;
|
||
}
|
||
});
|
||
return ret;
|
||
};
|
||
|
||
/*@
|
||
Ox.max <f> Returns the maximum value of a collection
|
||
> Ox.max([1, 2, 3])
|
||
3
|
||
> Ox.max({a: 1, b: 2, c: 3})
|
||
3
|
||
> Ox.max('123')
|
||
3
|
||
@*/
|
||
Ox.max = function(col) {
|
||
return Math.max.apply(Math, Ox.values(col));
|
||
};
|
||
|
||
/*@
|
||
Ox.min <f> Returns the minimum value of a collection
|
||
> Ox.min([1, 2, 3])
|
||
1
|
||
> Ox.min({a: 1, b: 2, c: 3})
|
||
1
|
||
> Ox.min('123')
|
||
1
|
||
@*/
|
||
Ox.min = function(col) {
|
||
return Math.min.apply(Math, Ox.values(col));
|
||
};
|
||
|
||
/*@
|
||
Ox.range <f> Python-style range
|
||
(stop) -> <[n]> range
|
||
Returns an array of integers from <code>0</code> (inclusive) to
|
||
<code>stop</code> (exclusive).
|
||
(start, stop) -> <[n]> range
|
||
Returns an array of integers from <code>start</code> (inclusive) to
|
||
<code>stop</code> (exclusive).
|
||
(start, stop, step) -> <[n]> range
|
||
Returns an array of numbers from <code>start</code> (inclusive) to
|
||
<code>stop</code> (exclusive), incrementing by <code>step</step>.
|
||
start <n> Start value
|
||
stop <n> Stop value
|
||
step <n> Step value
|
||
> Ox.range(3)
|
||
[0, 1, 2]
|
||
> Ox.range(1, 4)
|
||
[1, 2, 3]
|
||
> Ox.range(3, 0)
|
||
[3, 2, 1]
|
||
> Ox.range(1, 2, 0.5)
|
||
[1, 1.5]
|
||
> Ox.range(-1, -2, -0.5)
|
||
[-1, -1.5]
|
||
@*/
|
||
Ox.range = function() {
|
||
var args = Ox.makeArray(arguments),
|
||
arr = [];
|
||
args.push(function(i) {
|
||
arr.push(i);
|
||
});
|
||
Ox.loop.apply(null, args);
|
||
return arr;
|
||
};
|
||
|
||
/*@
|
||
Ox.setPropertyOnce <f> Sets a property once
|
||
Given a array of objects, each of which has a property with a boolean
|
||
value, this sets exactly one of these to true, and returns the index
|
||
of the object whose property is true.
|
||
> Ox.setPropertyOnce([{selected: false}, {selected: false}], 'selected')
|
||
0
|
||
> Ox.setPropertyOnce([{selected: false}, {selected: true}], 'selected')
|
||
1
|
||
> Ox.setPropertyOnce([{selected: true}, {selected: true}], 'selected')
|
||
0
|
||
@*/
|
||
// fixme: strange name, and shouldn't it return the full array?
|
||
Ox.setPropertyOnce = function(arr, str) {
|
||
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 <f> Randomizes the order of values within a collection
|
||
> Ox.shuffle([1, 2, 3]).length
|
||
3
|
||
> Ox.len(Ox.shuffle({a: 1, b: 2, c: 3}))
|
||
3
|
||
> Ox.shuffle('123').length
|
||
3
|
||
@*/
|
||
|
||
Ox.shuffle = function(col) {
|
||
var keys, ret, type = Ox.typeOf(col), values;
|
||
function sort() {
|
||
return Math.random() - 0.5;
|
||
}
|
||
if (type == 'array') {
|
||
ret = col.sort(sort);
|
||
} else if (type == 'object') {
|
||
keys = Object.keys(col);
|
||
values = Ox.values(col).sort(sort);
|
||
ret = {};
|
||
keys.forEach(function(key, i) {
|
||
ret[key] = values[i]
|
||
});
|
||
} else if (type == 'string') {
|
||
ret = col.split('').sort(sort).join('');
|
||
}
|
||
return ret;
|
||
};
|
||
|
||
/*@
|
||
Ox.some <f> Tests if one or more elements of a collection satisfy a given condition
|
||
Unlike <code>[].some()</code>, <code>Ox.some()</code> works for arrays,
|
||
objects and strings.
|
||
> 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
|
||
@*/
|
||
Ox.some = function(obj, fn) {
|
||
return Ox.filter(Ox.values(obj), fn).length > 0;
|
||
};
|
||
|
||
/*@
|
||
Ox.sub <f> Returns a substring or sub-array
|
||
Ox.sub behaves like collection[start:stop] in Python
|
||
(or, for strings, like str.substring() with negative values for stop)
|
||
> Ox.sub([1, 2, 3], 1, -1)
|
||
[2]
|
||
> Ox.sub('foobar', 1)
|
||
"oobar"
|
||
> Ox.sub('foobar', -1)
|
||
"r"
|
||
> Ox.sub('foobar', 1, 5)
|
||
"ooba"
|
||
> Ox.sub('foobar', 1, -1)
|
||
"ooba"
|
||
> Ox.sub('foobar', -5, 5)
|
||
"ooba"
|
||
> Ox.sub('foobar', -5, -1)
|
||
"ooba"
|
||
@*/
|
||
Ox.sub = function(col, start, stop) {
|
||
stop = Ox.isUndefined(stop) ? col.length : stop;
|
||
start = start < 0 ? col.length + start : start;
|
||
stop = stop < 0 ? col.length + stop : stop;
|
||
return Ox.isArray(col) ? Ox.filter(col, function(val, key) {
|
||
return key >= start && key < stop;
|
||
}) : col.substring(start, stop);
|
||
}
|
||
|
||
/*@
|
||
Ox.sum <f> Returns the sum of the values of a collection
|
||
> Ox.sum(1, 2, 3)
|
||
6
|
||
> Ox.sum([1, 2, 3])
|
||
6
|
||
> Ox.sum({a: 1, b: 2, c: 3})
|
||
6
|
||
> Ox.sum('123')
|
||
6
|
||
> Ox.sum('123foo')
|
||
6
|
||
> Ox.sum('08', -2, 'foo')
|
||
6
|
||
@*/
|
||
Ox.sum = function(col) {
|
||
var sum = 0;
|
||
col = arguments.length > 1 ? Ox.makeArray(arguments) : col;
|
||
Ox.forEach(col, function(val) {
|
||
val = +val;
|
||
sum += Ox.isNumber(val) ? val : 0;
|
||
});
|
||
return sum;
|
||
};
|
||
|
||
Ox.toArray = function(obj) {
|
||
// fixme: can this be thrown out?
|
||
/*
|
||
>>> Ox.toArray('foo')
|
||
['foo']
|
||
>>> Ox.toArray(['foo'])
|
||
['foo']
|
||
*/
|
||
var arr;
|
||
if (Ox.isArray(obj)) {
|
||
arr = obj;
|
||
} else if (Ox.isArguments(obj)) {
|
||
arr = Ox.makeArray(obj);
|
||
} else {
|
||
arr = [obj];
|
||
}
|
||
return arr;
|
||
};
|
||
|
||
/*@
|
||
Ox.values <f> Returns the values of a collection
|
||
> Ox.values([1, 2, 3])
|
||
[1, 2, 3]
|
||
> Ox.values({a: 1, b: 2, c: 3})
|
||
[1, 2, 3]
|
||
> Ox.values('abc')
|
||
['a', 'b', 'c']
|
||
> Ox.values([1,,3])
|
||
[1, 3]
|
||
@*/
|
||
Ox.values = function(col) {
|
||
// Ox.values(str) is identical to str.split('')
|
||
var values = [];
|
||
Ox.forEach(col, function(val) {
|
||
values.push(val);
|
||
});
|
||
return values;
|
||
};
|
||
|
||
/*@
|
||
Ox.walk <f> Recursively walk a tree-like key/value store
|
||
<script>
|
||
Ox.test.number = 0;
|
||
Ox.walk({a: 1, b: {c: 2, d: 3}}, function (v) {
|
||
Ox.test.number += Ox.isNumber(v) ? v : 0;
|
||
});
|
||
</script>
|
||
> Ox.test.number
|
||
6
|
||
@*/
|
||
Ox.walk = function(obj, fn) {
|
||
Ox.forEach(obj, function(val, key) {
|
||
fn(val, key, obj);
|
||
Ox.walk(obj[key], fn);
|
||
});
|
||
};
|
||
|
||
//@ Color ----------------------------------------------------------------------
|
||
|
||
/*@
|
||
Ox.hsl <f> Takes RGB values and returns HSL values
|
||
(rgb) <[n]> HSL values
|
||
(r, g, b) <[n]> HSL values
|
||
rgb <[n]> RGB values
|
||
r <n> red
|
||
g <n> green
|
||
b <n> blue
|
||
> 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]
|
||
@*/
|
||
Ox.hsl = function(rgb) {
|
||
if (arguments.length == 3) {
|
||
rgb = Ox.makeArray(arguments);
|
||
}
|
||
rgb = rgb.map(function(val) {
|
||
return val / 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 <f> Takes HSL values and returns RGB values
|
||
(hsl) <[n]> RGB values
|
||
(h, s, l) <[n]> RGB values
|
||
hsl <[n]> HSL values
|
||
h <n> hue
|
||
s <n> saturation
|
||
l <n> lightness
|
||
> 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]
|
||
@*/
|
||
|
||
Ox.rgb = function(hsl) {
|
||
if (arguments.length == 3) {
|
||
hsl = Ox.makeArray(arguments);
|
||
}
|
||
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;
|
||
});
|
||
};
|
||
|
||
//@ Constants ------------------------------------------------------------------
|
||
|
||
//@ Ox.AMPM <[str]> ['AM', 'PM']
|
||
Ox.AMPM = ['AM', 'PM'];
|
||
//@ Ox.BCAD <[str]> ['BC', 'AD']
|
||
Ox.BCAD = ['BC', 'AD'];
|
||
// fixme: this is unused, and probably unneeded
|
||
//@ Ox.DURATIONS <[str]> ['year', 'month', 'day', 'hour', 'minute', 'second']
|
||
Ox.DURATIONS = ['year', 'month', 'day', 'hour', 'minute', 'second'];
|
||
//@ Ox.EARTH_RADIUS <number> Radius of the earth in meters
|
||
// see http://en.wikipedia.org/wiki/WGS-84
|
||
Ox.EARTH_RADIUS = 6378137;
|
||
//@ Ox.EARTH_CIRCUMFERENCE <num> Circumference of the earth in meters
|
||
Ox.EARTH_CIRCUMFERENCE = Ox.EARTH_RADIUS * 2 * Math.PI;
|
||
//@ Ox.HTML_ENTITIES <object> HTML entities for ... (FIXME)
|
||
Ox.HTML_ENTITIES = {
|
||
'"': '"', '&': '&', "'": ''', '<': '<', '>': '>'
|
||
};
|
||
//@ Ox.KEYS <object> Names for key codes
|
||
// The dot notation ('0.numpad') allows for namespaced events ('key_0.numpad'),
|
||
// so that binding to 'key_0' will catch both 'key_0' and 'key_0.numpad'.
|
||
Ox.KEYS = {
|
||
0: 'section', 8: 'backspace', 9: 'tab', 12: 'clear', 13: 'enter',
|
||
16: 'shift', 17: 'control', 18: 'alt', 20: 'capslock', 27: 'escape',
|
||
32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home',
|
||
37: 'left', 38: 'up', 39: 'right', 40: 'down',
|
||
45: 'insert', 46: 'delete', 47: 'help',
|
||
48: '0', 49: '1', 50: '2', 51: '3', 52: '4',
|
||
53: '5', 54: '6', 55: '7', 56: '8', 57: '9',
|
||
65: 'a', 66: 'b', 67: 'c', 68: 'd', 69: 'e',
|
||
70: 'f', 71: 'g', 72: 'h', 73: 'i', 74: 'j',
|
||
75: 'k', 76: 'l', 77: 'm', 78: 'n', 79: 'o',
|
||
80: 'p', 81: 'q', 82: 'r', 83: 's', 84: 't',
|
||
85: 'u', 86: 'v', 87: 'w', 88: 'x', 89: 'y', 90: 'z',
|
||
// fixme: this is usually 91: window.left, 92: window.right, 93: select
|
||
91: 'meta.left', 92: 'meta.right', 93: 'meta.right',
|
||
96: '0.numpad', 97: '1.numpad', 98: '2.numpad',
|
||
99: '3.numpad', 100: '4.numpad', 101: '5.numpad',
|
||
102: '6.numpad', 103: '7.numpad', 104: '8.numpad', 105: '9.numpad',
|
||
106: 'asterisk.numpad', 107: 'plus.numpad', 109: 'minus.numpad',
|
||
108: 'enter.numpad', 110: 'dot.numpad', 111: 'slash.numpad',
|
||
112: 'f1', 113: 'f2', 114: 'f3', 115: 'f4', 116: 'f5',
|
||
117: 'f6', 118: 'f7', 119: 'f8', 120: 'f9', 121: 'f10',
|
||
122: 'f11', 123: 'f12', 124: 'f13', 125: 'f14', 126: 'f15', 127: 'f16',
|
||
144: 'numlock', 145: 'scrolllock',
|
||
186: 'semicolon', 187: 'equal', 188: 'comma', 189: 'minus',
|
||
190: 'dot', 191: 'slash', 192: 'backtick', 219: 'openbracket',
|
||
220: 'backslash', 221: 'closebracket', 222: 'quote', 224: 'meta'
|
||
// see dojo, for ex.
|
||
},
|
||
Ox.MAP_TILE_SIZE = 256; // fixme: definitely not needed here
|
||
//@ Ox.MODIFIER_KEYS <obj> Names for modifier keys
|
||
// meta comes last so that one can differentiate between
|
||
// alt_control_shift_meta.left and alt_control_shift_meta.right
|
||
Ox.MODIFIER_KEYS = {
|
||
altKey: 'alt', // Mac: option
|
||
ctrlKey: 'control',
|
||
shiftKey: 'shift',
|
||
metaKey: 'meta', // Mac: command
|
||
}
|
||
//@ Ox.MONTHS <[str]> Names of months
|
||
Ox.MONTHS = [
|
||
'January', 'February', 'March', 'April', 'May', 'June',
|
||
'July', 'August', 'September', 'October', 'November', 'December'
|
||
];
|
||
//@ Ox.SHORT_MONTHS <[str]> Short names of months
|
||
Ox.SHORT_MONTHS = Ox.MONTHS.map(function(val) {
|
||
return val.substr(0, 3);
|
||
});
|
||
//@ Ox.PATH <str> Path of Ox.js
|
||
Ox.PATH = Ox.makeArray(
|
||
document.getElementsByTagName('script')
|
||
).filter(function(element) {
|
||
return /Ox\.js$/.test(element.src);
|
||
})[0].src.replace('Ox.js', '');
|
||
//@ Ox.PREFIXES <[str]> <code>['K', 'M', 'G', 'T', 'P']</code>
|
||
Ox.PREFIXES = ['K', 'M', 'G', 'T', 'P'];
|
||
//@ Ox.SEASONS <[str]> Names of the seasons of the year
|
||
Ox.SEASONS = ['Winter', 'Spring', 'Summer', 'Fall'];
|
||
//@ Ox.SYMBOLS <obj> Unicode characters for symbols
|
||
Ox.SYMBOLS = {
|
||
DOLLAR: '\u0024',
|
||
CENT: '\u00A2', POUND: '\u00A3', CURRENCY: '\u00A4', YEN: '\u00A5',
|
||
BULLET: '\u2022', ELLIPSIS: '\u2026', PERMILLE: '\u2030',
|
||
COLON: '\u20A1', CRUZEIRO: '\u20A2', FRANC: '\u20A3', LIRA: '\u20A4',
|
||
NAIRA: '\u20A6', PESETA: '\u20A7', WON: '\u20A9', SHEQEL: '\u20AA',
|
||
DONG: '\u20AB', EURO: '\u20AC', KIP: '\u20AD', TUGRIK: '\u20AE',
|
||
DRACHMA: '\u20AF', PESO: '\u20B1', GUARANI: '\u20B2', AUSTRAL: '\u20B3',
|
||
HRYVNIA: '\u20B4', CEDI: '\u20B5', TENGE: '\u20B8', RUPEE: '\u20B9',
|
||
CELSIUS: '\u2103', FAHRENHEIT: '\u2109', POUNDS: '\u2114', OUNCE: '\u2125',
|
||
OHM: '\u2126', KELVIN: '\u212A', ANGSTROM: '\u212B', INFO: '\u2139',
|
||
LEFT: '\u2190', UP: '\u2191', RIGHT: '\u2192', DOWN: '\u2193',
|
||
HOME: '\u2196', END: '\u2198', RETURN: '\u21A9',
|
||
REDO: '\u21BA', UNDO: '\u21BB', PAGEUP: '\u21DE', PAGEDOWN: '\u21DF',
|
||
CAPSLOCK: '\u21EA', TAB: '\u21E5', SHIFT: '\u21E7', INFINITY: '\u221E',
|
||
CONTROL: '\u2303', COMMAND: '\u2318', ENTER: '\u2324', ALT: '\u2325',
|
||
DELETE: '\u2326', CLEAR:'\u2327',BACKSPACE: '\u232B', OPTION: '\u2387',
|
||
NAVIGATE: '\u2388', ESCAPE: '\u238B', EJECT: '\u23CF',
|
||
SPACE: '\u2423', DIAMOND: '\u25C6',
|
||
STAR: '\u2605', SOUND: '\u266B', TRASH: '\u267A', FLAG: '\u2691',
|
||
ANCHOR: '\u2693', GEAR: '\u2699', ATOM: '\u269B', WARNING: '\u26A0',
|
||
CUT: '\u2702', BACKUP: '\u2707', FLY: '\u2708', CHECK: '\u2713',
|
||
CLOSE: '\u2715', BALLOT: '\u2717', WINDOWS: '\u2756',
|
||
EDIT: '\uF802', CLICK: '\uF803', APPLE: '\uF8FF'
|
||
};
|
||
//@ Ox.TYPES <[str]> list of types, as returned by <code>Ox.type()</code>
|
||
Ox.TYPES = [
|
||
'Arguments', 'Array', 'Boolean', 'Date', 'Element', 'Function', 'Infinity',
|
||
'NaN', 'Null', 'Number', 'Object', 'RegExp', 'String', 'Undefined'
|
||
];
|
||
//@ Ox.VERSION <str> OxJS version number
|
||
Ox.VERSION = '0.1.2';
|
||
//@ Ox.WEEKDAYS <[str]> Names of weekdays
|
||
Ox.WEEKDAYS = [
|
||
'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'
|
||
];
|
||
//@ Ox.SHORT_WEEKDAYS <[str]> Short names of weekdays
|
||
Ox.SHORT_WEEKDAYS = Ox.WEEKDAYS.map(function(val) {
|
||
return val.substr(0, 3);
|
||
});
|
||
|
||
//@ Date -----------------------------------------------------------------------
|
||
|
||
//@ Ox.getDate <f> Get the day of a date, optionally UTC
|
||
// see Ox.setSeconds for source code
|
||
|
||
/*@
|
||
Ox.getDateInWeek <f> Get the date that falls on a given weekday in the same week
|
||
# Usage
|
||
(date, weekday) -> <dat> Date
|
||
(date, weekday, utc) -> <dat> Date
|
||
# Arguments
|
||
date <d> Date
|
||
weekday <n|s> 1-7 (Monday-Sunday) or name, full ("Monday") or short ("Sun")
|
||
utc <b> If true, all dates are UTC
|
||
# Examples
|
||
> 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"
|
||
@*/
|
||
// fixme: why is this Monday first? shouldn't it then be "getDateInISOWeek"??
|
||
Ox.getDateInWeek = function(date, weekday, utc) {
|
||
date = Ox.makeDate(date);
|
||
Ox.print(date, Ox.getDate(date, utc), Ox.formatDate(date, '%u', utc), date)
|
||
var sourceWeekday = Ox.getISODay(date, utc),
|
||
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];
|
||
return Ox.setDate(date, Ox.getDate(date, utc) - sourceWeekday + targetWeekday, utc);
|
||
}
|
||
|
||
//@ Ox.getDay <f> Get the weekday of a date, optionally UTC
|
||
// see Ox.setSeconds for source code
|
||
|
||
/*@
|
||
Ox.getDayOfTheYear <f> Get the day of the year for a given date
|
||
# Usage
|
||
(date) -> <d> Date
|
||
(date, utc) -> <d> Date
|
||
# Arguments
|
||
date <d> Date
|
||
utc <b> If true, all dates are UTC
|
||
# Examples
|
||
> Ox.getDayOfTheYear(new Date("12/31/2000"))
|
||
366
|
||
> Ox.getDayOfTheYear(new Date("12/31/2002"))
|
||
365
|
||
> Ox.getDayOfTheYear(new Date("12/31/2004"))
|
||
366
|
||
@*/
|
||
|
||
Ox.getDayOfTheYear = function(date, utc) {
|
||
date = Ox.makeDate(date);
|
||
var month = Ox.getMonth(date, utc),
|
||
year = Ox.getFullYear(date, utc);
|
||
return Ox.sum(Ox.map(Ox.range(month), function(i) {
|
||
return Ox.getDaysInMonth(year, i + 1);
|
||
})) + Ox.getDate(date, utc);
|
||
};
|
||
|
||
/*@
|
||
Ox.getDaysInMonth <f> Get the number of days in a given month
|
||
> Ox.getDaysInMonth(2000, 2)
|
||
29
|
||
> Ox.getDaysInMonth("2002", "Feb")
|
||
28
|
||
> Ox.getDaysInMonth(new Date('01/01/2004'), "February")
|
||
29
|
||
@*/
|
||
Ox.getDaysInMonth = function(year, month, utc) {
|
||
year = Ox.makeYear(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 new Date(year, month, 0).getDate();
|
||
}
|
||
|
||
/*@
|
||
Ox.getDaysInYear <f> Get the number of days in a given year
|
||
> Ox.getDaysInYear(1900)
|
||
365
|
||
> Ox.getDaysInYear('2000')
|
||
366
|
||
> Ox.getDaysInYear(new Date('01/01/2004'))
|
||
366
|
||
@*/
|
||
Ox.getDaysInYear = function(year, utc) {
|
||
return 365 + Ox.isLeapYear(Ox.makeYear(year, utc));
|
||
};
|
||
|
||
/*@
|
||
Ox.getFirstDayOfTheYear <f> Get the weekday of the first day of a given year
|
||
Returns the decimal weekday of January 1 (0-6, Sunday as first day)
|
||
> Ox.getFirstDayOfTheYear(new Date('01/01/2000'))
|
||
6
|
||
@*/
|
||
Ox.getFirstDayOfTheYear = function(date, utc) {
|
||
date = Ox.makeDate(date);
|
||
date = Ox.setMonth(date, 0, utc);
|
||
date = Ox.setDate(date, 1, utc);
|
||
return Ox.getDay(date, utc)
|
||
};
|
||
|
||
//@ Ox.getFullYear <f> Get the year of a date, optionally UTC
|
||
// see Ox.setSeconds for source code
|
||
//@ Ox.getHours <f> Get the hours of a date, optionally UTC
|
||
// see Ox.setSeconds for source code
|
||
|
||
/*@
|
||
Ox.getISODate <f> Get the ISO date string for a given date
|
||
> Ox.getISODate(new Date('01/01/2000'))
|
||
'2000-01-01T00:00:00Z'
|
||
@*/
|
||
Ox.getISODate = function(date, utc) {
|
||
return Ox.formatDate(Ox.makeDate(date), '%FT%TZ', utc);
|
||
};
|
||
|
||
/*@
|
||
Ox.getISODay <f> Get the ISO weekday of a given date
|
||
Returns the 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
|
||
@*/
|
||
Ox.getISODay = function(date, utc) {
|
||
return Ox.getDay(Ox.makeDate(date), utc) || 7;
|
||
};
|
||
|
||
/*@
|
||
Ox.getISOWeek <f> Get the ISO week of a given date
|
||
See <a href="http://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a>
|
||
> Ox.getISOWeek(new Date('01/01/2000'))
|
||
52
|
||
> Ox.getISOWeek(new Date('01/02/2000'))
|
||
52
|
||
> Ox.getISOWeek(new Date('01/03/2000'))
|
||
1
|
||
@*/
|
||
|
||
Ox.getISOWeek = function(date, utc) {
|
||
date = Ox.makeDate(date);
|
||
// set date to Thursday of the same week
|
||
return Math.floor((Ox.getDayOfTheYear(Ox.setDate(
|
||
date, Ox.getDate(date, utc) - Ox.getISODay(date, utc) + 4, utc
|
||
), utc) - 1) / 7) + 1;
|
||
};
|
||
|
||
/*@
|
||
Ox.getISOYear <f> Get the ISO year of a given date
|
||
See <a href="http://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a>
|
||
> Ox.getISOYear(new Date("01/01/2000"))
|
||
1999
|
||
> Ox.getISOYear(new Date("01/02/2000"))
|
||
1999
|
||
> Ox.getISOYear(new Date("01/03/2000"))
|
||
2000
|
||
@*/
|
||
|
||
Ox.getISOYear = function(date, utc) {
|
||
date = Ox.makeDate(date);
|
||
// set date to Thursday of the same week
|
||
return Ox.getFullYear(Ox.setDate(
|
||
date, Ox.getDate(date, utc) - Ox.getISODay(date, utc) + 4, utc
|
||
));
|
||
};
|
||
|
||
//@ Ox.getMilliseconds <f> Get the milliseconds of a date
|
||
// see Ox.setSeconds for source code
|
||
//@ Ox.getMinutes <f> Get the minutes of a date, optionally UTC
|
||
// see Ox.setSeconds for source code
|
||
//@ Ox.getMonth <f> Get the month of a date, optionally UTC
|
||
// see Ox.setSeconds for source code
|
||
//@ Ox.getSeconds <f> Get the seconds of a date
|
||
// see Ox.setSeconds for source code
|
||
|
||
//@ Ox.getTime <f> Alias for <code>+new Date()</code> (deprecated)
|
||
Ox.getTime = function() {
|
||
// fixme: needed?
|
||
return +new Date();
|
||
};
|
||
|
||
/*@
|
||
Ox.getTimezoneOffset <f> Get the local time zone offset in milliseconds
|
||
@*/
|
||
Ox.getTimezoneOffset = function() {
|
||
return new Date().getTimezoneOffset() * 60000;
|
||
};
|
||
|
||
/*@
|
||
Ox.getTimezoneOffsetString <f> Get the local time zone offset as a string
|
||
Returns a time zone offset string (from around '-1200' to around '+1200').
|
||
> Ox.getTimezoneOffsetString(new Date('01/01/2000')).length
|
||
5
|
||
@*/
|
||
Ox.getTimezoneOffsetString = function(date) {
|
||
var offset = (Ox.makeDate(date)).getTimezoneOffset();
|
||
return (offset < 0 ? '+' : '-') +
|
||
Ox.pad(Math.floor(Math.abs(offset) / 60), 2) +
|
||
Ox.pad(Math.abs(offset) % 60, 2);
|
||
};
|
||
|
||
/*@
|
||
Ox.getWeek <f> Get the week of a given day
|
||
Returns the 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
|
||
@*/
|
||
Ox.getWeek = function(date, utc) {
|
||
date = Ox.makeDate(date);
|
||
return Math.floor((Ox.getDayOfTheYear(date, utc) +
|
||
Ox.getFirstDayOfTheYear(date, utc) - 1) / 7);
|
||
};
|
||
|
||
/*@
|
||
Ox.isLeapYear <f> Returns true if a given year is a leap year
|
||
> Ox.isLeapYear(1900)
|
||
false
|
||
> Ox.isLeapYear('2000')
|
||
true
|
||
> Ox.isLeapYear(new Date('01/01/2004'))
|
||
true
|
||
@*/
|
||
Ox.isLeapYear = function(year, utc) {
|
||
year = Ox.makeYear(year, utc);
|
||
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
|
||
};
|
||
|
||
/*@
|
||
Ox.makeDate <f> Takes a date, number or string, returns a date
|
||
> Ox.formatDate(Ox.makeDate(new Date('01/01/1970')), '%m/%d/%Y')
|
||
'01/01/1970'
|
||
> Ox.formatDate(Ox.makeDate(0), '%m/%d/%Y')
|
||
'01/01/1970'
|
||
> Ox.formatDate(Ox.makeDate('01/01/1970'), '%m/%d/%Y')
|
||
'01/01/1970'
|
||
@*/
|
||
Ox.makeDate = function(date) {
|
||
// if date is a date, new Date(date) makes a clone
|
||
return Ox.isUndefined(date) ? new Date() : new Date(date);
|
||
};
|
||
|
||
/*@
|
||
Ox.makeYear <f> Takes a date, number or string, returns a year
|
||
> Ox.makeYear(new Date('01/01/1970'))
|
||
1970
|
||
> Ox.makeYear(1970)
|
||
1970
|
||
> Ox.makeYear('1970')
|
||
1970
|
||
@*/
|
||
Ox.makeYear = function(date, utc) {
|
||
return Ox.isDate(date) ? Ox.getFullYear(date, utc) : parseInt(date);
|
||
};
|
||
|
||
/*@
|
||
Ox.parseDate <f> Takes a string ('YYYY-MM-DD HH:MM:SS') and returns a date
|
||
str <s> string
|
||
utc <b|false> If true, Date is UTC
|
||
> +Ox.parseDate('1970-01-01 01:01:01', true)
|
||
3661000
|
||
> +Ox.parseDate('1970', true)
|
||
0
|
||
> Ox.parseDate('50', true).getUTCFullYear()
|
||
50
|
||
@*/
|
||
Ox.parseDate = function(str, utc) {
|
||
var date = new Date(0),
|
||
defaults = [, 1, 1, 0, 0, 0],
|
||
values = /(-?\d+)-?(\d+)?-?(\d+)? ?(\d+)?:?(\d+)?:?(\d+)?/.exec(str);
|
||
values.shift();
|
||
values = values.map(function(v, i) {
|
||
return v || defaults[i];
|
||
});
|
||
values[1]--;
|
||
[
|
||
'FullYear', 'Month', 'Date', 'Hours', 'Minutes', 'Seconds'
|
||
].forEach(function(part, i) {
|
||
Ox['set' + part](date, values[i], utc);
|
||
});
|
||
return date;
|
||
};
|
||
|
||
/*
|
||
Ox.parseDateRange = function(start, end, utc) {
|
||
var dates = [
|
||
Ox.parseDate(start, utc),
|
||
Ox.parseDate(end, utc)
|
||
],
|
||
part = [
|
||
'FullYear', 'Month', 'Date', 'Hours', 'Minutes', 'Seconds'
|
||
][
|
||
Ox.compact(
|
||
/(-?\d+)-?(\d+)?-?(\d+)? ?(\d+)?:?(\d+)?:?(\d+)?/.exec(end)
|
||
).length - 2
|
||
];
|
||
Ox['set' + part](dates[1], Ox['get' + part](dates[1], utc) + 1, utc);
|
||
return dates;
|
||
};
|
||
*/
|
||
|
||
//@ Ox.setDate <f> Set the day of a date, optionally UTC
|
||
// see Ox.setSeconds for source code
|
||
//@ Ox.setDay <f> Set the weekday of a date, optionally UTC
|
||
// see Ox.setSeconds for source code
|
||
//@ Ox.setFullYear <f> Set the year of a date, optionally UTC
|
||
// see Ox.setSeconds for source code
|
||
//@ Ox.setHours <f> Set the hours of a date, optionally UTC
|
||
// see Ox.setSeconds for source code
|
||
//@ Ox.setMilliseconds <f> Set the milliseconds of a date
|
||
// see Ox.setSeconds for source code
|
||
//@ Ox.setMinutes <f> Set the minutes of a date, optionally UTC
|
||
// see Ox.setSeconds for source code
|
||
//@ Ox.setMonth <f> Set the month of a date, optionally UTC
|
||
// see Ox.setSeconds for source code
|
||
//@ Ox.setSeconds <f> Set the seconds of a date
|
||
|
||
[
|
||
'FullYear', 'Month', 'Date', 'Day',
|
||
'Hours', 'Minutes', 'Seconds', 'Milliseconds'
|
||
].forEach(function(part) {
|
||
Ox['get' + part] = function(date, utc) {
|
||
return Ox.makeDate(date)['get' + (utc ? 'UTC' : '') + part]()
|
||
}
|
||
// Ox.setPart(date) modifies date
|
||
Ox['set' + part] = function(date, num, utc) {
|
||
return (
|
||
Ox.isDate(date) ? date : new Date(date)
|
||
)['set' + (utc ? 'UTC' : '') + part](num);
|
||
}
|
||
});
|
||
|
||
//@ DOM ------------------------------------------------------------------------
|
||
|
||
/*@
|
||
Ox.canvas <function> Generic canvas object
|
||
Returns an object with the properties: <code>canvas</code>,
|
||
<code>context</code>, <code>data</code> and <code>imageData</code>.
|
||
# Usage --------------------------------------------------------------------
|
||
Ox.canvas(width, height) -> <object> canvas
|
||
Ox.canvas(image) -> <object> canvas
|
||
# Arguments ----------------------------------------------------------------
|
||
width <n> Width in px
|
||
height <n> Height in px
|
||
image <e> Image object
|
||
@*/
|
||
|
||
Ox.canvas = function() {
|
||
// Ox.print("CANVAS", arguments)
|
||
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.documentReady <function> Calls a callback function once the DOM is ready
|
||
(callback) -> <boolean> If true, the document was ready
|
||
callback <function> Callback function
|
||
@*/
|
||
Ox.documentReady = (function() {
|
||
var callbacks = [];
|
||
document.onreadystatechange = function() {
|
||
if (document.readyState == 'complete') {
|
||
callbacks.forEach(function(callback) {
|
||
callback();
|
||
});
|
||
delete callbacks;
|
||
}
|
||
};
|
||
return function(callback) {
|
||
if (document.readyState == 'complete') {
|
||
callback();
|
||
return true;
|
||
} else {
|
||
callbacks.push(callback);
|
||
return false;
|
||
}
|
||
}
|
||
}());
|
||
|
||
/*@
|
||
Ox.element <f> Generic HTML element, mimics jQuery
|
||
(str) -> <o> Element object
|
||
str <s> Tagname ('<tagname>') or selector ('tagname', '.classname', '#id')
|
||
> Ox.element("<div>").addClass("red").hasClass("red")
|
||
true
|
||
> Ox.element("<div>").addClass("red").removeClass("red").hasClass("red")
|
||
false
|
||
> Ox.element("<div>").addClass("red").addClass("red")[0].className
|
||
"red"
|
||
> Ox.element("<div>").attr({id: "red"}).attr("id")
|
||
"red"
|
||
> Ox.element("<div>").css("color", "red").css("color")
|
||
"red"
|
||
> Ox.element("<div>").html("red").html()
|
||
"red"
|
||
@*/
|
||
Ox.element = function(str) {
|
||
return {
|
||
//@ 0 <e> The DOM element itself
|
||
0: str[0] == '<' ? document.createElement(str.substr(1, str.length - 2)) :
|
||
// fixme: why only take the first match?
|
||
str[0] == '.' ? document.getElementsByClassName(str.substr(1))[0] :
|
||
str[0] == '#' ? document.getElementById(str.substr(1)) :
|
||
document.getElementsByTagName(str)[0],
|
||
/*@
|
||
addClass <f> Adds a class name
|
||
(className) -> <o> This element
|
||
className <s> Class name
|
||
@*/
|
||
addClass: function(str) {
|
||
this[0].className = this[0].className ? Ox.unique(
|
||
(this[0].className + ' ' + str).split(' ')
|
||
).join(' ') : str;
|
||
return this;
|
||
},
|
||
/*@
|
||
append <f> Appends another element to this element
|
||
(element) -> <o> This element
|
||
element <o> Another element
|
||
@*/
|
||
append: function(element) {
|
||
this[0].appendChild(element[0]);
|
||
return this;
|
||
},
|
||
/*@
|
||
appendTo <f> appends this element object to another element object
|
||
(element) -> <o> This element
|
||
element <o> Another element
|
||
@*/
|
||
appendTo: function(element) {
|
||
element[0].appendChild(this[0]);
|
||
return this;
|
||
},
|
||
/*@
|
||
attr <f> Gets or sets an attribute
|
||
(key) -> <s> Value
|
||
(key, value) -> <o> This element
|
||
({key, value}) -> <o> This element
|
||
key <str> Attribute name
|
||
value <str> Attribute value
|
||
@*/
|
||
attr: function() {
|
||
var ret, that = this;
|
||
if (arguments.length == 1 && Ox.isString(arguments[0])) {
|
||
ret = this[0].getAttribute(arguments[0]);
|
||
} else {
|
||
Ox.forEach(Ox.makeObject(arguments), function(v, k) {
|
||
that[0].setAttribute(k, v);
|
||
});
|
||
ret = this;
|
||
}
|
||
return ret;
|
||
},
|
||
/*@
|
||
click <f> Binds a function to the click event
|
||
(callback) -> <o> This element
|
||
callback <f> Callback function
|
||
event <o> The DOM event
|
||
@*/
|
||
// fixme: why not a generic bind?
|
||
click: function(callback) {
|
||
this[0].onclick = callback;
|
||
return this;
|
||
},
|
||
/*@
|
||
css <f> Gets or sets a CSS attribute
|
||
(key) -> <s> Value
|
||
(key, value) -> <o> This element
|
||
({key, value}) -> <o> This element
|
||
key <str> Attribute name
|
||
value <str> Attribute value
|
||
@*/
|
||
css: function() {
|
||
var ret, that = this;
|
||
if (arguments.length == 1 && Ox.isString(arguments[0])) {
|
||
ret = this[0].style[arguments[0]];
|
||
} else {
|
||
Ox.forEach(Ox.makeObject(arguments), function(v, k) {
|
||
that[0].style[k] = v;
|
||
});
|
||
ret = this;
|
||
}
|
||
return ret;
|
||
},
|
||
/*@
|
||
hasClass <f> Returns true if the element has a given class
|
||
(className) -> <b> True if the element has the class
|
||
className <s> Class name
|
||
@*/
|
||
hasClass: function(str) {
|
||
return this[0].className.split(' ').indexOf(str) > -1;
|
||
},
|
||
/*@
|
||
html <f> Gets or sets the inner HTML
|
||
() -> <s> The inner HTML
|
||
(html) -> <o> This element
|
||
html <s> The inner HTML
|
||
@*/
|
||
html: function(str) {
|
||
var ret;
|
||
if (Ox.isUndefined(str)) {
|
||
ret = this[0].innerHTML;
|
||
} else {
|
||
this[0].innerHTML = str;
|
||
ret = this;
|
||
}
|
||
return ret;
|
||
},
|
||
/*@
|
||
mousedown <f> Binds a function to the mousedown event
|
||
(callback) -> <o> This element
|
||
callback <f> Callback function
|
||
event <o> The DOM event
|
||
@*/
|
||
// fixme: why not a generic bind?
|
||
mousedown: function(callback) {
|
||
this[0].onmousedown = callback;
|
||
return this;
|
||
},
|
||
/*@
|
||
removeAttr <f> Removes an attribute
|
||
(key) -> <o> This element
|
||
key <s> The attribute
|
||
@*/
|
||
removeAttr: function(key) {
|
||
this[0].removeAttribute(key);
|
||
return this;
|
||
},
|
||
/*@
|
||
removeClass <f> Removes a class name
|
||
(className) -> <o> This element
|
||
className <s> Class name
|
||
@*/
|
||
removeClass: function(str) {
|
||
this[0].className = Ox.filter(
|
||
this[0].className.split(' '), function(className) {
|
||
return className != str;
|
||
}
|
||
).join(' ');
|
||
return this;
|
||
}
|
||
}
|
||
};
|
||
|
||
//@ Encoding -------------------------------------------------------------------
|
||
|
||
(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
|
||
// use: num.toString(2).replace(/0/g, '').length % 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 <b> Encode a number as base32
|
||
See <a href="http://www.crockford.com/wrmg/base32.html">Base 32</a>.
|
||
> Ox.encodeBase32(15360)
|
||
'F00'
|
||
> Ox.encodeBase32(33819)
|
||
'110V'
|
||
@*/
|
||
Ox.encodeBase32 = function(num) {
|
||
return Ox.map(num.toString(32), function(char) {
|
||
return digits[parseInt(char, 32)];
|
||
}).join('');
|
||
}
|
||
|
||
/*@
|
||
Ox.decodeBase32 <f> Decodes a base32-encoded number
|
||
See <a href="http://www.crockford.com/wrmg/base32.html">Base 32</a>.
|
||
> Ox.decodeBase32('foo')
|
||
15360
|
||
> Ox.decodeBase32('ILOU')
|
||
33819
|
||
> Ox.decodeBase32('?').toString()
|
||
'NaN'
|
||
@*/
|
||
Ox.decodeBase32 = function(str) {
|
||
return parseInt(Ox.map(str.toUpperCase(), function(char) {
|
||
var index = digits.indexOf(aliases[char] || char);
|
||
return (index == -1 ? ' ' : index).toString(32);
|
||
}).join(''), 32);
|
||
}
|
||
|
||
/*@
|
||
Ox.encodeBase64 <f> Encode a number as base64
|
||
> Ox.encodeBase64(32394)
|
||
'foo'
|
||
@*/
|
||
Ox.encodeBase64 = function(num) {
|
||
return btoa(Ox.encodeBase256(num)).replace(/=/g, '');
|
||
}
|
||
|
||
/*@
|
||
Ox.decodeBase64 <f> Decodes a base64-encoded number
|
||
> Ox.decodeBase64('foo')
|
||
32394
|
||
@*/
|
||
Ox.decodeBase64 = function(str) {
|
||
return Ox.decodeBase256(atob(str));
|
||
}
|
||
|
||
/*@
|
||
Ox.encodeBase128 <f> Encode a number as base128
|
||
> Ox.encodeBase128(1685487)
|
||
'foo'
|
||
@*/
|
||
Ox.encodeBase128 = function(num) {
|
||
var str = '';
|
||
while (num) {
|
||
str = Ox.char(num & 127) + str;
|
||
num >>= 7;
|
||
}
|
||
return str;
|
||
}
|
||
|
||
/*@
|
||
Ox.decodeBase128 <f> Decode a base128-encoded number
|
||
> Ox.decodeBase128('foo')
|
||
1685487
|
||
@*/
|
||
Ox.decodeBase128 = function(str) {
|
||
var num = 0, len = str.length;
|
||
Ox.forEach(str, function(char, i) {
|
||
num += char.charCodeAt(0) << (len - i - 1) * 7;
|
||
});
|
||
return num;
|
||
}
|
||
|
||
/*@
|
||
Ox.encodeBase256 <f> Encode a number as base256
|
||
> Ox.encodeBase256(6713199)
|
||
'foo'
|
||
@*/
|
||
Ox.encodeBase256 = function(num) {
|
||
var str = '';
|
||
while (num) {
|
||
str = Ox.char(num & 255) + str;
|
||
num >>= 8;
|
||
}
|
||
return str;
|
||
}
|
||
|
||
/*@
|
||
Ox.decodeBase256 <f> Decode a base256-encoded number
|
||
> Ox.decodeBase256('foo')
|
||
6713199
|
||
@*/
|
||
Ox.decodeBase256 = function(str) {
|
||
var num = 0, len = str.length;
|
||
Ox.forEach(str, function(char, i) {
|
||
num += char.charCodeAt(0) << (len - i - 1) * 8;
|
||
});
|
||
return num;
|
||
}
|
||
|
||
/*@
|
||
Ox.encodeDeflate <f> Encodes a string, using deflate
|
||
Since PNGs are deflate-encoded, the <code>canvas</code> object's
|
||
<code>toDataURL</code> method provides an efficient implementation.
|
||
The string is encoded as UTF-8 and written to the RGB channels of a
|
||
canvas element, then the PNG dataURL is decoded from base64, and some
|
||
head, tail and chunk names are removed.
|
||
(str) -> <s> The encoded string
|
||
str <s> The string to be encoded
|
||
# Test with: Ox.decodeDeflate(Ox.encodeDeflate('foo'), alert)
|
||
@*/
|
||
|
||
Ox.encodeDeflate = function(str, callback) {
|
||
// Make sure we can encode the full unicode range of characters.
|
||
str = Ox.encodeUTF8(str);
|
||
// We can only safely write to RGB, so we need 1 pixel for 3 bytes.
|
||
// The string length may not be a multiple of 3, so we need to encode
|
||
// the number of padding bytes (1 byte), the string, and non-0-bytes
|
||
// as padding, so that the combined length becomes a multiple of 3.
|
||
var len = 1 + str.length, c = Ox.canvas(Math.ceil(len / 3), 1),
|
||
data, idat, pad = (3 - len % 3) % 3;
|
||
str = Ox.char(pad) + str + Ox.repeat('\u00FF', pad);
|
||
Ox.loop(c.data.length, function(i) {
|
||
// Write character codes into RGB, and 255 into ALPHA
|
||
c.data[i] = i % 4 < 3 ? str.charCodeAt(i - parseInt(i / 4)) : 255;
|
||
});
|
||
c.context.putImageData(c.imageData, 0, 0);
|
||
// Get the PNG data from the data URL and decode it from base64.
|
||
str = atob(c.canvas.toDataURL().split(',')[1]);
|
||
// Discard bytes 0 to 15 (8 bytes PNG signature, 4 bytes IHDR length, 4
|
||
// bytes IHDR name), keep bytes 16 to 19 (width), discard bytes 20 to 29
|
||
// (4 bytes height, 5 bytes flags), keep bytes 29 to 32 (IHDR checksum),
|
||
// keep the rest (IDAT chunks), discard the last 12 bytes (IEND chunk).
|
||
data = str.substr(16, 4) + str.substr(29, 4);
|
||
idat = str.substr(33, str.length - 45);
|
||
while (idat) {
|
||
// Each IDAT chunk is 4 bytes length, 4 bytes name, length bytes
|
||
// data and 4 bytes checksum. We can discard the name parts.
|
||
len = idat.substr(0, 4);
|
||
data += len + idat.substr(8, 4 + (len = Ox.decodeBase256(len)));
|
||
idat = idat.substr(12 + len);
|
||
}
|
||
callback && callback(data);
|
||
return data;
|
||
}
|
||
|
||
/*@
|
||
Ox.decodeDeflate <f> Decodes an deflate-encoded string
|
||
Since PNGs are deflate-encoded, the <code>canvas</code> object's
|
||
<code>drawImage</code> method provides an efficient implementation. The
|
||
string will be wrapped as a PNG dataURL, encoded as base64, and drawn
|
||
onto a canvas element, then the RGB channels will be read, and the
|
||
result will be decoded from UTF8.
|
||
(str) -> <u> undefined
|
||
str <s> The string to be decoded
|
||
callback <f> Callback function
|
||
str <s> The decoded string
|
||
@*/
|
||
|
||
Ox.decodeDeflate = function(str, callback) {
|
||
var image = new Image(),
|
||
// PNG file signature and IHDR chunk
|
||
data = '\u0089PNG\r\n\u001A\n\u0000\u0000\u0000\u000DIHDR'
|
||
+ str.substr(0, 4) + '\u0000\u0000\u0000\u0001'
|
||
+ '\u0008\u0006\u0000\u0000\u0000' + str.substr(4, 4),
|
||
// IDAT chunks
|
||
idat = str.substr(8), len;
|
||
function error() {
|
||
throw new RangeError('Deflate codec can\'t decode data.');
|
||
}
|
||
while (idat) {
|
||
// Reinsert the IDAT chunk names
|
||
len = idat.substr(0, 4);
|
||
data += len + 'IDAT' + idat.substr(4, 4 + (len = Ox.decodeBase256(len)));
|
||
idat = idat.substr(8 + len);
|
||
}
|
||
// IEND chunk
|
||
data += '\u0000\u0000\u0000\u0000IEND\u00AE\u0042\u0060\u0082';
|
||
// Unfortunately, we can't synchronously set the source of an image,
|
||
// draw it onto a canvas, and read its data.
|
||
image.onload = function() {
|
||
str = Ox.makeArray(Ox.canvas(image).data).map(function(v, i) {
|
||
// Read one character per RGB byte, ignore ALPHA.
|
||
return i % 4 < 3 ? Ox.char(v) : '';
|
||
}).join('');
|
||
try {
|
||
// Parse the first byte as number of bytes to chop at the end,
|
||
// and the rest, without these bytes, as an UTF8-encoded string.
|
||
str = Ox.decodeUTF8(str.substr(1, str.length - 1 - str.charCodeAt(0)))
|
||
} catch (e) {
|
||
error();
|
||
}
|
||
callback(str);
|
||
}
|
||
image.onerror = error;
|
||
image.src = 'data:image/png;base64,' + btoa(data);
|
||
}
|
||
|
||
/*@
|
||
Ox.encodeHTML <f> HTML-encodes a string
|
||
> Ox.encodeHTML('\'<"&">\'')
|
||
''<"&">''
|
||
> Ox.encodeHTML('äbçdê')
|
||
'äbçdê'
|
||
@*/
|
||
Ox.encodeHTML = function(str) {
|
||
return Ox.map(str.toString(), 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 <f> Decodes an HTML-encoded string
|
||
> Ox.decodeHTML(''<"&">'')
|
||
'\'<"&">\''
|
||
> Ox.decodeHTML(''<"&">'')
|
||
'\'<"&">\''
|
||
> Ox.decodeHTML('äbçdê')
|
||
'äbçdê'
|
||
> Ox.decodeHTML('äbçdê')
|
||
'äbçdê'
|
||
@*/
|
||
Ox.decodeHTML = function(str) {
|
||
// relies on dom, but shorter than using this:
|
||
// http://www.w3.org/TR/html5/named-character-references.html
|
||
return Ox.element('<div>').html(str)[0].childNodes[0].nodeValue;
|
||
};
|
||
|
||
/*@
|
||
Ox.encodePNG <f> Encodes a string into an image, returns a new image
|
||
The string is compressed with deflate (by proxy of canvas), prefixed
|
||
with its length (four bytes), and encoded bitwise into the red, green
|
||
and blue bytes of all fully opaque pixels of the image, by flipping, if
|
||
necessary, the least significant bit, so that for every byte, the total
|
||
number of bits set to to 1, modulo 2, is the bit that we are encoding.
|
||
(img, src) -> <e> An image into which the string has been encoded
|
||
img <e> Any JavaScript PNG image object
|
||
str <s> The string to be encoded
|
||
@*/
|
||
|
||
Ox.encodePNG = function(img, str) {
|
||
var c = Ox.canvas(img), i = 0;
|
||
// Compress the string
|
||
str = Ox.encodeDeflate(str);
|
||
// Prefix the string with its length, as a four-byte value
|
||
str = Ox.pad(Ox.encodeBase256(str.length), 4, Ox.char(0)) + str;
|
||
// Create an array of bit values
|
||
Ox.forEach(Ox.flatten(Ox.map(str, function(chr) {
|
||
return Ox.map(Ox.range(8), function(i) {
|
||
return chr.charCodeAt(0) >> 7 - i & 1;
|
||
});
|
||
})), function(bit) {
|
||
// Skip all pixels that are not fully opaque
|
||
while (i < c.data.length && i % 4 == 0 && c.data[i + 3] < 255) {
|
||
i += 4;
|
||
}
|
||
if (i == c.data.length) {
|
||
throw new RangeError('PNG codec can\'t encode data');
|
||
}
|
||
// If the number of bits set to one, modulo 2 is equal to the bit,
|
||
// do nothing, otherwise, flip the least significant bit.
|
||
c.data[i] += c.data[i].toString(2).replace(/0/g, '').length % 2
|
||
== bit ? 0 : c.data[i] % 2 ? -1 : 1;
|
||
i++;
|
||
});
|
||
c.context.putImageData(c.imageData, 0, 0);
|
||
img = new Image();
|
||
img.src = c.canvas.toDataURL();
|
||
return img;
|
||
/*
|
||
wishlist:
|
||
- only use deflate if it actually shortens the message
|
||
- 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
|
||
*/
|
||
}
|
||
|
||
/*@
|
||
Ox.decodePNG <f> Decodes an image, returns a string
|
||
For every red, green and blue byte of every fully opaque pixel of the
|
||
image, one bit, namely the number of bits of the byte set to one, modulo
|
||
2, is being read, the result being the string, prefixed with its length
|
||
(four bytes), which is decompressed with deflate (by proxy of canvas).
|
||
(img, callback) -> <u> undefined
|
||
img <e> The image into which the string has been encoded
|
||
callback <f> Callback function
|
||
str <s> The decoded string
|
||
@*/
|
||
|
||
Ox.decodePNG = function(img, callback) {
|
||
var bits = '', data = Ox.canvas(img).data, flag = false, i = 0,
|
||
len = 4, str = '';
|
||
while (len) {
|
||
// Skip all pixels that are not fully opaque
|
||
while (i < data.length && i % 4 == 0 && data[i + 3] < 255) {
|
||
i += 4;
|
||
}
|
||
if (i == data.length) {
|
||
break;
|
||
}
|
||
// Read the number of bits set to one, modulo 2
|
||
bits += data[i].toString(2).replace(/0/g, '').length % 2;
|
||
if (++i % 8 == 0) {
|
||
// Every 8 bits, add one byte
|
||
str += Ox.char(parseInt(bits, 2));
|
||
bits = '';
|
||
// When length reaches 0 for the first time,
|
||
// decode the string and treat it as the new length
|
||
if (--len == 0 && !flag) {
|
||
flag = true;
|
||
len = Ox.decodeBase256(str);
|
||
str = '';
|
||
}
|
||
}
|
||
}
|
||
try {
|
||
Ox.decodeDeflate(str, callback);
|
||
} catch (e) {
|
||
throw new RangeError('PNG codec can\'t decode image');
|
||
}
|
||
}
|
||
|
||
/*@
|
||
Ox.encodeUTF8 <f> Encodes a string as UTF-8
|
||
see http://en.wikipedia.org/wiki/UTF-8
|
||
(string) -> <s> UTF-8 encoded string
|
||
string <s> Any string
|
||
> Ox.encodeUTF8("YES")
|
||
"YES"
|
||
> Ox.encodeUTF8("¥€$")
|
||
"\u00C2\u00A5\u00E2\u0082\u00AC\u0024"
|
||
@*/
|
||
Ox.encodeUTF8 = function(str) {
|
||
return Ox.map(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 <f> Decodes an UTF-8-encoded string
|
||
see http://en.wikipedia.org/wiki/UTF-8
|
||
(utf8) -> <s> string
|
||
utf8 <s> Any UTF-8-encoded string
|
||
> Ox.decodeUTF8('YES')
|
||
'YES'
|
||
> Ox.decodeUTF8('\u00C2\u00A5\u00E2\u0082\u00AC\u0024')
|
||
'¥€$'
|
||
@*/
|
||
Ox.decodeUTF8 = function(str) {
|
||
var bytes = Ox.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 ---------------------------------------------------------------------
|
||
|
||
/*@
|
||
Ox.formatArea <f> Formats a number of meters as square meters or kilometers
|
||
> Ox.formatArea(1000)
|
||
'1,000 m\u00B2'
|
||
> Ox.formatArea(1000000)
|
||
'1 km\u00B2'
|
||
@*/
|
||
|
||
Ox.formatArea = function(num, dec) {
|
||
var km = num >= 1000000;
|
||
return Ox.formatNumber(
|
||
(km ? num / 1000000 : num).toPrecision(8)
|
||
) + ' ' + (km ? 'k' : '') + 'm\u00B2';
|
||
}
|
||
|
||
/*@
|
||
Ox.formatColor <f> (strange one)
|
||
@*/
|
||
Ox.formatColor = function(val, type) {
|
||
var background, color;
|
||
if (type == 'hue') {
|
||
background = Ox.rgb(val, 1, 0.25).map(function(val) {
|
||
return Math.round(val);
|
||
});
|
||
color = Ox.rgb(val, 1, 0.75).map(function(val) {
|
||
return Math.round(val);
|
||
});
|
||
} else if (type == 'saturation') {
|
||
background = Ox.range(7).map(function(i) {
|
||
return Ox.rgb(i * 60, val, 0.25).map(function(val) {
|
||
return Math.round(val);
|
||
});
|
||
});
|
||
color = Ox.range(3).map(function() {
|
||
return Math.round(128 + val * 127);
|
||
});
|
||
} else if (type == 'lightness') {
|
||
background = Ox.range(3).map(function() {
|
||
return Math.round(val * 255);
|
||
});
|
||
color = Ox.range(3).map(function() {
|
||
var v = val * 255;
|
||
return val < 0.5 ? 128 + v : 255 - v;
|
||
});
|
||
}
|
||
return Ox.element('<div>')
|
||
.css({
|
||
borderRadius: '4px',
|
||
padding: '0 3px 1px 3px',
|
||
background: Ox.isNumber(background[0])
|
||
? 'rgb(' + background.join(', ') + ')'
|
||
: Ox.print([/*'moz', 'o', */'webkit'].map(function(browser) {
|
||
return '-' + browser + '-linear-gradient(left, '
|
||
+ background.map(function(rgb, i) {
|
||
return 'rgb(' + rgb.join(', ') + ') '
|
||
+ Math.round(i * 100 / 6) + '%';
|
||
}).join(', ')
|
||
+ ')';
|
||
}).join(', ')) &&
|
||
[/*'moz', 'o', */'webkit'].map(function(browser) {
|
||
return '-' + browser + '-linear-gradient(left, '
|
||
+ background.map(function(rgb, i) {
|
||
return 'rgb(' + rgb.join(', ') + ') '
|
||
+ Math.round(i * 100 / 6) + '%';
|
||
}).join(', ')
|
||
+ ')';
|
||
}).join(', '),
|
||
color: 'rgb(' + color.join(', ') + ')',
|
||
overflow: 'hidden',
|
||
textOverflow: 'ellipsis',
|
||
//textShadow: 'black 1px 1px 1px'
|
||
})
|
||
.html(Ox.formatNumber(val, 3));
|
||
};
|
||
|
||
/*@
|
||
Ox.formatCurrency <f> Formats a number with a currency symbol
|
||
> Ox.formatCurrency(1000, '$', 2)
|
||
'$1,000.00'
|
||
@*/
|
||
|
||
Ox.formatCurrency = function(num, str, dec) {
|
||
return str + Ox.formatNumber(num, dec);
|
||
};
|
||
|
||
/*@
|
||
Ox.formatDate <f> Formats a date according to a format string
|
||
See
|
||
<a href="http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man3/strftime.3.html">strftime</a>
|
||
and <a href="http://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a>.
|
||
'%Q' (quarter) and '%X'/'%x' (year with 'BC'/'AD') are non-standard
|
||
<script>
|
||
Ox.test.date = new Date('2005-01-02 00:03:04');
|
||
</script>
|
||
> Ox.formatDate(Ox.test.date, '%A') // Full weekday
|
||
'Sunday'
|
||
> Ox.formatDate(Ox.test.date, '%a') // Abbreviated weekday
|
||
'Sun'
|
||
> Ox.formatDate(Ox.test.date, '%B') // Full month
|
||
'January'
|
||
> Ox.formatDate(Ox.test.date, '%b') // Abbreviated month
|
||
'Jan'
|
||
> Ox.formatDate(Ox.test.date, '%C') // Century
|
||
'20'
|
||
> Ox.formatDate(Ox.test.date, '%c') // US time and date
|
||
'01/02/05 12:03:04 AM'
|
||
> Ox.formatDate(Ox.test.date, '%D') // US date
|
||
'01/02/05'
|
||
> Ox.formatDate(Ox.test.date, '%d') // Zero-padded day of the month
|
||
'02'
|
||
> Ox.formatDate(Ox.test.date, '%e') // Space-padded day of the month
|
||
' 2'
|
||
> Ox.formatDate(Ox.test.date, '%F') // Date
|
||
'2005-01-02'
|
||
> Ox.formatDate(Ox.test.date, '%G') // Full ISO-8601 year
|
||
'2004'
|
||
> Ox.formatDate(Ox.test.date, '%g') // Abbreviated ISO-8601 year
|
||
'04'
|
||
> Ox.formatDate(Ox.test.date, '%H') // Zero-padded hour (24-hour clock)
|
||
'00'
|
||
> Ox.formatDate(Ox.test.date, '%h') // Abbreviated month
|
||
'Jan'
|
||
> Ox.formatDate(Ox.test.date, '%I') // Zero-padded hour (12-hour clock)
|
||
'12'
|
||
> Ox.formatDate(Ox.test.date, '%j') // Zero-padded day of the year
|
||
'002'
|
||
> Ox.formatDate(Ox.test.date, '%k') // Space-padded hour (24-hour clock)
|
||
' 0'
|
||
> Ox.formatDate(Ox.test.date, '%l') // Space-padded hour (12-hour clock)
|
||
'12'
|
||
> Ox.formatDate(Ox.test.date, '%M') // Zero-padded minute
|
||
'03'
|
||
> Ox.formatDate(Ox.test.date, '%m') // Zero-padded month
|
||
'01'
|
||
> Ox.formatDate(Ox.test.date, '%n') // Newline
|
||
'\n'
|
||
> Ox.formatDate(Ox.test.date, '%p') // AM or PM
|
||
'AM'
|
||
> Ox.formatDate(Ox.test.date, '%Q') // Quarter of the year
|
||
'1'
|
||
> Ox.formatDate(Ox.test.date, '%R') // Zero-padded hour and minute
|
||
'00:03'
|
||
> Ox.formatDate(Ox.test.date, '%r') // US time
|
||
'12:03:04 AM'
|
||
> Ox.formatDate(Ox.test.date, '%S') // Zero-padded second
|
||
'04'
|
||
> Ox.formatDate(Ox.test.date, '%s', true) // Number of seconds since the Epoch
|
||
'1104620584'
|
||
> Ox.formatDate(Ox.test.date, '%T') // Time
|
||
'00:03:04'
|
||
> Ox.formatDate(Ox.test.date, '%t') // Tab
|
||
'\t'
|
||
> Ox.formatDate(Ox.test.date, '%U') // Zero-padded week of the year (00-53, Sunday as first day)
|
||
'01'
|
||
> Ox.formatDate(Ox.test.date, '%u') // Decimal weekday (1-7, Monday as first day)
|
||
'7'
|
||
> Ox.formatDate(Ox.test.date, '%V') // Zero-padded ISO-8601 week of the year
|
||
'53'
|
||
> Ox.formatDate(Ox.test.date, '%v') // Formatted date
|
||
' 2-Jan-2005'
|
||
> Ox.formatDate(Ox.test.date, '%W') // Zero-padded week of the year (00-53, Monday as first day)
|
||
'00'
|
||
> Ox.formatDate(Ox.test.date, '%w') // Decimal weekday (0-6, Sunday as first day)
|
||
'0'
|
||
> Ox.formatDate(Ox.test.date, '%X') // Full year with BC or AD
|
||
'2005 AD'
|
||
> Ox.formatDate(Ox.test.date, '%x') // Full year with BC or AD if year < 1000
|
||
'2005'
|
||
> Ox.formatDate(Ox.test.date, '%Y') // Full year
|
||
'2005'
|
||
> Ox.formatDate(Ox.test.date, '%y') // Abbreviated year
|
||
'05'
|
||
> Ox.formatDate(Ox.test.date, '%Z', true) // Time zone name
|
||
'UTC'
|
||
> Ox.formatDate(Ox.test.date, '%z', true) // Time zone offset
|
||
'+0000'
|
||
> Ox.formatDate(Ox.test.date, '%+', true) // Formatted date and time
|
||
'Sun Jan 2 00:03:04 CET 2005'
|
||
> Ox.formatDate(Ox.test.date, '%%')
|
||
'%'
|
||
@*/
|
||
|
||
Ox.formatDate = function(date, str, utc) {
|
||
// fixme: date and utc are optional, date can be date, number or string
|
||
date = Ox.makeDate(date);
|
||
var format = [
|
||
['%', function() {return '%{%}';}],
|
||
['c', function() {return '%D %r';}],
|
||
['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 Ox.WEEKDAYS[(Ox.getDay(d, utc) + 6) % 7];}],
|
||
['a', function(d) {return Ox.SHORT_WEEKDAYS[(Ox.getDay(d, utc) + 6) % 7];}],
|
||
['B', function(d) {return Ox.MONTHS[Ox.getMonth(d, utc)];}],
|
||
['b', function(d) {return Ox.SHORT_MONTHS[Ox.getMonth(d, utc)];}],
|
||
['C', function(d) {return Math.floor(Ox.getFullYear(d, utc) / 100).toString();}],
|
||
['d', function(d) {return Ox.pad(Ox.getDate(d, utc), 2);}],
|
||
['e', function(d) {return Ox.pad(Ox.getDate(d, utc), 2, ' ');}],
|
||
['G', function(d) {return Ox.getISOYear(d, utc);}],
|
||
['g', function(d) {return Ox.getISOYear(d, utc).toString().substr(-2);}],
|
||
['H', function(d) {return Ox.pad(Ox.getHours(d, utc), 2);}],
|
||
['I', function(d) {return Ox.pad((Ox.getHours(d, utc) + 11) % 12 + 1, 2);}],
|
||
['j', function(d) {return Ox.pad(Ox.getDayOfTheYear(d, utc), 3);}],
|
||
['k', function(d) {return Ox.pad(Ox.getHours(d, utc), 2, ' ');}],
|
||
['l', function(d) {return Ox.pad(((Ox.getHours(d, utc) + 11) % 12 + 1), 2, ' ');}],
|
||
['M', function(d) {return Ox.pad(Ox.getMinutes(d, utc), 2);}],
|
||
['m', function(d) {return Ox.pad((Ox.getMonth(d, utc) + 1), 2);}],
|
||
['p', function(d) {return Ox.AMPM[Math.floor(Ox.getHours(d, utc) / 12)];}],
|
||
['Q', function(d) {return Math.floor(Ox.getMonth(d, utc) / 4) + 1;}],
|
||
['S', function(d) {return Ox.pad(Ox.getSeconds(d, utc), 2);}],
|
||
['s', function(d) {return Math.floor(d.getTime() / 1000);}],
|
||
['U', function(d) {return Ox.pad(Ox.getWeek(d, utc), 2);}],
|
||
['u', function(d) {return Ox.getISODay(d, utc);}],
|
||
['V', function(d) {return Ox.pad(Ox.getISOWeek(d, utc), 2);}],
|
||
['W', function(d) {
|
||
return Ox.pad(Math.floor((Ox.getDayOfTheYear(d, utc) +
|
||
(Ox.getFirstDayOfTheYear(d, utc) || 7) - 2) / 7), 2);
|
||
}],
|
||
['w', function(d) {return Ox.getDay(d, utc);}],
|
||
['X', function(d) {
|
||
var y = Ox.getFullYear(d, utc);
|
||
return Math.abs(y) + ' ' + Ox.BCAD[y < 0 ? 0 : 1];
|
||
}],
|
||
['x', function(d) {
|
||
var y = Ox.getFullYear(d, utc);
|
||
return Math.abs(y) + (y < 1000 ? ' ' + Ox.BCAD[y < 0 ? 0 : 1] : '');
|
||
}],
|
||
['Y', function(d) {return Ox.getFullYear(d, utc);}],
|
||
['y', function(d) {return Ox.getFullYear(d, utc).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 '%';}]
|
||
];
|
||
format.forEach(function(v) {
|
||
var regexp = new RegExp('%' + v[0], 'g');
|
||
if (regexp.test(str)) {
|
||
str = str.replace(regexp, v[1](date));
|
||
}
|
||
});
|
||
return str;
|
||
};
|
||
|
||
/*@
|
||
Ox.formatDateRange <f> Formats a date range as a string
|
||
A date range is a pair of arbitrary-presicion date strings
|
||
> Ox.formatDateRange('2000', '2001')
|
||
'2000'
|
||
> Ox.formatDateRange('2000', '2002')
|
||
'2000 - 2002'
|
||
> Ox.formatDateRange('2000-01', '2000-02')
|
||
'January 2000'
|
||
> Ox.formatDateRange('2000-01', '2000-03')
|
||
'January - March 2000'
|
||
> Ox.formatDateRange('2000-01-01', '2000-01-02')
|
||
'Sat, Jan 1, 2000'
|
||
> Ox.formatDateRange('2000-01-01', '2000-01-03')
|
||
'Sat, Jan 1 - Mon, Jan 3, 2000'
|
||
> Ox.formatDateRange('2000-01-01 00', '2000-01-01 01')
|
||
'Sat, Jan 1, 2000, 00:00'
|
||
> Ox.formatDateRange('2000-01-01 00', '2000-01-01 02')
|
||
'Sat, Jan 1, 2000, 00:00 - 02:00'
|
||
> Ox.formatDateRange('2000-01-01 00:00', '2000-01-01 00:01')
|
||
'Sat, Jan 1, 2000, 00:00'
|
||
> Ox.formatDateRange('2000-01-01 00:00', '2000-01-01 00:02')
|
||
'Sat, Jan 1, 2000, 00:00 - 00:02'
|
||
> Ox.formatDateRange('2000-01-01 00:00:00', '2000-01-01 00:00:01')
|
||
'Sat, Jan 1, 2000, 00:00:00'
|
||
> Ox.formatDateRange('2000-01-01 00:00:00', '2000-01-01 00:00:02')
|
||
'Sat, Jan 1, 2000, 00:00:00 - 00:00:02'
|
||
> Ox.formatDateRange('-50', '50')
|
||
'50 BC - 50 AD'
|
||
> Ox.formatDateRange('-50-01-01', '-50-12-31')
|
||
'Sun, Jan 1 - Sun, Dec 31, 50 BC'
|
||
> Ox.formatDateRange('-50-01-01 00:00:00', '-50-01-01 23:59:59')
|
||
'Sun, Jan 1, 50 BC, 00:00:00 - 23:59:59'
|
||
@*/
|
||
Ox.formatDateRange = function(start, end, utc) {
|
||
var isOneUnit = false,
|
||
range = [start, end],
|
||
strings,
|
||
dates = range.map(function(str){
|
||
return Ox.parseDate(str, utc);
|
||
}),
|
||
parts = range.map(function(str) {
|
||
var parts = Ox.compact(
|
||
/(-?\d+)-?(\d+)?-?(\d+)? ?(\d+)?:?(\d+)?:?(\d+)?/.exec(str)
|
||
);
|
||
parts.shift();
|
||
return parts.map(function(part) {
|
||
return parseInt(part);
|
||
});
|
||
}),
|
||
precision = parts.map(function(parts) {
|
||
return parts.length;
|
||
}),
|
||
y = parts[0][0] < 0 ? '%X' : '%Y',
|
||
formats = [
|
||
y,
|
||
'%B ' + y,
|
||
'%a, %b %e, ' + y,
|
||
'%a, %b %e, ' + y + ', %H:%M',
|
||
'%a, %b %e, ' + y + ', %H:%M',
|
||
'%a, %b %e, ' + y + ', %H:%M:%S',
|
||
];
|
||
if (precision[0] == precision[1]) {
|
||
isOneUnit = true;
|
||
Ox.loop(precision[0], function(i) {
|
||
if (i < precision[0] - 1 && parts[0][i] != parts[1][i]) {
|
||
isOneUnit = false;
|
||
}
|
||
if (i == precision[0] - 1 && parts[0][i] != parts[1][i] - 1) {
|
||
isOneUnit = false;
|
||
}
|
||
return isOneUnit;
|
||
});
|
||
}
|
||
if (isOneUnit) {
|
||
strings = [Ox.formatDate(dates[0], formats[precision[0] - 1], utc)];
|
||
} else {
|
||
format = formats[precision[0] - 1];
|
||
strings = [
|
||
Ox.formatDate(dates[0], formats[precision[0] - 1], utc),
|
||
Ox.formatDate(dates[1], formats[precision[1] - 1], utc)
|
||
];
|
||
// if same year, and neither date is more precise than day, then omit first year
|
||
if (
|
||
parts[0][0] == parts[1][0]
|
||
&& precision[0] <= 3
|
||
&& precision[1] <= 3
|
||
) {
|
||
strings[0] = Ox.formatDate(
|
||
dates[0], formats[precision[0] - 1].replace(
|
||
new RegExp(',? ' + y), ''
|
||
), utc
|
||
);
|
||
}
|
||
// if same day then omit second day
|
||
if (
|
||
parts[0][0] == parts[1][0]
|
||
&& parts[0][1] == parts[1][1]
|
||
&& parts[0][2] == parts[1][2]
|
||
) {
|
||
strings[1] = strings[1].split(', ').pop();
|
||
}
|
||
}
|
||
return strings.map(function(string) {
|
||
// %e is a space-padded day
|
||
return string.replace(' ', ' ');
|
||
}).join(' - ');
|
||
};
|
||
|
||
/*@
|
||
Ox.formatDateRangeDuration <f> Formats the duration of a date range as a string
|
||
A date range is a pair of arbitrary-presicion date strings
|
||
> Ox.formatDateRangeDuration('2000-01-01 00:00:00', '2001-03-04 04:05:06')
|
||
'1 year 2 months 3 days 4 hours 5 minutes 6 seconds'
|
||
> Ox.formatDateRangeDuration('1999', '2000', true)
|
||
'1 year'
|
||
> Ox.formatDateRangeDuration('2000', '2001', true)
|
||
'1 year'
|
||
> Ox.formatDateRangeDuration('1999-02', '1999-03', true)
|
||
'1 month'
|
||
> Ox.formatDateRangeDuration('2000-02', '2000-03', true)
|
||
'1 month'
|
||
@*/
|
||
Ox.formatDateRangeDuration = function(start, end, utc) {
|
||
var date = Ox.parseDate(start, utc),
|
||
dates = [start, end].map(function(str) {
|
||
return Ox.parseDate(str, utc);
|
||
}),
|
||
keys = ['year', 'month', 'day', 'hour', 'minute', 'second'],
|
||
parts = ['FullYear', 'Month', 'Date', 'Hours', 'Minutes', 'Seconds'],
|
||
values = [];
|
||
Ox.forEach(keys, function(key, i) {
|
||
while (true) {
|
||
if (key == 'month') {
|
||
Ox.setDate(date, Math.min(
|
||
Ox.getDate(date, utc),
|
||
Ox.getDaysInMonth(
|
||
Ox.getFullYear(date, utc),
|
||
Ox.getMonth(date, utc) + 2
|
||
)
|
||
), utc);
|
||
}
|
||
Ox['set' + parts[i]](date, Ox['get' + parts[i]](date, utc) + 1, utc);
|
||
if (date <= dates[1]) {
|
||
values[i] = (values[i] || 0) + 1;
|
||
} else {
|
||
Ox['set' + parts[i]](date, Ox['get' + parts[i]](date, utc) - 1, utc);
|
||
break;
|
||
}
|
||
}
|
||
});
|
||
return Ox.map(values, function(value, i) {
|
||
return value ? value + ' ' + keys[i] + (value > 1 ? 's' : '') : null;
|
||
}).join(' ');
|
||
};
|
||
|
||
/*@
|
||
Ox.formatDuration <f> Formats a duration as a string
|
||
> 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"
|
||
@*/
|
||
Ox.formatDuration = function(sec, dec, format) {
|
||
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 Ox.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 <f> Formats a number with thousands separators
|
||
> Ox.formatNumber(123456789, 3)
|
||
"123,456,789.000"
|
||
> Ox.formatNumber(-2000000 / 3, 3)
|
||
"-666,666.667"
|
||
> Ox.formatNumber(666666.666, 0)
|
||
"666,667"
|
||
@*/
|
||
Ox.formatNumber = function(num, dec) {
|
||
// fixme: specify decimal and thousands separators
|
||
var arr = [],
|
||
abs = Math.abs(num),
|
||
str = Ox.isUndefined(dec) ? abs.toString() : abs.toFixed(dec),
|
||
spl = str.split('.');
|
||
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.formatOrdinal <f> Formats a number as an ordinal
|
||
> Ox.formatOrdinal(1)
|
||
"1st"
|
||
> Ox.formatOrdinal(2)
|
||
"2nd"
|
||
> Ox.formatOrdinal(3)
|
||
"3rd"
|
||
> Ox.formatOrdinal(4)
|
||
"4th"
|
||
> Ox.formatOrdinal(11)
|
||
"11th"
|
||
> Ox.formatOrdinal(12)
|
||
"12th"
|
||
> Ox.formatOrdinal(13)
|
||
"13th"
|
||
@*/
|
||
Ox.formatOrdinal = function(num) {
|
||
var str = num.toString(),
|
||
end = str[str.length - 1],
|
||
ten = str.length > 1 && str[str.length - 2] == '1';
|
||
if (end == '1' && !ten) {
|
||
str += 'st';
|
||
} else if (end == '2' && !ten) {
|
||
str += 'nd';
|
||
} else if (end == '3' && !ten) {
|
||
str += 'rd';
|
||
} else {
|
||
str += 'th';
|
||
}
|
||
return str;
|
||
};
|
||
|
||
/*@
|
||
Ox.formatPercent <f> Formats the relation of two numbers as a percentage
|
||
> Ox.formatPercent(1, 1000, 2)
|
||
"0.10%"
|
||
@*/
|
||
Ox.formatPercent = function(num, total, dec) {
|
||
return Ox.formatNumber(num / total * 100, dec) + '%'
|
||
};
|
||
|
||
/*@
|
||
Ox.formatResolution <f> Formats two values as a resolution
|
||
> Ox.formatResolution([1920, 1080], 'px')
|
||
"1920 x 1080 px"
|
||
@*/
|
||
// fixme: should be formatDimensions
|
||
Ox.formatResolution = function(arr, str) {
|
||
return arr[0] + ' x ' + arr[1] + (str ? ' ' + str : '');
|
||
}
|
||
|
||
/*@
|
||
Ox.formatString <f> Basic string formatting
|
||
> Ox.formatString('{0}{1}', ['foo', 'bar'])
|
||
'foobar'
|
||
> Ox.formatString('{a}{b}', {a: 'foo', b: 'bar'})
|
||
'foobar'
|
||
@*/
|
||
|
||
Ox.formatString = function (str, obj) {
|
||
return str.replace(/\{([^}]+)\}/g, function(str, match) {
|
||
return obj[match];
|
||
});
|
||
}
|
||
|
||
/*@
|
||
Ox.formatValue <f> Formats a numerical value
|
||
> Ox.formatValue(0, "B")
|
||
"0 KB"
|
||
> Ox.formatValue(123456789, "B")
|
||
"123.5 MB"
|
||
> Ox.formatValue(1234567890, "B", true)
|
||
"1.15 GiB"
|
||
@*/
|
||
// fixme: is this the best name?
|
||
Ox.formatValue = function(num, str, bin) {
|
||
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 <f> Formats a number with a unit
|
||
@*/
|
||
Ox.formatUnit = function(num, str) {
|
||
return num + ' ' + str;
|
||
};
|
||
|
||
//@ Geo ------------------------------------------------------------------------
|
||
|
||
(function() {
|
||
|
||
// fixme: make all this work with different types of "points"
|
||
// i.e. {lat, lng}, [lat, lng]
|
||
|
||
function rad(point) {
|
||
return {
|
||
lat: Ox.rad(point.lat),
|
||
lng: Ox.rad(point.lng)
|
||
};
|
||
}
|
||
|
||
/*@
|
||
Ox.crossesDateline <f> Returns true if a given rectangle crosses the dateline
|
||
@*/
|
||
Ox.crossesDateline = function(point0, point1) {
|
||
return point0.lng > point1.lng;
|
||
}
|
||
|
||
/*@
|
||
Ox.getArea <f> Returns the area in square meters of a given rectancle
|
||
@*/
|
||
Ox.getArea = function(point0, point1) {
|
||
/*
|
||
area of a ring between two latitudes:
|
||
2 * PI * r^2 * abs(sin(lat0) - sin(lat1))
|
||
see http://mathforum.org/library/drmath/view/63767.html
|
||
*/
|
||
/*
|
||
2 * Math.PI *
|
||
Math.pow(Ox.EARTH_RADIUS, 2) *
|
||
Math.abs(Math.sin(Ox.rad(0)) - Math.sin(Ox.rad(1))) *
|
||
Math.abs(Ox.rad(0) - Ox.rad(1)) /
|
||
(2 * Math.PI)
|
||
*/
|
||
if (Ox.crossesDateline(point0, point1)) {
|
||
point1.lng += 360;
|
||
}
|
||
var point0 = rad(point0),
|
||
point1 = rad(point1);
|
||
return Math.pow(Ox.EARTH_RADIUS, 2) *
|
||
Math.abs(Math.sin(point0.lat) - Math.sin(point1.lat)) *
|
||
Math.abs(point0.lng - point1.lng);
|
||
};
|
||
|
||
/*@
|
||
Ox.getBearing <f> Returns the bearing from one point to another
|
||
> Ox.getBearing({lat: -45, lng: 0}, {lat: 45, lng: 0})
|
||
0
|
||
@*/
|
||
Ox.getBearing = function(point0, point1) {
|
||
var point0 = rad(point0),
|
||
point1 = rad(point1),
|
||
x = Math.cos(point0.lat) * Math.sin(point1.lat) -
|
||
Math.sin(point0.lat) * Math.cos(point1.lat) *
|
||
Math.cos(point1.lng - point0.lng),
|
||
y = Math.sin(point1.lng - point0.lng) *
|
||
Math.cos(point1.lat);
|
||
return (Ox.deg(Math.atan2(y, x)) + 360) % 360;
|
||
};
|
||
|
||
/*@
|
||
Ox.getCenter <f> Returns the center of a recangle on a spehre
|
||
> Ox.getCenter({lat: -45, lng: -90}, {lat: 45, lng: 90})
|
||
{lat: 0, lng: 0}
|
||
@*/
|
||
Ox.getCenter = function(point0, point1) {
|
||
var point0 = rad(point0),
|
||
point1 = rad(point1),
|
||
x = Math.cos(point1.lat) *
|
||
Math.cos(point1.lng - point0.lng),
|
||
y = Math.cos(point1.lat) *
|
||
Math.sin(point1.lng - point0.lng),
|
||
d = Math.sqrt(
|
||
Math.pow(Math.cos(point0.lat) + x, 2) + Math.pow(y, 2)
|
||
),
|
||
lat = Ox.deg(
|
||
Math.atan2(Math.sin(point0.lat) + Math.sin(point1.lat), d)
|
||
),
|
||
lng = Ox.deg(
|
||
point0.lng + Math.atan2(y, Math.cos(point0.lat) + x)
|
||
);
|
||
return {lat: lat, lng: lng};
|
||
};
|
||
|
||
/*@
|
||
Ox.getDegreesPerMeter <f> Returns degrees per meter at a given latitude
|
||
> 360 / Ox.getDegreesPerMeter(0)
|
||
Ox.EARTH_CIRCUMFERENCE
|
||
@*/
|
||
Ox.getDegreesPerMeter = function(lat) {
|
||
return 360 / Ox.EARTH_CIRCUMFERENCE / Math.cos(lat * Math.PI / 180);
|
||
};
|
||
|
||
/*@
|
||
Ox.getDistance <f> Returns the distance in meters between two points
|
||
> Ox.getDistance({lat: -45, lng: -90}, {lat: 45, lng: 90}) * 2
|
||
Ox.EARTH_CIRCUMFERENCE
|
||
@*/
|
||
Ox.getDistance = function(point0, point1) {
|
||
var point0 = rad(point0),
|
||
point1 = rad(point1);
|
||
return Math.acos(
|
||
Math.sin(point0.lat) * Math.sin(point1.lat) +
|
||
Math.cos(point0.lat) * Math.cos(point1.lat) *
|
||
Math.cos(point1.lng - point0.lng)
|
||
) * Ox.EARTH_RADIUS;
|
||
};
|
||
|
||
/*@
|
||
Ox.getLatLngByXY <f> Returns lat/lng for a given x/y on a 1x1 mercator projection
|
||
> Ox.getLatLngByXY({x: 0.5, y: 0.5})
|
||
{lat: 0, lng: 0}
|
||
@*/
|
||
Ox.getLatLngByXY = function(xy) {
|
||
function getVal(val) {
|
||
return (val - 0.5) * 2 * Math.PI;
|
||
}
|
||
return {
|
||
lat: -Ox.deg(Math.atan(Ox.sinh(getVal(xy.y)))),
|
||
lng: Ox.deg(getVal(xy.x))
|
||
}
|
||
};
|
||
|
||
/*@
|
||
Ox.getMetersPerDegree <f> Returns meters per degree at a given latitude
|
||
> Ox.getMetersPerDegree(0) * 360
|
||
Ox.EARTH_CIRCUMFERENCE
|
||
@*/
|
||
Ox.getMetersPerDegree = function(lat) {
|
||
return Math.cos(lat * Math.PI / 180) * Ox.EARTH_CIRCUMFERENCE / 360;
|
||
};
|
||
|
||
/*@
|
||
Ox.getXYByLatLng <f> Returns x/y on a 1x1 mercator projection for a given lat/lng
|
||
> Ox.getXYByLatLng({lat: 0, lng: 0})
|
||
{x: 0.5, y: 0.5}
|
||
@*/
|
||
Ox.getXYByLatLng = function(latlng) {
|
||
function getVal(val) {
|
||
return (val / (2 * Math.PI) + 0.5)
|
||
}
|
||
return {
|
||
x: getVal(Ox.rad(latlng.lng)),
|
||
y: getVal(Ox.asinh(Math.tan(Ox.rad(-latlng.lat))))
|
||
};
|
||
};
|
||
|
||
}());
|
||
|
||
//@ Ox.Line <f> (undocumented)
|
||
Ox.Line = function(point0, point1) {
|
||
|
||
var self = {
|
||
points: [point0, point1]
|
||
},
|
||
that = this;
|
||
|
||
function rad() {
|
||
return self.points.map(function(point) {
|
||
return {
|
||
lat: Ox.rad(point.lat()),
|
||
lng: Ox.rad(point.lng())
|
||
};
|
||
});
|
||
}
|
||
|
||
that.getArea = function() {
|
||
|
||
};
|
||
|
||
that.getBearing = function() {
|
||
};
|
||
|
||
that.getDistance = function() {
|
||
var points = rad();
|
||
return Math.acos(
|
||
Math.sin(point[0].lat) * Math.sin(point[1].lat) +
|
||
Math.cos(point[0].lat) * Math.cos(point[1].lat) *
|
||
Math.cos(point[1].lng - point[0].lng)
|
||
) * Ox.EARTH_RADIUS;
|
||
};
|
||
|
||
that.getMidpoint = function() {
|
||
var points = rad(),
|
||
x = Math.cos(point[1].lat) *
|
||
Math.cos(point[1].lng - point[0].lng),
|
||
y = Math.cos(point[1].lat) *
|
||
Math.sin(point[1].lng - point[0].lng),
|
||
d = Math.sqrt(
|
||
Math.pow(Math.cos(point[0].lat) + x, 2) + Math.pow(y, 2)
|
||
),
|
||
lat = Ox.deg(
|
||
Math.atan2(Math.sin(points[0].lat) + Math.sin(points[1].lat), d)
|
||
),
|
||
lng = Ox.deg(
|
||
points[0].lng + Math.atan2(y, math.cos(points[0].lat) + x)
|
||
);
|
||
return new Point(lat, lng);
|
||
};
|
||
|
||
that.points = function() {
|
||
|
||
};
|
||
|
||
return that;
|
||
|
||
};
|
||
|
||
//@ Ox.Point <f> (undocumented)
|
||
Ox.Point = function(lat, lng) {
|
||
|
||
var self = {lat: lat, lng: lng},
|
||
that = this;
|
||
|
||
that.lat = function() {
|
||
|
||
};
|
||
|
||
that.latlng = function() {
|
||
|
||
};
|
||
|
||
that.lng = function() {
|
||
|
||
};
|
||
|
||
that.getMetersPerDegree = function() {
|
||
return Math.cos(self.lat * Math.PI / 180) *
|
||
Ox.EARTH_CIRCUMFERENCE / 360;
|
||
}
|
||
|
||
that.getXY = function() {
|
||
return [
|
||
getXY(Ox.rad(self.lng)),
|
||
getXY(Ox.asinh(Math.tan(Ox.rad(-self.lat))))
|
||
];
|
||
};
|
||
|
||
return that;
|
||
|
||
};
|
||
|
||
//@ Ox.Rectangle <f> (undocumented)
|
||
Ox.Rectangle = function(point0, point1) {
|
||
|
||
var self = {
|
||
points: [
|
||
new Point(
|
||
Math.min(point0.lat(), point1.lat()),
|
||
point0.lng()
|
||
),
|
||
new Point(
|
||
Math.max(point0.lat(), point1.lat()),
|
||
point1.lng()
|
||
)
|
||
]
|
||
},
|
||
that = this;
|
||
|
||
that.contains = function(rectangle) {
|
||
|
||
}
|
||
|
||
that.crossesDateline = function() {
|
||
return self.points[0].lng > self.points[1].lng;
|
||
}
|
||
|
||
that.getCenter = function() {
|
||
return new Ox.Line(self.points[0], self.points[1]).getMidpoint();
|
||
};
|
||
|
||
that.intersects = function(rectangle) {
|
||
|
||
};
|
||
|
||
return that;
|
||
|
||
};
|
||
|
||
//@ HTML -----------------------------------------------------------------------
|
||
|
||
/*@
|
||
Ox.parseEmailAddresses <f> Takes HTML and turns e-mail addresses into links
|
||
@*/
|
||
// fixme: no tests
|
||
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 <f> Takes HTML from an untrusted source and returns something sane
|
||
> 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>')
|
||
'<a href="javascript:alert()">foo</a>'
|
||
> 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>')
|
||
'<script>alert()</script>'
|
||
@*/
|
||
Ox.parseHTML = (function() {
|
||
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); fixme: can this be a parameter?
|
||
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();
|
||
// fixme: this converts '"' to '"'
|
||
return Ox.element('<div>').html(html).html();
|
||
}
|
||
|
||
}());
|
||
|
||
/*@
|
||
Ox.parseURL <f> Takes a URL, returns its components
|
||
(url) -> <o> URL components
|
||
url <s> URL
|
||
<script>
|
||
Ox.test.object = Ox.parseURL('http://www.foo.com:8080/bar/index.html?a=0&b=1#c');
|
||
</script>
|
||
> Ox.test.object.hash
|
||
'#c'
|
||
> Ox.test.object.host
|
||
'www.foo.com:8080'
|
||
> Ox.test.object.hostname
|
||
'www.foo.com'
|
||
> Ox.test.object.origin
|
||
'http://www.foo.com:8080'
|
||
> Ox.test.object.pathname
|
||
'/bar/index.html'
|
||
> Ox.test.object.port
|
||
'8080'
|
||
> Ox.test.object.protocol
|
||
'http:'
|
||
> Ox.test.object.search
|
||
'?a=0&b=1'
|
||
@*/
|
||
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 <f> Takes HTML and turns URLs into links
|
||
@*/
|
||
// fixme: is parseURLs the right name?
|
||
// fixme: no tests
|
||
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
|
||
}
|
||
);
|
||
}
|
||
);
|
||
};
|
||
|
||
//@ JavaScript -----------------------------------------------------------------
|
||
|
||
/*@
|
||
Ox.doc <f> Generates documentation for annotated JavaScript
|
||
(file, callback) -> <u> undefined
|
||
file <s> JavaScript file
|
||
callback <f> Callback function
|
||
doc <[o]> Array of doc objects
|
||
arguments <[o]|u> Arguments (array of doc objects)
|
||
Present if the <code>type</code> of the item is
|
||
<code>"function"</code>.
|
||
description <s|u> Multi-line description with optional markup
|
||
See Ox.parseHTML for details
|
||
events <[o]|u> Events (array of doc objects)
|
||
Present if the item fires any events
|
||
file <s> File name
|
||
line <n> Line number
|
||
name <s> Name of the item
|
||
properties <[o]|u> Properties (array of doc objects)
|
||
Present if the <code>type</code> of the item is
|
||
<code>"event"</code>, <code>"function"</code>
|
||
or <code>"object"</code>.
|
||
section <s|u> Section in the file
|
||
source <[o]> Source code (array of tokens)
|
||
length <n> Length of the token
|
||
offset <n> Offset of the token
|
||
type <s> Type of the token
|
||
See Ox.tokenize for list of types
|
||
summary <s> One-line summary
|
||
usage <[o]> Usage (array of doc objects)
|
||
Present if the <code>type</code> of the item is
|
||
<code>"function"</code>.
|
||
type <s> Type of the item
|
||
|
||
(source) <a> Array of documentation objects
|
||
source <s> JavaScript source code
|
||
# > Ox.doc("//@ Ox.foo <string> just some string")
|
||
# [{"name": "Ox.foo", "summary": "just some string", "type": "string"}]
|
||
@*/
|
||
|
||
Ox.doc = (function() {
|
||
// fixme: dont require the trailing '@'
|
||
var re = {
|
||
item: /^(.+?) <(.+?)> (.+)$/,
|
||
multiline: /^\/\*\@.*?\n([\w\W]+)\n.*?\@\*\/$/,
|
||
script: /\n(\s*<script>s*\n[\w\W]+\n\s*<\/script>s*)/g,
|
||
singleline: /^\/\/@\s*(.*?)\s*$/,
|
||
test: /\n(\s*> .+\n.+?)/g,
|
||
usage: /\(.*?\)/
|
||
},
|
||
types = {
|
||
a: 'array', b: 'boolean', d: 'date',
|
||
e: 'element', f: 'function', n: 'number',
|
||
o: 'object', r: 'regexp', s: 'string',
|
||
u: 'undefined', '*': 'any', '!': 'event'
|
||
};
|
||
function decodeLinebreaks(match, submatch) {
|
||
return (submatch || match).replace(/\u21A9/g, '\n');
|
||
}
|
||
function encodeLinebreaks(match, submatch) {
|
||
return '\n' + (submatch || match).replace(/\n/g, '\u21A9');
|
||
}
|
||
function getIndent(str) {
|
||
var indent = -1;
|
||
while (str[++indent] == ' ') {}
|
||
return indent;
|
||
}
|
||
function parseItem(str) {
|
||
var matches = re.item.exec(str);
|
||
// to tell a variable with default value, like
|
||
// name <string|'<a href="...">foo</a>'> summary
|
||
// from a line of description with tags, like
|
||
// some <a href="...">description</a> text
|
||
// we need to check if there is either no forward slash
|
||
// or if the second last char is a single or double quote
|
||
return matches && (
|
||
matches[2].indexOf('/') == -1 ||
|
||
'\'"'.indexOf(matches[2].substr(-2, 1)) > -1
|
||
) ? Ox.extend({
|
||
name: parseName(matches[1].trim()),
|
||
summary: matches[3].trim()
|
||
}, parseType(matches[2])) : null;
|
||
}
|
||
function parseName(str) {
|
||
var matches = re.usage.exec(str);
|
||
return matches ? matches[0] : str;
|
||
}
|
||
function parseNode(node) {
|
||
var item = parseItem(node.line), subitem;
|
||
node.nodes && node.nodes.forEach(function(node) {
|
||
var key, line = node.line, subitem;
|
||
if (!/^#/.test(node.line)) {
|
||
if (/^<script>/.test(line)) {
|
||
item.examples = [parseScript(line)];
|
||
} else if (/^>/.test(line)) {
|
||
item.examples = item.examples || [];
|
||
item.examples.push(parseTest(line));
|
||
} else if ((subitem = parseItem(line))) {
|
||
if (/^\(/.test(subitem.name)) {
|
||
item.usage = item.usage || [];
|
||
item.usage.push(parseNode(node));
|
||
} else if (subitem.types[0] == 'event') {
|
||
item.events = item.events || [];
|
||
item.events.push(parseNode(node));
|
||
} else {
|
||
key = item.types[0] == 'function' ?
|
||
'arguments' : 'properties'
|
||
item[key] = item[key] || [];
|
||
item[key].push(parseNode(node));
|
||
}
|
||
} else {
|
||
item.description = item.description ?
|
||
item.description + ' ' + line : line
|
||
}
|
||
}
|
||
});
|
||
return item;
|
||
}
|
||
function parseScript(str) {
|
||
// remove script tags and extra indentation
|
||
var lines = decodeLinebreaks(str).split('\n'),
|
||
indent = getIndent(lines[1]);
|
||
return {
|
||
statement: Ox.map(lines, function(line, i) {
|
||
return i && i < lines.length - 1 ?
|
||
line.substr(indent) : null;
|
||
}).join('\n')
|
||
};
|
||
}
|
||
function parseTest(str) {
|
||
var lines = decodeLinebreaks(str).split('\n');
|
||
return {
|
||
statement: lines[0].substr(2),
|
||
result: lines[1].trim()
|
||
};
|
||
}
|
||
function parseTokens(tokens, includeLeading) {
|
||
// fixme: do not strip whitespace from the beginning of the first line of the items' source
|
||
var leading = [],
|
||
tokens_ = [];
|
||
tokens.forEach(function(token) {
|
||
if (['linebreak', 'whitespace'].indexOf(token.type) > -1) {
|
||
includeLeading && leading.push(token);
|
||
} else {
|
||
tokens_ = Ox.merge(tokens_, leading, [token]);
|
||
leading = [];
|
||
includeLeading = true;
|
||
}
|
||
});
|
||
return tokens_;
|
||
}
|
||
function parseTree(lines) {
|
||
// parses indented lines into a tree structure, like
|
||
// {line: "...", nodes: [{line: "...", nodes: [...]}]}
|
||
var branches = [],
|
||
indent,
|
||
node = {
|
||
// chop the root line
|
||
line: lines.shift().trim()
|
||
};
|
||
if (lines.length) {
|
||
indent = getIndent(lines[0]);
|
||
lines.forEach(function(line) {
|
||
if (getIndent(line) == indent) {
|
||
// line is a child,
|
||
// make it the root line of a new branch
|
||
branches.push([line]);
|
||
} else {
|
||
// line is a descendant of the last child,
|
||
// add it to the last branch
|
||
branches[branches.length - 1].push(line);
|
||
}
|
||
});
|
||
node.nodes = branches.map(function(lines) {
|
||
return parseTree(lines);
|
||
});
|
||
}
|
||
return node;
|
||
}
|
||
function parseType(str) {
|
||
// returns {types: [""]}
|
||
// or {types: [""], default: ""}
|
||
// or {types: [""], parent: ""}
|
||
var isArray,
|
||
ret = {types: []},
|
||
split,
|
||
type;
|
||
// only split by ':' if there is no default string value
|
||
if ('\'"'.indexOf(str.substr(-2, 1)) == -1) {
|
||
split = str.split(':');
|
||
str = split[0];
|
||
if (split.length == 2) {
|
||
ret.parent = split[1];
|
||
}
|
||
}
|
||
str.split('|').forEach(function(str) {
|
||
var unwrapped = unwrap(str);
|
||
if (unwrapped in types) {
|
||
ret.types.push(wrap(types[unwrapped]))
|
||
} else if (
|
||
(type = Ox.filter(Ox.values(types), function(type) {
|
||
return Ox.startsWith(type, unwrapped);
|
||
})).length
|
||
) {
|
||
ret.types.push(wrap(type[0]));
|
||
} else {
|
||
ret['default'] = str;
|
||
}
|
||
});
|
||
function unwrap(str) {
|
||
return (isArray = /^\[.+\]$/.test(str)) ?
|
||
str = str.substr(1, str.length - 2) : str;
|
||
}
|
||
function wrap(str) {
|
||
return isArray ?
|
||
'array[' + str + (str != 'any' ? 's' : '') + ']' : str;
|
||
}
|
||
return ret;
|
||
}
|
||
return function(file, callback) {
|
||
Ox.get(file, function(source) {
|
||
var blocks = [],
|
||
items = [],
|
||
section = '',
|
||
tokens = [];
|
||
Ox.tokenize(source).forEach(function(token) {
|
||
var match;
|
||
token.source = source.substr(token.offset, token.length);
|
||
if (token.type == 'comment' && (match =
|
||
re.multiline.exec(token.source)|| re.singleline.exec(token.source)
|
||
)) {
|
||
blocks.push(match[1]);
|
||
tokens.push([]);
|
||
} else if (tokens.length) {
|
||
tokens[tokens.length - 1].push(token);
|
||
}
|
||
});
|
||
/*
|
||
var blocks = Ox.map(Ox.tokenize(source), function(token) {
|
||
// filter out tokens that are not comments
|
||
// or don't match the doc comment pattern
|
||
var match;
|
||
token.source = source.substr(token.offset, token.length);
|
||
return token.type == 'comment' && (match =
|
||
re.multiline(token.source) || re.singleline(token.source)
|
||
) ? match[1] : null;
|
||
}),
|
||
items = [];
|
||
*/
|
||
blocks.forEach(function(block, i) {
|
||
var item, lastItem,
|
||
lines = block
|
||
.replace(re.script, encodeLinebreaks)
|
||
.replace(re.test, encodeLinebreaks)
|
||
.split('\n'),
|
||
tree = parseTree(lines);
|
||
if (re.item.test(tree.line)) {
|
||
// parse the tree's root node
|
||
item = parseNode(tree);
|
||
item.file = file;
|
||
if (section) {
|
||
item.section = section;
|
||
}
|
||
if (/^[A-Z]/.test(item.name)) {
|
||
// main item
|
||
item.source = parseTokens(tokens[i]);
|
||
item.line = source.substr(0, item.source[0].offset)
|
||
.split('\n').length;
|
||
items.push(item);
|
||
} else {
|
||
// property of a function item
|
||
lastItem = items[items.length - 1];
|
||
lastItem.properties = lastItem.properties || [];
|
||
lastItem.properties.push(item);
|
||
// include leading linebreaks and whitespace
|
||
lastItem.source = Ox.merge(
|
||
lastItem.source, parseTokens(tokens[i], true)
|
||
);
|
||
}
|
||
} else {
|
||
section = tree.line.split(' ')[0]
|
||
}
|
||
});
|
||
callback(items);
|
||
});
|
||
}
|
||
}());
|
||
|
||
/*@
|
||
Ox.minify <f> Minifies JavaScript (experimental!)
|
||
@*/
|
||
Ox.minify = function(source) {
|
||
var tokens = Ox.tokenize(source)
|
||
return Ox.map(tokens, function(token, i) {
|
||
var ret = source.substr(token.offset, token.type == 'whitespace' ? 1 : token.length);
|
||
if (token.type == 'comment') {
|
||
ret = null;
|
||
} else if (
|
||
token.type == 'linebreak' && (
|
||
i == 0 || i == tokens.length - 1 ||
|
||
tokens[i - 1].type == 'linebreak' ||
|
||
tokens[i + 1].type == 'linebreak'
|
||
)
|
||
) {
|
||
ret = null;
|
||
} else if (
|
||
token.type == 'whitespace' && (
|
||
i == 0 || i == tokens.length - 1 ||
|
||
tokens[i - 1].type != 'keyword' || (
|
||
tokens[i + 1].type == 'operator'
|
||
)
|
||
)
|
||
) {
|
||
ret = null;
|
||
}
|
||
return ret;
|
||
}).join('');
|
||
};
|
||
|
||
/*@
|
||
Ox.test <f> Takes JavaScript, runs inline tests, returns results
|
||
@*/
|
||
Ox.test = function(file, callback) {
|
||
Ox.doc(file, function(items) {
|
||
var tests = [];
|
||
items.forEach(function(item) {
|
||
item.examples && item.examples.forEach(function(example) {
|
||
var actual = eval(example.statement);
|
||
if (example.result) {
|
||
tests.push({
|
||
actual: JSON.stringify(actual),
|
||
expected: example.result,
|
||
name: item.name,
|
||
section: item.section,
|
||
statement: example.statement,
|
||
passed: Ox.isEqual(eval(
|
||
'Ox.test.result = ' + example.result
|
||
), actual)
|
||
});
|
||
}
|
||
});
|
||
});
|
||
callback(tests);
|
||
});
|
||
};
|
||
|
||
/*@
|
||
Ox.tokenize <f> Tokenizes JavaScript
|
||
(source) -> <[o]> Array of tokens
|
||
length <n> Length of the token
|
||
offset <n> Offset of the token
|
||
type <s> Type of the token
|
||
Type can be <code>"comment"</code>, <code>"constant"</code>,
|
||
<code>"identifier"</code>, <code>"keyword"</code>,
|
||
<code>"linebreak"</code>, <code>"method"</code>,
|
||
<code>"number"</code>, <code>"object"</code>,
|
||
<code>"operator"</code>, <code>"property"</code>,
|
||
<code>"regexp"</code>, <code>"string"</code>
|
||
or <code>"whitespace"</code>
|
||
source <s> JavaScript source code
|
||
@*/
|
||
Ox.tokenize = (function() {
|
||
|
||
// see https://github.com/mozilla/narcissus/blob/master/lib/jslex.js
|
||
// and https://developer.mozilla.org/en/JavaScript/Reference
|
||
|
||
var identifier = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_',
|
||
linebreak = '\n\r',
|
||
number = '0123456789',
|
||
operator = [
|
||
// arithmetic
|
||
'+', '-', '*', '/', '%', '++', '--',
|
||
// assignment
|
||
'=', '+=', '-=', '*=', '/=', '%=',
|
||
'&=', '|=', '^=', '<<=', '>>=', '>>>=',
|
||
// bitwise
|
||
'&', '|', '^', '~', '<<', '>>', '>>>',
|
||
// comparison
|
||
'==', '!=', '===', '!==', '>', '>=', '<', '<=',
|
||
// conditional
|
||
'?', ':',
|
||
// grouping
|
||
'(', ')', '[', ']', '{', '}',
|
||
// logical
|
||
'&&', '||', '!',
|
||
// other
|
||
'.', ',', ';'
|
||
],
|
||
whitespace = ' \t',
|
||
word = {
|
||
constant: [
|
||
// Math
|
||
'E', 'LN2', 'LN10', 'LOG2E', 'LOG10E', 'PI', 'SQRT1_2', 'SQRT2',
|
||
// Number
|
||
'MAX_VALUE', 'MIN_VALUE', 'NEGATIVE_INFINITY', 'POSITIVE_INFINITY'
|
||
],
|
||
keyword: [
|
||
'break',
|
||
'case', 'catch', 'class', 'const', 'continue',
|
||
'debugger', 'default', 'delete', 'do',
|
||
'else', 'enum', 'export', 'extends',
|
||
'false', 'finally', 'for', 'function',
|
||
'if', 'implements', 'import', 'in', 'instanceof', 'interface',
|
||
'let', 'module',
|
||
'new', 'null',
|
||
'package', 'private', 'protected', 'public',
|
||
'return',
|
||
'super', 'switch', 'static',
|
||
'this', 'throw', 'true', 'try', 'typeof',
|
||
'var', 'void',
|
||
'yield',
|
||
'while', 'with',
|
||
],
|
||
method: [
|
||
// Array
|
||
'concat',
|
||
'every',
|
||
'filter', 'forEach',
|
||
'join',
|
||
'lastIndexOf',
|
||
'indexOf', 'isArray',
|
||
'map',
|
||
'pop', 'push',
|
||
'reduce', 'reduceRight', 'reverse',
|
||
'shift', 'slice', 'some', 'sort', 'splice',
|
||
'unshift',
|
||
// Date
|
||
'getDate', 'getDay', 'getFullYear', 'getHours',
|
||
'getMilliseconds', 'getMinutes', 'getMonth', 'getSeconds',
|
||
'getTime', 'getTimezoneOffset',
|
||
'getUTCDate', 'getUTCDay', 'getUTCFullYear', 'getUTCHours',
|
||
'getUTCMilliseconds', 'getUTCMinutes', 'getUTCMonth', 'getUTCSeconds',
|
||
'now',
|
||
'parse',
|
||
'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
|
||
'setMinutes', 'setMonth', 'setSeconds', 'setTime',
|
||
'setUTCDate', 'setUTCFullYear', 'setUTCHours', 'setUTCMilliseconds',
|
||
'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
|
||
'toDateString', 'toJSON', 'toLocaleDateString', 'toLocaleString',
|
||
'toLocaleTimeString', 'toTimeString', 'toUTCString',
|
||
'UTC',
|
||
// Function
|
||
'apply', 'bind', 'call', 'isGenerator',
|
||
// JSON
|
||
'parse', 'stringify',
|
||
// Math
|
||
'abs', 'acos', 'asin', 'atan', 'atan2',
|
||
'ceil', 'cos',
|
||
'exp',
|
||
'floor',
|
||
'log',
|
||
'max', 'min',
|
||
'pow',
|
||
'random', 'round',
|
||
'sin', 'sqrt',
|
||
'tan',
|
||
// Number
|
||
'toExponential', 'toFixed', 'toLocaleString', 'toPrecision',
|
||
// Object
|
||
'create',
|
||
'defineProperty', 'defineProperties',
|
||
'freeze',
|
||
'getOwnPropertyDescriptor', 'getOwnPropertyNames', 'getPrototypeOf',
|
||
'hasOwnProperty',
|
||
'isExtensible', 'isFrozen', 'isPrototypeOf', 'isSealed',
|
||
'keys',
|
||
'preventExtensions', 'propertyIsEnumerable',
|
||
'seal',
|
||
'toLocaleString', 'toString',
|
||
'valueOf',
|
||
// RegExp
|
||
'exec', 'test',
|
||
// String
|
||
'charAt', 'charCodeAt', 'concat',
|
||
'fromCharCode',
|
||
'indexOf',
|
||
'lastIndexOf', 'localeCompare',
|
||
'match',
|
||
'replace',
|
||
'search', 'slice', 'split', 'substr', 'substring',
|
||
'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toUpperCase', 'trim',
|
||
// Window
|
||
'addEventListener', 'alert', 'atob',
|
||
'blur', 'btoa',
|
||
'clearInterval', 'clearTimeout', 'close', 'confirm',
|
||
'dispatchEvent',
|
||
'escape',
|
||
'find', 'focus',
|
||
'getComputedStyle', 'getSelection',
|
||
'moveBy', 'moveTo',
|
||
'open',
|
||
'postMessage', 'print', 'prompt',
|
||
'removeEventListener', 'resizeBy', 'resizeTo',
|
||
'scroll', 'scrollBy', 'scrollTo',
|
||
'setCursor', 'setInterval', 'setTimeout', 'stop',
|
||
'unescape'
|
||
],
|
||
object: [
|
||
'Array',
|
||
'Boolean',
|
||
'Date', 'decodeURI', 'decodeURIComponent',
|
||
'encodeURI', 'encodeURIComponent', 'Error', 'eval', 'EvalError',
|
||
'Function',
|
||
'Infinity', 'isFinite', 'isNaN',
|
||
'JSON',
|
||
'Math',
|
||
'NaN', 'Number',
|
||
'Object',
|
||
'parseFloat', 'parseInt',
|
||
'RangeError', 'ReferenceError', 'RegExp',
|
||
'String', 'SyntaxError',
|
||
'TypeError',
|
||
'undefined', 'URIError',
|
||
'window'
|
||
],
|
||
property: [
|
||
// Function
|
||
'constructor', 'length', 'prototype',
|
||
// RegExp
|
||
'global', 'ignoreCase', 'lastIndex', 'multiline', 'source',
|
||
// Window
|
||
'applicationCache',
|
||
'closed', 'content', 'crypto',
|
||
'defaultStatus', 'document',
|
||
'frameElement', 'frames',
|
||
'history',
|
||
'innerHeight', 'innerWidth',
|
||
'length', 'location', 'locationbar', 'localStorage',
|
||
'menubar',
|
||
'name', 'navigator',
|
||
'opener', 'outerHeight', 'outerWidth',
|
||
'pageXOffset', 'pageYOffset', 'parent', 'personalbar',
|
||
'screen', 'screenX', 'screenY', 'scrollbars', 'scrollX', 'scrollY',
|
||
'self', 'sessionStorage', 'status', 'statusbar',
|
||
'toolbar', 'top'
|
||
]
|
||
// Window stuff? 'atob', 'btoa', 'console', 'document' ...
|
||
};
|
||
|
||
return function(source) {
|
||
|
||
//source = source.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
||
|
||
var cursor = 0,
|
||
tokenize = {
|
||
comment: function() {
|
||
while (char = source[++cursor]) {
|
||
if (next == '/' && char == '\n') {
|
||
break;
|
||
} else if (next == '*' && char == '*' && source[cursor + 1] == '/') {
|
||
cursor += 2;
|
||
break;
|
||
}
|
||
}
|
||
},
|
||
identifier: function() {
|
||
var str;
|
||
while ((identifier + number).indexOf(source[++cursor]) > -1) {}
|
||
str = source.substring(start, cursor);
|
||
Ox.forEach(word, function(value, key) {
|
||
if (value.indexOf(str) > -1) {
|
||
type = key;
|
||
return false;
|
||
}
|
||
});
|
||
},
|
||
linebreak: function() {
|
||
while (linebreak.indexOf(source[++cursor]) > -1) {}
|
||
},
|
||
number: function() {
|
||
while ((number + '.').indexOf(source[++cursor]) > -1) {}
|
||
},
|
||
operator: function() {
|
||
if (operator.indexOf(char + source[++cursor]) > -1) {
|
||
if (operator.indexOf(char + next + source[++cursor]) > 1) {
|
||
++cursor;
|
||
}
|
||
}
|
||
},
|
||
regexp: function() {
|
||
while ((char = source[++cursor]) != '/') {
|
||
char == '\\' && ++cursor;
|
||
if (cursor == source.length) {
|
||
break;
|
||
}
|
||
}
|
||
while (identifier.indexOf(source[++cursor]) > -1) {}
|
||
},
|
||
string: function() {
|
||
var delimiter = char;
|
||
while ((char = source[++cursor]) != delimiter) {
|
||
char == '\\' && ++cursor;
|
||
if (cursor == source.length) {
|
||
break;
|
||
}
|
||
}
|
||
++cursor;
|
||
},
|
||
whitespace: function() {
|
||
while (whitespace.indexOf(source[++cursor]) > -1) {}
|
||
}
|
||
},
|
||
tokens = [],
|
||
type;
|
||
|
||
while (cursor < source.length) {
|
||
var char = source[cursor],
|
||
next = source[cursor + 1],
|
||
start = cursor;
|
||
if (char == '/' && (next == '/' || next == '*')) {
|
||
type = 'comment';
|
||
} else if (identifier.indexOf(char) > -1) {
|
||
type = 'identifier';
|
||
} else if (linebreak.indexOf(char) > -1) {
|
||
type = 'linebreak';
|
||
} else if (number.indexOf(char) > -1) {
|
||
type = 'number';
|
||
} else if (char == "'" || char == '"') {
|
||
type = 'string';
|
||
} else if (whitespace.indexOf(char) > -1) {
|
||
type = 'whitespace';
|
||
} else if (char == '/') {
|
||
type = isRegExp() ? 'regexp' : 'operator';
|
||
} else if (operator.indexOf(char) > -1) {
|
||
type = 'operator';
|
||
}
|
||
tokenize[type]();
|
||
tokens.push({
|
||
length: cursor - start,
|
||
offset: start,
|
||
type: type,
|
||
});
|
||
}
|
||
|
||
function isRegExp() {
|
||
// checks if a forward slash is the beginning of a regexp,
|
||
// as opposed to the beginning of an operator
|
||
// see http://www.mozilla.org/js/language/js20-2000-07/rationale/syntax.html#regular-expressions
|
||
var index = tokens.length,
|
||
isRegExp = false
|
||
offset = 0;
|
||
// scan back to the previous significant token,
|
||
// or the beginning of the source
|
||
while (
|
||
typeof tokens[--index] != 'undefined' &&
|
||
['comment', 'linebreak', 'whitespace'].indexOf(tokens[index].type) > -1
|
||
) {
|
||
offset += tokens[index].length;
|
||
}
|
||
if (typeof tokens[index] == 'undefined') {
|
||
// source begins with forward slash
|
||
isRegExp = true;
|
||
} else {
|
||
prevToken = tokens[index];
|
||
prevString = source.substr(cursor - prevToken.length - offset, prevToken.length);
|
||
isRegExp = (
|
||
prevToken.type == 'keyword' &&
|
||
['false', 'null', 'true'].indexOf(prevString) == -1
|
||
) || (
|
||
prevToken.type == 'operator' &&
|
||
['++', '--', ')', ']', '}'].indexOf(prevString) == -1
|
||
);
|
||
}
|
||
return isRegExp;
|
||
}
|
||
|
||
return tokens;
|
||
|
||
};
|
||
|
||
}());
|
||
|
||
//@ Math -----------------------------------------------------------------------
|
||
|
||
/*@
|
||
Ox.asinh <f> Inverse hyperbolic sine
|
||
Strangely missing from <code>Math</code>.
|
||
@*/
|
||
Ox.asinh = function(x) {
|
||
// fixme: no test
|
||
return Math.log(x + Math.sqrt(x * x + 1));
|
||
};
|
||
|
||
/*@
|
||
Ox.deg <f> Takes radians, returns degrees
|
||
Strangely missing from <code>Math</code>.
|
||
> Ox.deg(2 * Math.PI)
|
||
360
|
||
@*/
|
||
Ox.deg = function(rad) {
|
||
return rad * 180 / Math.PI;
|
||
};
|
||
|
||
/*@
|
||
Ox.divideInt <f> Divides a number by another and returns an array of integers
|
||
<code>Ox.divideInt(num, by)</code> returns a sorted array of "as equal as
|
||
possible" integers that has a sum of <code>num</code> and a length of
|
||
<code>by</code>.
|
||
> Ox.divideInt(100, 3)
|
||
[33, 33, 34]
|
||
> Ox.divideInt(100, 6)
|
||
[16, 16, 17, 17, 17, 17]
|
||
@*/
|
||
Ox.divideInt = function(num, by) {
|
||
var arr = [],
|
||
div = parseInt(num / by),
|
||
mod = num % by;
|
||
Ox.loop(by, function(i) {
|
||
arr[i] = div + (i > by - 1 - mod);
|
||
});
|
||
return arr;
|
||
}
|
||
|
||
/*@
|
||
Ox.limit <f> Limits a number by a given mininum and maximum
|
||
<code>Ox.limit(num, min, max)</code> is a shorthand for
|
||
<code>Math.min(Math.max(num, min), max)</code>
|
||
(num) -> <n> <code>num</code>
|
||
(num, max) -> <n> <code>Math.max(num, max)</code>
|
||
(num, min, max) -> <n> <code>Math.min(Math.max(num, min), max)</code>
|
||
num <n> number
|
||
min <n> minimum
|
||
max <n> maximum
|
||
> Ox.limit(1, 2, 3)
|
||
2
|
||
> Ox.limit(4, 2, 3)
|
||
3
|
||
> Ox.limit(2, 1)
|
||
1
|
||
@*/
|
||
Ox.limit = function(/*num[[, min], max]*/) {
|
||
var len = arguments.length,
|
||
num = arguments[0],
|
||
min = len == 3 ? arguments[1] : 0, // fixme: should be -Infinity
|
||
max = arguments[len - 1];
|
||
return Math.min(Math.max(num, min), max);
|
||
};
|
||
|
||
/*@
|
||
Ox.log <f> Returns the logarithm of a given number to a given base
|
||
Strangely missing from <code>Math</code>.
|
||
> Ox.log(100, 10)
|
||
2
|
||
> Ox.log(Math.E)
|
||
1
|
||
@*/
|
||
Ox.log = function(num, base) {
|
||
return Math.log(num) / Math.log(base || Math.E);
|
||
};
|
||
|
||
/*@
|
||
Ox.mod <f> Modulo function
|
||
Unlike <code>-1 % 10</code>, which returns <code>-1</code>,
|
||
<code>Ox.mod(-1, 10)</code> returns <code>9</code>.
|
||
> Ox.mod(11, 10)
|
||
1
|
||
> Ox.mod(-11, 10)
|
||
9
|
||
@*/
|
||
Ox.mod = function(num, by) {
|
||
var mod = num % by;
|
||
return mod >= 0 ? mod : mod + by;
|
||
};
|
||
|
||
/*@
|
||
Ox.rad <f> Takes degrees, returns radians
|
||
Strangely missing from <code>Math</code>.
|
||
> Ox.rad(360)
|
||
2 * Math.PI
|
||
@*/
|
||
Ox.rad = function(deg) {
|
||
return deg * Math.PI / 180;
|
||
};
|
||
|
||
/*@
|
||
Ox.random <f> Returns a random integer
|
||
> [0, 1, 2].indexOf(Ox.random(3)) > -1
|
||
true
|
||
> Ox.random(1, 2) == 1
|
||
true
|
||
@*/
|
||
Ox.random = function() {
|
||
var len = arguments.length,
|
||
min = len == 1 ? 0 : arguments[0],
|
||
max = arguments[len - 1];
|
||
return min + parseInt(Math.random() * (max - min));
|
||
};
|
||
|
||
/*@
|
||
Ox.round <f> Rounds a number with a given number of decimals
|
||
> Ox.round(2 / 3, 6)
|
||
0.666667
|
||
> Ox.round(1 / 2, 3)
|
||
0.5
|
||
> Ox.round(1 / 2)
|
||
1
|
||
@*/
|
||
Ox.round = function(num, dec) {
|
||
var pow = Math.pow(10, dec || 0);
|
||
return Math.round(num * pow) / pow;
|
||
};
|
||
|
||
/*@
|
||
Ox.sinh <f> Hyperbolic sine
|
||
Strangely missing from <code>Math</code>.
|
||
@*/
|
||
Ox.sinh = function(x) {
|
||
// fixme: no test
|
||
return (Math.exp(x) - Math.exp(-x)) / 2;
|
||
};
|
||
|
||
//@ Constants ------------------------------------------------------------------
|
||
|
||
//@ Ox.MAX_LATITUDE <n> Maximum latitude of a Mercator projection
|
||
Ox.MAX_LATITUDE = Ox.deg(Math.atan(Ox.sinh(Math.PI)));
|
||
//@ Ox.MIN_LATITUDE <n> Minimum latitude of a Mercator projection
|
||
Ox.MIN_LATITUDE = -Ox.MAX_LATITUDE;
|
||
|
||
//@ Object ---------------------------------------------------------------------
|
||
|
||
/*@
|
||
Ox.extend <function> Extends an object with one or more other objects
|
||
> Ox.extend({a: 1, b: 1, c: 1}, {b: 2, c: 2}, {c: 3})
|
||
{a: 1, b: 2, c: 3}
|
||
@*/
|
||
Ox.extend = function() {
|
||
var obj = arguments[0];
|
||
Ox.forEach(Array.prototype.slice.call(arguments, 1), function(arg, i) {
|
||
Ox.forEach(arg, function(val, key) {
|
||
obj[key] = val;
|
||
});
|
||
});
|
||
return obj;
|
||
};
|
||
|
||
/*@
|
||
Ox.serialize <f> Parses an object into query parameters
|
||
> Ox.serialize({a: 1, b: 2, c: 3})
|
||
'a=1&b=2&c=3'
|
||
> Ox.serialize({a: 1, b: 2.3, c: [4, 5]})
|
||
'a=1&b=2.3&c=4,5'
|
||
@*/
|
||
Ox.serialize = function(obj) {
|
||
var arr = [];
|
||
Ox.forEach(obj, function(val, key) {
|
||
arr.push(key + '=' + val);
|
||
});
|
||
return arr.join('&');
|
||
};
|
||
|
||
/*@
|
||
Ox.unserialize <f> Parses query parameters into an object
|
||
> Ox.unserialize('a=1&b=2&c=3')
|
||
{a: '1', b: '2', c: '3'}
|
||
> Ox.unserialize('a=1&b=2.3&c=4,5', true)
|
||
{a: 1, b: 2.3, c: [4, 5]}
|
||
@*/
|
||
Ox.unserialize = function(str, toNumber) {
|
||
var obj = {};
|
||
Ox.forEach(str.split('&'), function(val) {
|
||
var arr = val.split('=');
|
||
obj[arr[0]] = !toNumber ? arr[1]
|
||
: arr[1].indexOf(',') == -1 ? +arr[1]
|
||
: arr[1].split(',').map(function(val) {
|
||
return +val;
|
||
});
|
||
});
|
||
return obj;
|
||
};
|
||
|
||
/*
|
||
================================================================================
|
||
RegExp functions
|
||
================================================================================
|
||
*/
|
||
|
||
Ox.regexp = {
|
||
'accents': '¨´`ˆ˜',
|
||
'letters': 'a-z¨´`ˆ˜äåáà âãæçëéèèñïÃìîöóòôõøœßúùûÿ'
|
||
};
|
||
|
||
//@ Requests -------------------------------------------------------------------
|
||
|
||
/*@
|
||
Ox.get <f> Get a remote file
|
||
# fixme: remote? same-origin-policy? jsonp?
|
||
(url, callback) -> <u> undefined
|
||
url <s> Remote URL
|
||
callback <f> Callback function
|
||
data <s> The contents of the remote resource
|
||
@*/
|
||
Ox.get = function(url, callback) {
|
||
var req = new XMLHttpRequest();
|
||
req.open('GET', url, true);
|
||
req.onreadystatechange = function() {
|
||
if (req.readyState == 4) {
|
||
if (req.status == 200) {
|
||
callback(req.responseText);
|
||
} else {
|
||
throw new Error('URL ' + url + ', status ' + req.status);
|
||
}
|
||
}
|
||
};
|
||
req.send();
|
||
};
|
||
|
||
/*@
|
||
Ox.getJSON <f> Get and parse a remote JSON file
|
||
# fixme: remote? same-origin-policy? jsonp?
|
||
(url, callback) -> <u> undefined
|
||
url <s> Remote URL
|
||
callback <f> Callback function
|
||
data <s> The contents of the remote resource
|
||
@*/
|
||
Ox.getJSON = function(url, callback) {
|
||
Ox.get(url, function(data) {
|
||
callback(JSON.parse(data));
|
||
});
|
||
}
|
||
|
||
/*@
|
||
Ox.loadFile <f> Loads a file (image, script or stylesheet)
|
||
(file="script.js", callback) -> <u> undefined
|
||
(file="stylesheet.css", callback) -> <u> undefined
|
||
(file="image.png", callback) -> <e> DOM element
|
||
file <s> Local path or remote URL
|
||
callback <f> Callback function
|
||
@*/
|
||
|
||
Ox.loadFile = (function() {
|
||
// fixme: this doesn't handle errors yet
|
||
// fixme: rename to getFile?
|
||
// fixme: what about array of files?
|
||
var cache = {};
|
||
return function (file, callback) {
|
||
var element,
|
||
request,
|
||
type = file.split('.').pop();
|
||
isImage = type != 'css' && type != 'js';
|
||
if (!cache[file]) {
|
||
if (isImage) {
|
||
element = new Image();
|
||
element.onload = addFileToCache;
|
||
element.src = file;
|
||
} else {
|
||
if (!findFileInHead()) {
|
||
element = document.createElement(
|
||
type == 'css' ? 'link' : 'script'
|
||
);
|
||
element[type == 'css' ? 'href' : 'src'] = file + '?' + Ox.random(1000000);
|
||
element.type = type == 'css' ? 'text/css' : 'text/javascript';
|
||
if (/MSIE/.test(navigator.userAgent)) {
|
||
// fixme: find a way to check if css/js have loaded in msie
|
||
setTimeout(addFileToCache, 2500);
|
||
} else {
|
||
if (type == 'css') {
|
||
element.rel = 'stylesheet';
|
||
waitForCSS();
|
||
} else {
|
||
element.onload = addFileToCache;
|
||
}
|
||
}
|
||
document.getElementsByTagName('head')[0].appendChild(element);
|
||
} else {
|
||
addFileToCache();
|
||
}
|
||
}
|
||
} else {
|
||
callback();
|
||
}
|
||
function addFileToCache() {
|
||
if (isImage) {
|
||
// for an image, save the element itself,
|
||
// so that it remains in the browser cache
|
||
cache['file'] = element;
|
||
callback(element);
|
||
} else {
|
||
cache['file'] = true;
|
||
callback();
|
||
}
|
||
}
|
||
function findFileInHead() {
|
||
return Ox.makeArray(
|
||
document.getElementsByTagName(type == 'css' ? 'link' : 'script')
|
||
).map(function(element) {
|
||
return element[type == 'css' ? 'href' : 'src'] == file;
|
||
}).reduce(function(prev, curr) {
|
||
return prev || curr;
|
||
}, false);
|
||
}
|
||
function waitForCSS() {
|
||
var error = false;
|
||
try {
|
||
element.sheet.cssRule;
|
||
} catch(e) {
|
||
error = true;
|
||
setTimeout(function() {
|
||
waitForCSS();
|
||
}, 25);
|
||
}
|
||
!error && addFileToCache();
|
||
}
|
||
};
|
||
}());
|
||
|
||
//@ String ---------------------------------------------------------------------
|
||
|
||
Ox.basename = function(str) {
|
||
/*
|
||
fixme: deprecate
|
||
>>> Ox.basename("foo/bar/foo.bar")
|
||
"foo.bar"
|
||
>>> Ox.basename("foo.bar")
|
||
"foo.bar"
|
||
*/
|
||
return str.replace(/^.*[\/\\]/g, '');
|
||
};
|
||
|
||
/*@
|
||
Ox.char <f> Alias for String.fromCharCode
|
||
@*/
|
||
// fixme: add some mapping? like Ox.char(9, 13) or Ox.char([9, 13])?
|
||
Ox.char = String.fromCharCode;
|
||
|
||
/*@
|
||
Ox.clean <f> Remove leading, trailing and double whitespace from a string
|
||
> Ox.clean("foo bar")
|
||
"foo bar"
|
||
> Ox.clean(" foo bar ")
|
||
"foo bar"
|
||
> Ox.clean(" foo \n bar ")
|
||
"foo\nbar"
|
||
@*/
|
||
Ox.clean = function(str) {
|
||
return Ox.map(str.split('\n'), function(str) {
|
||
return Ox.trim(str.replace(/\s+/g, ' '));
|
||
}).join('\n');
|
||
};
|
||
|
||
/*@
|
||
Ox.endsWith <f> Checks if a string ends with a given substring
|
||
If the substring is a string literal (and not a variable),
|
||
<code>/sub$/.test(str)</code> or <code>!!/sub$/(str)</code>
|
||
is shorter than <code>Ox.ends(str, sub)</code>.
|
||
> Ox.endsWith('foobar', 'bar')
|
||
true
|
||
@*/
|
||
Ox.endsWith = function(str, sub) {
|
||
// fixme: rename to ends
|
||
return str.substr(str.length - sub.length) == sub;
|
||
};
|
||
|
||
/*@
|
||
Ox.highlight <f> Highlight matches in a string
|
||
> Ox.highlight('foobar', 'foo', 'match')
|
||
'<span class="match">foo</span>bar'
|
||
@*/
|
||
// fixme: with regexp, special chars would have to be escaped
|
||
Ox.highlight = function(txt, str, classname) {
|
||
return str && str.length ? txt.replace(
|
||
new RegExp('(' + str + ')', 'ig'),
|
||
'<span class="' + classname + '">$1</span>'
|
||
) : txt;
|
||
};
|
||
|
||
/*@
|
||
Ox.isValidEmail <f> Tests if a string is a valid e-mail address
|
||
(str) -> <b> True if the string is a valid e-mail address
|
||
str <s> Any string
|
||
> Ox.isValidEmail("foo@bar.com")
|
||
true
|
||
> Ox.isValidEmail("foo.bar@foobar.co.uk")
|
||
true
|
||
> Ox.isValidEmail("foo@bar")
|
||
false
|
||
> Ox.isValidEmail("foo@bar..com")
|
||
false
|
||
@*/
|
||
// fixme: rename to isEmail
|
||
Ox.isValidEmail = function(str) {
|
||
return !!/^[0-9A-Z\.\+\-_]+@(?:[0-9A-Z\-]+\.)+[A-Z]{2,6}$/i(str);
|
||
}
|
||
|
||
/*@
|
||
Ox.pad <f> Pad a string to a given length
|
||
> Ox.pad(1, 2)
|
||
"01"
|
||
> Ox.pad("abc", -6, ".")
|
||
"abc..."
|
||
> Ox.pad("foobar", -3, ".")
|
||
"foo"
|
||
> Ox.pad("abc", -6, "123456")
|
||
"abc123"
|
||
> Ox.pad("abc", 6, "123456")
|
||
"456abc"
|
||
@*/
|
||
Ox.pad = function(str, len, pad) {
|
||
// fixme: slighly obscure signature
|
||
// fixme: weird for negative numbers
|
||
var pos = len / (len = Math.abs(len));
|
||
str = str.toString().substr(0, len);
|
||
pad = Ox.repeat(pad || '0', len - str.length);
|
||
str = pos == 1 ? pad + str : str + pad;
|
||
str = pos == 1 ?
|
||
str.substr(str.length - len, str.length) :
|
||
str.substr(0, len);
|
||
return str;
|
||
};
|
||
|
||
/*@
|
||
Ox.parsePath <f> Returns the components of a path
|
||
(str) -> <o> Path
|
||
extension <s> File extension
|
||
filename <s> Filename
|
||
pathname <s> Pathname
|
||
> Ox.parsePath('/foo/bar/foo.bar')
|
||
{extension: 'bar', filename: 'foo.bar', pathname: '/foo/bar/'}
|
||
> Ox.parsePath('foo/')
|
||
{extension: '', filename: '', pathname: 'foo/'}
|
||
> Ox.parsePath('foo')
|
||
{extension: '', filename: 'foo', pathname: ''}
|
||
> Ox.parsePath('.foo')
|
||
{extension: '', filename: '.foo', pathname: ''}
|
||
@*/
|
||
Ox.parsePath = function(str) {
|
||
var matches = /^(.+\/)?(.+?(\..+)?)?$/.exec(str);
|
||
return {
|
||
pathname: matches[1] || '',
|
||
filename: matches[2] || '',
|
||
extension: matches[3] ? matches[3].substr(1) : ''
|
||
};
|
||
}
|
||
|
||
/*@
|
||
Ox.parseSRT <f> Parses an srt subtitle file
|
||
(str) -> <o> Parsed subtitles
|
||
in <n> In point (sec)
|
||
out <n> Out point (sec)
|
||
text <s> Text
|
||
str <s> Contents of an srt subtitle file
|
||
> Ox.parseSRT('1\n01:02:00,000 --> 01:02:03,400\nHello World')
|
||
[{'in': 3720, out: 3723.4, text: 'Hello World'}]
|
||
@*/
|
||
Ox.parseSRT = function(str, fps) {
|
||
return str.replace(/\r\n/g, '\n').replace(/\n+$/, '').split('\n\n')
|
||
.map(function(block) {
|
||
var lines = block.split('\n'), points;
|
||
lines.shift();
|
||
points = lines.shift().split(' --> ').map(function(point) {
|
||
return point.replace(',', ':').split(':')
|
||
.reduce(function(prev, curr, i) {
|
||
return prev + parseInt(curr, 10) *
|
||
[3600, 60, 1, 0.001][i];
|
||
}, 0);
|
||
});
|
||
if (fps) {
|
||
points = points.map(function(point) {
|
||
return Math.round(point * fps) / fps;
|
||
});
|
||
}
|
||
return {
|
||
'in': points[0],
|
||
out: points[1],
|
||
text: lines.join('\n')
|
||
};
|
||
});
|
||
};
|
||
|
||
/*@
|
||
Ox.repeat <f> Repeat a value multiple times
|
||
Works for arrays, numbers and strings
|
||
> Ox.repeat(1, 3)
|
||
"111"
|
||
> Ox.repeat("foo", 3)
|
||
"foofoofoo"
|
||
> Ox.repeat([1, 2], 3)
|
||
[1, 2, 1, 2, 1, 2]
|
||
@*/
|
||
Ox.repeat = function(val, num) {
|
||
var ret;
|
||
if (Ox.isArray(val)) {
|
||
ret = [];
|
||
num >= 1 && Ox.loop(num, function() {
|
||
ret = Ox.merge(ret, val);
|
||
});
|
||
} else {
|
||
ret = num >= 1 ? new Array(num + 1).join(val.toString()) : '';
|
||
}
|
||
return ret;
|
||
};
|
||
|
||
Ox.reverse = function(str) {
|
||
/*
|
||
Ox.reverse("foo")
|
||
oof
|
||
*/
|
||
return str.toString().split('').reverse().join('');
|
||
};
|
||
|
||
/*@
|
||
Ox.startsWith <f> Checks if a string starts with a given substring
|
||
If the substring is a string literal (and not a variable),
|
||
<code>/^sub/.test(str)</code> or <code>!!/^sub/(str)</code>
|
||
is shorter than <code>Ox.starts(str, sub)</code>.
|
||
> Ox.startsWith('foobar', 'foo')
|
||
true
|
||
@*/
|
||
Ox.startsWith = function(str, sub) {
|
||
// fixme: rename to starts
|
||
return str.substr(0, sub.length) == sub;
|
||
};
|
||
|
||
/*@
|
||
Ox.stripTags <f> Strips HTML tags from a string
|
||
> Ox.stripTags('f<span>o</span>o')
|
||
'foo'
|
||
@*/
|
||
Ox.stripTags = function(str) {
|
||
return str.replace(/<.*?>/g, '');
|
||
};
|
||
|
||
/*@
|
||
Ox.toCamelCase <f> Takes a string with '-', '/' or '_', returns a camelCase string
|
||
> Ox.toCamelCase('foo-bar-baz')
|
||
'fooBarBaz'
|
||
> Ox.toCamelCase('foo/bar/baz')
|
||
'fooBarBaz'
|
||
> Ox.toCamelCase('foo_bar_baz')
|
||
'fooBarBaz'
|
||
@*/
|
||
Ox.toCamelCase = function(str) {
|
||
return str.replace(/[\-\/_][a-z]/g, function(str) {
|
||
return str[1].toUpperCase();
|
||
});
|
||
};
|
||
|
||
/*@
|
||
Ox.toDashes <f> Takes a camelCase string, returns a string with dashes
|
||
> Ox.toDashes('fooBarBaz')
|
||
'foo-bar-baz'
|
||
@*/
|
||
Ox.toDashes = function(str) {
|
||
return str.replace(/[A-Z]/g, function(str) {
|
||
return '-' + str.toLowerCase();
|
||
});
|
||
};
|
||
|
||
/*@
|
||
Ox.toSlashes <f> Takes a camelCase string, returns a string with slashes
|
||
> Ox.toSlashes('fooBarBaz')
|
||
'foo/bar/baz'
|
||
@*/
|
||
Ox.toSlashes = function(str) {
|
||
/*
|
||
*/
|
||
return str.replace(/[A-Z]/g, function(str) {
|
||
return '/' + str.toLowerCase();
|
||
});
|
||
};
|
||
|
||
/*@
|
||
Ox.toTitleCase <f> Returns a string with capitalized words
|
||
> Ox.toTitleCase('foo')
|
||
'Foo'
|
||
> Ox.toTitleCase('Apple releases iPhone, IBM stock plummets')
|
||
'Apple Releases iPhone, IBM Stock Plummets'
|
||
@*/
|
||
Ox.toTitleCase = function(str) {
|
||
return Ox.map(str.split(' '), function(val) {
|
||
var sub = val.substr(1),
|
||
low = sub.toLowerCase();
|
||
if (sub == low) {
|
||
val = val.substr(0, 1).toUpperCase() + low;
|
||
}
|
||
return val;
|
||
}).join(' ');
|
||
};
|
||
|
||
/*@
|
||
Ox.toUnderscores <f> Takes a camelCase string, returns string with underscores
|
||
> Ox.toUnderscores('fooBarBaz')
|
||
'foo_bar_baz'
|
||
@*/
|
||
Ox.toUnderscores = function(str) {
|
||
return str.replace(/[A-Z]/g, function(str) {
|
||
return '_' + str.toLowerCase();
|
||
});
|
||
};
|
||
|
||
Ox.trim = function(str) { // is in jQuery, and in JavaScript itself
|
||
/*
|
||
Ox.trim(" foo ")
|
||
"foo"
|
||
*/
|
||
return str.replace(/^\s+|\s+$/g, '');
|
||
};
|
||
|
||
/*@
|
||
Ox.truncate <f> Truncate a string to a given length
|
||
(string, length) <s> Truncated string
|
||
(string, length, position) -> <s> Truncated string
|
||
(string, length, placeholder) -> <s> Truncated string
|
||
(string, length, position, placeholder) -> <s> Truncated string
|
||
> Ox.truncate('anticonstitutionellement', 16)
|
||
'anticonstitut...'
|
||
> Ox.truncate('anticonstitutionellement', 16, '...', 'left')
|
||
'...utionellement'
|
||
> Ox.truncate('anticonstitutionellement', 16, '>')
|
||
'anticonstitutio>'
|
||
> Ox.truncate('anticonstitutionellement', 16, '...', 'center')
|
||
'anticon...lement'
|
||
@*/
|
||
Ox.truncate = function(str, len, pad, pos) {
|
||
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 <f> Splits a string into words, removing punctuation
|
||
(string) -> <[s]> Array of words
|
||
string <s> Any string
|
||
> Ox.words('Let\'s "walk" a tree-like key/value store--okay?')
|
||
["let's", "walk", "a", "tree-like", "key", "value", "store", "okay"]
|
||
@*/
|
||
Ox.words = function(str) {
|
||
var arr = str.toLowerCase().split(/\b/),
|
||
chr = "-'",
|
||
len = arr.length,
|
||
startsWithWord = /\w/.test(arr[0]);
|
||
arr.forEach(function(v, i) {
|
||
// find single occurrences of "-" or "-"
|
||
// that are not at the beginning or end of the string
|
||
// 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 <f> Wrap a string at word boundaries
|
||
> 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'
|
||
@*/
|
||
Ox.wordwrap = function(str, len, sep, bal, spa) {
|
||
// fixme: bad API, sep/bal/spa should be in options object
|
||
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(Ox.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 = [''];
|
||
Ox.forEach(words, function(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 = Ox.map(lines, function(line) {
|
||
return Ox.trim(line);
|
||
});
|
||
}
|
||
return Ox.trim(lines.join(sep));
|
||
};
|
||
|
||
//@ Type -----------------------------------------------------------------------
|
||
|
||
/*@
|
||
Ox.isArguments <f> Tests if a value is an arguments "array"
|
||
(value) -> <b> True if the value is an arguments "array"
|
||
value <*> Any value
|
||
> Ox.isArguments((function() { return arguments; }()))
|
||
true
|
||
@*/
|
||
Ox.isArguments = /MSIE/.test(navigator.userAgent) ? function(val) {
|
||
return !!(val && Object.hasOwnProperty.call(val, 'callee'));
|
||
} : function(val) {
|
||
return !!(val && val.toString() == '[object Arguments]');
|
||
}
|
||
|
||
/*@
|
||
Ox.isArray <f> Tests if a value is an array
|
||
(value) -> <b> True if the value is a date
|
||
value <*> Any value
|
||
> Ox.isArray([])
|
||
true
|
||
> Ox.isArray((function() { return arguments; }()))
|
||
false
|
||
> Ox.isArray({0: 0, length: 1})
|
||
false
|
||
@*/
|
||
Ox.isArray = function(val) {
|
||
return val instanceof Array;
|
||
}
|
||
|
||
/*@
|
||
Ox.isBoolean <f> Tests if a value is boolean
|
||
(value) -> <b> True if the value is boolean
|
||
value <*> Any value
|
||
> Ox.isBoolean(false)
|
||
true
|
||
@*/
|
||
Ox.isBoolean = function(val) {
|
||
return typeof val == 'boolean';
|
||
};
|
||
|
||
/*@
|
||
Ox.isDate <f> Tests if a value is a date
|
||
(value) -> <b> True if the value is a date
|
||
value <*> Any value
|
||
> Ox.isDate(new Date())
|
||
true
|
||
@*/
|
||
Ox.isDate = function(val) {
|
||
return val instanceof Date;
|
||
};
|
||
|
||
/*@
|
||
Ox.isElement <f> Tests if a value is a DOM element
|
||
(value) -> <b> True if the value is a DOM element
|
||
value <*> Any value
|
||
> Ox.isElement(document.createElement())
|
||
true
|
||
@*/
|
||
Ox.isElement = function(val) {
|
||
return !!(val && val.nodeType == 1);
|
||
};
|
||
|
||
/*@
|
||
Ox.isEqual <function> Returns true if two values are equal
|
||
> Ox.isEqual((function() { return arguments; }()), (function() { return arguments; }()))
|
||
true
|
||
> Ox.isEqual([1, 2, 3], [1, 2, 3])
|
||
true
|
||
> Ox.isEqual([1, 2, 3], [3, 2, 1])
|
||
false
|
||
> Ox.isEqual(false, false)
|
||
true
|
||
> Ox.isEqual(new Date(0), new Date(0))
|
||
true
|
||
> Ox.isEqual(new Date(0), new Date(1))
|
||
false
|
||
> Ox.isEqual(document.createElement('a'), document.createElement('a'))
|
||
true
|
||
> Ox.isEqual(document.createElement('a'), document.createElement('b'))
|
||
false
|
||
> Ox.isEqual(function(a) { return a; }, function(a) { return a; })
|
||
true
|
||
> Ox.isEqual(function(a) { return a; }, function(b) { return b; })
|
||
false
|
||
> Ox.isEqual(Infinity, Infinity)
|
||
true
|
||
> Ox.isEqual(-Infinity, Infinity)
|
||
false
|
||
> Ox.isEqual(NaN, NaN)
|
||
false
|
||
> Ox.isEqual(0, 0)
|
||
true
|
||
> Ox.isEqual({}, {})
|
||
true
|
||
> Ox.isEqual({a: 1, b: 2, c: 3}, {c: 3, b: 2, a: 1})
|
||
true
|
||
> Ox.isEqual({a: 1, b: [2, 3], c: {d: '4'}}, {a: 1, b: [2, 3], c: {d: '4'}})
|
||
true
|
||
> Ox.isEqual(/ /, / /)
|
||
true
|
||
> Ox.isEqual(/ /g, / /i)
|
||
false
|
||
> Ox.isEqual('', '')
|
||
true
|
||
> Ox.isEqual(void 0, void 0)
|
||
true
|
||
@*/
|
||
Ox.isEqual = function(a, b) {
|
||
var isEqual = false, type = Ox.typeOf(a);
|
||
if (a === b) {
|
||
isEqual = true;
|
||
} else if (type == Ox.typeOf(b)) {
|
||
if (a == b) {
|
||
isEqual = true;
|
||
} else if (type == 'date') {
|
||
isEqual = a.getTime() == b.getTime();
|
||
/* toString doesn't do it
|
||
} else if (['element', 'function'].indexOf(type) > -1) {
|
||
isEqual = a.toString() == b.toString();
|
||
*/
|
||
} else if (type == 'regexp') {
|
||
isEqual = a.global == b.global &&
|
||
a.ignore == b.ignore &&
|
||
a.multiline == b.multiline &&
|
||
a.source == b.source;
|
||
} else if (
|
||
['arguments', 'array', 'object'].indexOf(type) > -1 &&
|
||
Ox.len(a) == Ox.len(b)
|
||
) {
|
||
isEqual = true;
|
||
Ox.forEach(a, function(v, k) {
|
||
isEqual = Ox.isEqual(v, b[k]);
|
||
return isEqual;
|
||
});
|
||
}
|
||
}
|
||
return isEqual;
|
||
};
|
||
|
||
/*@
|
||
Ox.isFunction <f> Tests if a value is a function
|
||
(value) -> <b> True if the value is a function
|
||
value <*> Any value
|
||
> Ox.isFunction(function() {})
|
||
true
|
||
> Ox.isFunction(/ /)
|
||
false
|
||
@*/
|
||
Ox.isFunction = function(val) { // is in jQuery
|
||
return typeof val == 'function' && !Ox.isRegExp(val);
|
||
};
|
||
|
||
/*@
|
||
Ox.isInfinity <f> Tests if a value is infinite
|
||
(value) -> <b> True if the value is infinite
|
||
value <*> Any value
|
||
> Ox.isInfinity(Infinity)
|
||
true
|
||
> Ox.isInfinity(-Infinity)
|
||
true
|
||
> Ox.isInfinity(NaN)
|
||
false
|
||
@*/
|
||
Ox.isInfinity = function(val) {
|
||
return typeof val == 'number' && !isFinite(val) && !Ox.isNaN(val);
|
||
};
|
||
|
||
/*@
|
||
Ox.isNaN <f> Tests if a value is "Not a Number"
|
||
(value) -> <b> True if the value is "Not a Number"
|
||
value <*> Any value
|
||
> Ox.isNaN(NaN)
|
||
true
|
||
@*/
|
||
|
||
Ox.isNaN = function(val) {
|
||
return val !== val;
|
||
}
|
||
|
||
/*@
|
||
Ox.isNull <f> Tests if a value is <code>null</code>
|
||
(value) -> <b> True if the value is <code>null</null>
|
||
value <*> Any value
|
||
> Ox.isNull(null)
|
||
true
|
||
@*/
|
||
Ox.isNull = function(val) {
|
||
return val === null;
|
||
};
|
||
|
||
/*@
|
||
Ox.isNumber <f> Tests if a value is a number
|
||
(value) -> <b> True if the value is a number
|
||
value <*> Any value
|
||
> Ox.isNumber(0)
|
||
true
|
||
> Ox.isNumber(Infinity)
|
||
false
|
||
> Ox.isNumber(-Infinity)
|
||
false
|
||
> Ox.isNumber(NaN)
|
||
false
|
||
@*/
|
||
|
||
Ox.isNumber = function(val) {
|
||
return typeof val == 'number' && isFinite(val);
|
||
};
|
||
|
||
/*@
|
||
Ox.isObject <f> Tests if a value is a an object
|
||
(value) -> <b> True if the value is an object
|
||
value <*> Any value
|
||
> Ox.isObject({})
|
||
true
|
||
> Ox.isObject([])
|
||
false
|
||
> Ox.isObject(new Date())
|
||
false
|
||
> Ox.isObject(null)
|
||
false
|
||
@*/
|
||
|
||
Ox.isObject = function(val) {
|
||
return typeof val == 'object' && !Ox.isArguments(val)
|
||
&& !Ox.isArray(val) && !Ox.isDate(val) && !Ox.isNull(val);
|
||
};
|
||
|
||
/*@
|
||
Ox.isRegExp <f> Tests if a value is a regular expression
|
||
(value) -> <b> True if the value is a regular expression
|
||
value <*> Any value
|
||
> Ox.isRegExp(/ /)
|
||
true
|
||
@*/
|
||
|
||
Ox.isRegExp = function(val) {
|
||
return val instanceof RegExp;
|
||
};
|
||
|
||
/*@
|
||
Ox.isString <f> Tests if a value is a string
|
||
(value) -> <b> True if the value is a string
|
||
value <*> Any value
|
||
> Ox.isString('')
|
||
true
|
||
@*/
|
||
|
||
Ox.isString = function(val) {
|
||
return typeof val == 'string';
|
||
};
|
||
|
||
/*@
|
||
Ox.isUndefined <f> Tests if a value is undefined
|
||
(value) -> <b> True if the value is undefined
|
||
value <*> Any value
|
||
> Ox.isUndefined()
|
||
true
|
||
@*/
|
||
|
||
Ox.isUndefined = function(val) {
|
||
// fixme: val === void 0 is also nice
|
||
return typeof val == 'undefined';
|
||
};
|
||
|
||
/*@
|
||
Ox.typeOf <f> Returns the type of a value
|
||
(value) -> <s> type
|
||
value <*> Any value
|
||
# Examples
|
||
> (function() { return Ox.typeOf(arguments); }())
|
||
"arguments"
|
||
> Ox.typeOf([])
|
||
"array"
|
||
> Ox.typeOf(false)
|
||
"boolean"
|
||
> Ox.typeOf(new Date())
|
||
"date"
|
||
> Ox.typeOf(document.createElement())
|
||
"element"
|
||
> Ox.typeOf(function() {})
|
||
"function"
|
||
> Ox.typeOf(Infinity)
|
||
"infinity"
|
||
> Ox.typeOf(NaN)
|
||
"nan"
|
||
> Ox.typeOf(null)
|
||
"null"
|
||
> Ox.typeOf(0)
|
||
"number"
|
||
> Ox.typeOf({})
|
||
"object"
|
||
> Ox.typeOf(/ /)
|
||
"regexp"
|
||
> Ox.typeOf('')
|
||
"string"
|
||
> Ox.typeOf()
|
||
"undefined"
|
||
@*/
|
||
|
||
Ox.typeOf = function(val) {
|
||
var ret;
|
||
Ox.forEach(Ox.TYPES, function(type) {
|
||
if (Ox['is' + type](val)) {
|
||
ret = type.toLowerCase();
|
||
return false;
|
||
}
|
||
});
|
||
return ret;
|
||
};
|