// vim: et:ts=4:sw=4:sts=4:ft=js // OxJS (c) 2007-2011 Ox2620, dual-licensed (GPL/MIT), see oxjs.org for details // 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 /*@ Ox The Ox object See Ox.wrap for details. (value) -> wrapped value value <*> Any value @*/ Ox = function(val) { return Ox.wrap(val); }; /* ================================================================================ Constants ================================================================================ */ //@ Ox.AMPM <[str]> ['AM', 'PM'] Ox.AMPM = ['AM', 'PM']; //@ Ox.DURATIONS <[str]> ['year', 'month', 'day', 'minute', 'second'] Ox.DURATIONS = ['year', 'month', 'day', 'minute', 'second']; //@ Ox.EARTH_RADIUS Radius of the earth in meters // see http://en.wikipedia.org/wiki/WGS-84 Ox.EARTH_RADIUS = 6378137; //@ Ox.EARTH_CIRCUMFERENCE Circumference of the earth in meters Ox.EARTH_CIRCUMFERENCE = Ox.EARTH_RADIUS * 2 * Math.PI; //@ Ox.HTML_ENTITIES HTML entities for ... (FIXME) Ox.HTML_ENTITIES = { '"': '"', '&': '&', "'": ''', '<': '<', '>': '>' }; //@ Ox.KEYS 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 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 Path of Ox.js Ox.PATH = Array.prototype.slice.apply( document.getElementsByTagName('script') ).filter(function(element) { return /Ox\.js$/.test(element.src); })[0].src.replace('Ox.js', ''); //@ Ox.PREFIXES <[str]> ['K', 'M', 'G', 'T', 'P'] Ox.PREFIXES = ['K', 'M', 'G', 'T', 'P']; //@ Ox.SYMBOLS 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 Ox.type() Ox.TYPES = [ 'Arguments', 'Array', 'Boolean', 'Date', 'Element', 'Function', 'Infinity', 'NaN', 'Null', 'Number', 'Object', 'RegExp', 'String', 'Undefined' ]; //@ Ox.VERSION 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); }); /* ================================================================================ Core functions ================================================================================ */ 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 = function(url, callback) { Ox.get(url, function(data) { callback(JSON.parse(data)); }); } /*@ Ox.getset Generic getter and setter function See examples for details. # Usage -------------------------------------------------------------------- Ox.getset(options, args=[]) -> all options Ox.getset(options, args=[key]) -> <*> options[key] Ox.getset(options, args=[key, value], callback, context) -> 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) -> context sets multiple options and calls fn(key, value) for every key/value pair that was added or modified # Arguments ---------------------------------------------------------------- options Options object (key/value pairs) args The arguments "array" of the caller function callback Callback function The callback is called for every key/value pair that was added or modified. key Key value <*> Value context The parent object of the caller function (for chaining) # Examples ----------------------------------------------------------------- > 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.load Loads a module (module, callback) -> undefined (module, options, callback) -> undefined module Module name options Module options callback Callback function success If true, the module has been loaded successfully @*/ Ox.load = function(module, options, callback) { // fixme: no way to load multiple modules // problem: where do multiple options go? // [{name: "", options: {}}, {name: "", options: {}}] isn't pretty callback = arguments[arguments.length - 1]; options = arguments.length == 3 ? arguments[1] : {}; Ox.loadFile(Ox.PATH + 'Ox.' + module + '/Ox.' + module + '.js', function() { Ox.load[module](options, callback); }); }; /*@ Ox.loadFile Loads a file (image, script or stylesheet) (file="script.js", callback) -> undefined (file="stylesheet.css", callback) -> undefined (file="image.png", callback) -> DOM element file Local path or remote URL callback Callback function @*/ Ox.loadFile = (function() { // fixme: this doesn't handle errors yet 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; element.type = type == 'css' ? 'text/css' : 'text/javascript'; if (type == 'css') { element.rel = 'stylesheet'; waitForCSS(); } else { element.onload = addFileToCache; } document.head.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 Array.prototype.slice.apply( 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(); } }; }()); /*@ Ox.print Prints its arguments to the console (arg, ...) -> String The string contains the timestamp, the name of the caller function, and any arguments, separated by spaces arg <*> any value > Ox.print("foo").substr(-3) "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 Returns a unique id () -> Unique id > Ox.uid() != Ox.uid() true @*/ Ox.uid = (function() { var uid = 0; return function() { return uid++; }; }()); /*@ Ox.wrap Wraps a value so that one can directly call any Ox function on it Additionally, chain() allows for chaining, and value() returns the original value. See examples for details. (value) -> wrapped value chain wrap the return value to allow chaining value unwrap the value wrapped by 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) { 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 and Object functions ================================================================================ */ /*@ Ox.avg returns the average of an array's values, or an object's properties (collection) -> average collection <[n]|o> An array or object with numerical values > Ox.avg([-1, 0, 1]) 0 > Ox.avg({a: 1, b: 2, c: 3}) 2 @*/ Ox.avg = function(obj) { return Ox.sum(obj) / Ox.len(obj); }; Ox.clone = function(obj) { /*** returns a copy of an array or object >>> (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' ***/ return Ox.isArray(obj) ? obj.slice() : Ox.extend({}, obj); }; Ox.compact = function(arr) { /*** returns an array without null or undefined values >>> Ox.compact([null,,1,,2,,3]) [1, 2, 3] ***/ return Ox.map(arr, function(val) { return Ox.isUndefined(val) ? null : val; }); }; Ox.count = function(arr) { /*** >>> Ox.count(['foo', 'bar', 'foo']).foo 2 ***/ var obj = {}; arr.forEach(function(v) { obj[v] = (obj[v] || 0) + 1; }); return obj; }; Ox.each = function(obj, fn) { // fixme: deprecate! /* Ox.each() works for arrays, objects and strings, like $.each(), unlike [].forEach() >>> Ox.each([0, 1, 2], function(i, v) {}) [0, 1, 2] >>> Ox.each({a: 1, b: 2, c: 3}, function(k, v) {}).a 1 >>> Ox.each('foo', function(i, v) {}) 'foo' */ var i, isArray = Ox.isArray(obj); for (i in obj) { i = isArray ? parseInt(i) : i; // fixme: should be (v, k), like [].forEach() if (fn(i, obj[i]) === false) { break; } } return obj; }; Ox.every = function(obj, fn) { /* Ox.every() works for arrays, objects and strings, unlike [].every() >>> Ox.every([0, 1, 2], function(v, i) { return i == v; }) true >>> Ox.every({a: 1, b: 2, c: 3}, function(v) { return v == 1; }) false >>> Ox.every("foo", function(v) { return v == 'f'; }) false >>> Ox.every([true, true, true]) true */ return Ox.filter(Ox.values(obj), fn || function(v) { return v; }).length == Ox.len(obj); }; Ox.extend = function() { /* >>> Ox.extend({a: 1, b: 1, c: 1}, {b: 2, c: 2}, {c: 3}).c {"a":1,"b":2,"c":3} */ 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.filter = function(obj, fn) { /*** Ox.filter works for arrays, objects and strings, unlike [].filter() >>> 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" ***/ var type = Ox.typeOf(obj), ret = type == 'array' ? [] : type == 'object' ? {} : ''; Ox.forEach(obj, 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 = function(arr, str) { /* returns an array with two arrays as elements: an array of elements of arr that begin with str, and an array of elements of arr that contain, but do not begin with str >>> Ox.find(["foo", "bar", "foobar", "barfoo"], "foo") [["foo", "foobar"], ["barfoo"]] */ var arrLowerCase = arr.map(function(v) { return v.toLowerCase(); }), ret = [[], []]; str && arrLowerCase.forEach(function(v, i) { var index = v.indexOf(str.toLowerCase()); index > -1 && ret[index == 0 ? 0 : 1].push(arr[i]); }); return ret; }; Ox.flatten = function(arr) { /* >>> Ox.flatten([1, [2, [3], 4], 5]) [1, 2, 3, 4, 5] */ var ret = []; arr.forEach(function(v) { if (Ox.isArray(v)) { Ox.flatten(v).forEach(function(v) { ret.push(v); }); } else { ret.push(v); } }); return ret; } /*@ Ox.forEach forEach loop Ox.forEach() loops over arrays, objects and strings. Returning false from the iterator function acts like a break statement (unlike [].forEach(), like $.each()). The arguments of the iterator function are (value, key) (like [].forEach(), unlike $.each()). (collection, callback) The collection collection An array, object or string callback Callback function value <*> Value key Key @*/ /* > 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; }) > Ox.test.string "012abcfoo" */ Ox.forEach = function(obj, fn) { var isObject = Ox.isObject(obj), key; for (key in obj) { key = isObject ? key : parseInt(key); if (/*hasOwnProperty.call(obj, key) && */fn(obj[key], key) === false) { break; } } return obj; }; Ox.getObjectById = function(arr, id) { /*** >>> Ox.getObjectById([{id: "foo", title: "Foo"}, {id: "bar", title: "Bar"}], "foo").title "Foo" ***/ var ret = null; Ox.forEach(arr, function(v) { if (v.id == id) { ret = v; return false; } }); return ret; }; Ox.getPositionById = function(arr, id) { /*** >>> Ox.getPositionById([{id: "foo", title: "Foo"}, {id: "bar", title: "Bar"}], "bar") 1 ***/ var ret = -1; Ox.forEach(arr, function(v, i) { if (v.id == id) { ret = i; return false; } }); return ret; }; Ox.isEmpty = function(val) { return Ox.len(val) == 0; }; Ox.isEqual = function(obj0, obj1) { /* >>> Ox.isEqual(false, false) true >>> Ox.isEqual(0, 0) true >>> Ox.isEqual(NaN, NaN) false >>> Ox.isEqual('', '') true >>> Ox.isEqual([1, 2, 3], [1, 2, 3]) true >>> Ox.isEqual({a: 1, b: [2, 3], c: {d: '4'}}, {a: 1, b: [2, 3], c: {d: '4'}}) true >>> Ox.isEqual({a: 1, b: 2, c: 3}, {c: 3, b: 2, a: 1}) true // FIXME: is false >>> Ox.isEqual(function(arg) { return arg; }, function(arg) { return arg; }) true >>> Ox.isEqual(function(foo) { return foo; }, function(bar) { return bar; }) false */ var ret = false; if (obj0 === obj1) { ret = true; } else if (typeof(obj0) == typeof(obj1)) { if (obj0 == obj1) { ret = true; } else if (Ox.isArray(obj0) && obj0.length == obj1.length) { ret = true; Ox.forEach(obj0, function(v, i) { ret = Ox.isEqual(v, obj1[i]); return ret; }); } else if (Ox.isDate(obj0)) { ret = obj0.getTime() == obj1.getTime(); } else if (Ox.isObject(obj0)) { ret = Ox.isEqual(Ox.keys(obj0), Ox.keys(obj1)) && Ox.isEqual(Ox.values(obj0), Ox.values(obj1)); } else if (Ox.isFunction(obj0)) { ret = obj0.toString() == obj1.toString(); } } return ret; }; Ox.keys = function(obj) { /* works for arrays, objects and strings, unlike Object.keys() >>> Ox.keys([1, 2, 3]) [0, 1, 2] >>> Ox.keys({a: 1, b: 2, c: 3}) ["a", "b", "c"] >>> Ox.keys('abc') [0, 1, 2] >>> Ox.keys([,]) [0] */ var keys = []; Ox.forEach(obj, function(v, k) { keys.push(k); }); return keys.sort(); }; Ox.last = function(arr, val) { /*** >>> Ox.last([1, 2, 3]) 3 >>> Ox.last([1, 2, 3], 4) [1, 2, 4] ***/ var ret; if (arguments.length == 1) { ret = arr[arr.length - 1]; } else { arr[arr.length - 1] = val; ret = arr; } return ret; }; Ox.len = function(obj) { /* >>> Ox.len([1, 2, 3]) 3 >>> Ox.len({a: 1, b: 2, c: 3}) 3 >>> Ox.len('abc') 3 >>> Ox.len(function(x, y) { return x + y; }) 2 >>> Ox.len([,]) 1 */ return Ox.isObject(obj) ? Ox.values(obj).length : obj.length; }; /*@ Ox.loop For-loop, functional-style Returning false from the iterater function acts like a break statement. Unlike a for loop, Ox.loop doesn't leak its counter variable to the outer scope, but returns it. (stop, callback) -> Next value equivalent to for (var i = 0; i < stop; i++) (start, stop, callback) -> Next value equivalent to for (var i = start; i < stop; i++) or, if start is larger than stop, for (var i = start; i > stop; i--) (start, stop, step) -> Next value equivalent to for (var i = start; i < stop; i += step) start Start value stop Stop value (exclusive) step Step value callback Iterator function i Counter value > Ox.loop(0, 3, 2, function() {}) 4 > Ox.loop(10, function(i) { return i != 4; }) 4 @*/ Ox.loop = function() { var length = arguments.length, start = length > 2 ? arguments[0] : 0, stop = arguments[length > 2 ? 1 : 0], step = length == 4 ? arguments[2] : (start <= stop ? 1 : -1), callback = arguments[length - 1], i; for (i = start; step > 0 ? i < stop : i > stop; i += step) { if (callback(i) === false) { break; }; } return i; }; /*@ Ox.makeArray Takes an array-like object and returns a true array Alias for Array.prototype.slice.call (value) -> 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 = function(obj) { return Array.prototype.slice.call(obj); } Ox.makeObject = function(obj) { /* >>> (function() { return Ox.makeObject(arguments); }({foo: 'bar'})).foo 'bar' >>> (function() { return Ox.makeObject(arguments); }('foo', 'bar')).foo 'bar' >>> (function() { return Ox.makeObject(arguments); }('foo')).foo undefined >>> (function() { return Ox.makeObject(arguments); }()).foo undefined */ 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 = function(obj, fn) { /* Ox.map() works for arrays, objects and strings, unlike [].map() >>> Ox.map([1, 1, 1], function(v, i) { return v == i; }) [false, true, false] >>> Ox.map({a: 'a', b: 'a', c: 'a'}, function(v, k) { return v == k; }).a true >>> Ox.map("111", function(v, i) { return v == i; }) [false, true, false] >>> Ox.map([,], function(v, i) { return i; }) [0] */ // fixme: return null to filter out is a bit esoteric var isObject = Ox.isObject(obj), ret = isObject ? {} : []; Ox.forEach(obj, function(val, key) { if ((v = fn(val, key)) !== null) { ret[isObject ? key : ret.length] = v; } }); return ret; }; Ox.max = function(obj) { /* >>> Ox.max([-1, 0, 1]) 1 >>> Ox.max({a: 1, b: 2, c: 3}) 3 */ return Math.max.apply(Math, Ox.values(obj)); }; Ox.min = function(obj) { /* >>> Ox.min([-1, 0, 1]) -1 >>> Ox.min({a: 1, b: 2, c: 3}) 1 */ return Math.min.apply(Math, Ox.values(obj)); }; Ox.merge = function(arr) { /* >>> Ox.merge(['foo'], ['bar'], ['baz']) ['foo', 'bar', 'baz'] */ Ox.forEach(Array.prototype.slice.call(arguments, 1), function(arg) { Ox.forEach(arg, function(val) { arr.push(val); }); }); return arr; }; /*@ Ox.range Python-style range (stop) -> <[n]> range Returns an array of integers from 0 (inclusive) to stop (exclusive). (start, stop) -> <[n]> range Returns an array of integers from start (inclusive) to stop (exclusive). (start, stop, step) -> <[n]> range Returns an array of numbers from start (inclusive) to stop (exclusive), incrementing by step. start Start value stop Stop value step 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.serialize = function(obj) { /* >>> Ox.serialize({a: 1, b: 2, c: 3}) 'a=1&b=2&c=3' */ var arr = []; Ox.forEach(obj, function(val, key) { val !== '' && arr.push(key + '=' + val); }); return arr.join('&'); }; Ox.setPropertyOnce = function(arr, str) { /* >>> Ox.setPropertyOnce([{selected: false}, {selected: false}], 'selected') 0 >>> Ox.setPropertyOnce([{selected: false}, {selected: true}], 'selected') 1 >>> Ox.setPropertyOnce([{selected: true}, {selected: true}], 'selected') 0 */ var pos = -1; Ox.forEach(arr, function(v, i) { if (pos == -1 && arr[i][str]) { pos = i; } else if (pos > -1 && arr[i][str]) { delete arr[i][str]; } }); if (pos == -1) { arr[0][str] = true; pos = 0; } return pos; }; Ox.shuffle = function(arr) { /* >>> Ox.shuffle([1, 2, 3]).length 3 */ var shuffle = arr; return shuffle.sort(function() { return Math.random() - 0.5; }); }; Ox.some = function(obj, fn) { /* Ox.some() works for arrays, objects and strings, unlike [].some() >>> Ox.some([2, 1, 0], function(i, v) { return i == v; }) true >>> Ox.some({a: 1, b: 2, c: 3}, function(v) { return v == 1; }) true >>> Ox.some("foo", function(v) { return v == 'f'; }) true */ return Ox.filter(Ox.values(obj), fn).length > 0; }; Ox.sort = function(arr) { /* >>> Ox.sort(['10', '9', 'B', 'a']) ['9', '10', 'a', 'B'] */ var len, matches = {}, sort = {}; // find leading numbers arr.forEach(function(val, i) { var match = /^\d+/(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 arr.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) { var ret = 0; if (sort[a] < sort[b]) { ret = -1; } else if (sort[a] > sort[b]) { ret = 1; } return ret; }); }; Ox.sum = function(obj) { /* >>> Ox.sum([-1, 0, 1]) 0 >>> Ox.sum({a: 1, b: 2, c: 3}) 6 */ var sum = 0; Ox.forEach(obj, function(val) { sum += val; }); return sum; }; Ox.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.unique = function(arr) { /* >>> Ox.unique([1, 2, 3, 1]) [1, 2, 3] */ var unique = []; Ox.forEach(arr, function(val) { unique.indexOf(val) == -1 && unique.push(val); }); return unique; }; Ox.unserialize = function(str) { /* >>> Ox.unserialize('a=1&b=2&c=3').c '3' */ var arr, obj = {}; Ox.forEach(str.split('&'), function(val) { arr = val.split('='); obj[arr[0]] = arr[1]; }); return obj; }; Ox.values = function(obj) { /* >>> Ox.values([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,]) [1] */ // fixme: why doesn't this use map? var values = []; Ox.forEach(obj, function(val) { values.push(val); }); return values; }; Ox.walk = function(obj, fn) { /* >>> a = 0; Ox.walk({a: 1, b: {c: 2, d: 3}}, function(v, k) { a += Ox.isNumber(v) ? v : 0}); a 6 */ Ox.forEach(obj, function(val, key) { fn(val, key, obj); Ox.walk(obj[key], fn); }); }; Ox.zip = function() { /* >>> Ox.zip([[0, 1], [2, 3], [4, 5]]) [[0, 2, 4], [1, 3, 5]] >>> Ox.zip([0, 1, 2], [3, 4, 5]) [[0, 3], [1, 4], [2, 5]] */ var args = arguments.length == 1 ? arguments[0] : Ox.makeArray(arguments), arr = []; args[0].forEach(function(v, i) { arr[i] = []; args.forEach(function(v_, i_) { arr[i].push(v_[i]); }); }); return arr; }; /* ================================================================================ Color functions ================================================================================ */ /*@ Ox.hsl Takes RGB values and returns HSL values (rgb) <[n]> HSL values rgb <[n]> RGB values > 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) { rgb = rgb.map(function(v) { return v / 255; }); var max = Ox.max(rgb), min = Ox.min(rgb), hsl = [0, 0, 0]; hsl[2] = 0.5 * (max + min); if (max == min) { hsl[0] = 0; hsl[1] = 0; } else { if (max == rgb[0]) { hsl[0] = (60 * (rgb[1] - rgb[2]) / (max - min) + 360) % 360; } else if (max == rgb[1]) { hsl[0] = 60 * (rgb[2] - rgb[0]) / (max - min) + 120; } else if (max == rgb[2]) { hsl[0] = 60 * (rgb[0] - rgb[1]) / (max - min) + 240; } if (hsl[2] <= 0.5) { hsl[1] = (max - min) / (2 * hsl[2]); } else { hsl[1] = (max - min) / (2 - 2 * hsl[2]); } } return hsl; }; /*@ Ox.rgb Takes HSL values and returns RGB values (hsl) <[n]> RGB values hsl <[n]> HSL values > 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) { hsl[0] /= 360; var rgb = [0, 0, 0], v1, v2, v3; if (hsl[1] == 0) { rgb = [hsl[2], hsl[2], hsl[2]]; } else { if (hsl[2] < 0.5) { v2 = hsl[2] * (1 + hsl[1]); } else { v2 = hsl[1] + hsl[2] - (hsl[1] * hsl[2]); } v1 = 2 * hsl[2] - v2; rgb.forEach(function(v, i) { v3 = hsl[0] + (1 - i) * 1/3; if (v3 < 0) { v3++; } else if (v3 > 1) { v3--; } if (v3 < 1/6) { rgb[i] = v1 + ((v2 - v1) * 6 * v3); } else if (v3 < 0.5) { rgb[i] = v2; } else if (v3 < 2/3) { rgb[i] = v1 + ((v2 - v1) * 6 * (2/3 - v3)); } else { rgb[i] = v1; } }); } return rgb.map(function(v) { return v * 255; }); }; /* ================================================================================ Date functions ================================================================================ */ /*@ Ox.getDateInWeek Get the date that falls on a given weekday in the same week # Usage (date, weekday) -> Date (date, weekday, utc) -> Date # Arguments date Date weekday 1-7 (Monday-Sunday) or name, full ("Monday") or short ("Sun") utc 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" @*/ 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]; Ox.print(date, Ox.getDate(date, utc), sourceWeekday, targetWeekday) return Ox.setDate(date, Ox.getDate(date, utc) - sourceWeekday + targetWeekday, utc); } /*@ Ox.getDayOfTheYear Get the day of the year for a given date # Usage (date) -> Date (date, utc) -> Date # Arguments date Date utc 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 = function(year, month, utc) { /* >>> Ox.getDaysInMonth(2000, 2) 29 >>> Ox.getDaysInMonth("2002", "Feb") 28 >>> Ox.getDaysInMonth(new Date('01/01/2004'), "February") 29 */ 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 = function(year, utc) { /* >>> Ox.getDaysInYear(1900) 365 >>> Ox.getDaysInYear('2000') 366 >>> Ox.getDaysInYear(new Date('01/01/2004')) 366 */ return 365 + Ox.isLeapYear(Ox.makeYear(year, utc)); }; Ox.getFirstDayOfTheYear = function(date, utc) { /* Decimal weekday of January 1 (0-6, Sunday as first day) >>> Ox.getFirstDayOfTheYear(new Date("01/01/2000")) 6 */ date = Ox.makeDate(date); date = Ox.setMonth(date, 0, utc); date = Ox.setDate(date, 1, utc); return Ox.getDay(date, utc) }; Ox.getISODate = function(date, utc) { /* >>> Ox.getISODate(new Date("01/01/2000")) "2000-01-01T00:00:00Z" */ return Ox.formatDate(Ox.makeDate(date), '%FT%TZ', utc); }; Ox.getISODay = function(date, utc) { /* Decimal weekday (1-7, Monday as first day) >>> Ox.getISODay(new Date("01/01/2000")) 6 >>> Ox.getISODay(new Date("01/02/2000")) 7 >>> Ox.getISODay(new Date("01/03/2000")) 1 */ return Ox.getDay(Ox.makeDate(date), utc) || 7; }; Ox.getISOWeek = function(date, utc) { /* see http://en.wikipedia.org/wiki/ISO_8601 >>> Ox.getISOWeek(new Date("01/01/2000")) 52 >>> Ox.getISOWeek(new Date("01/02/2000")) 52 >>> Ox.getISOWeek(new Date("01/03/2000")) 1 */ date = 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 = function(date, utc) { /* see http://en.wikipedia.org/wiki/ISO_8601 >>> Ox.getISOYear(new Date("01/01/2000")) 1999 >>> Ox.getISOYear(new Date("01/02/2000")) 1999 >>> Ox.getISOYear(new Date("01/03/2000")) 2000 */ date = 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.getTime = function() { // fixme: needed? return +new Date(); } Ox.getTimezoneOffset = function() { return new Date().getTimezoneOffset() * 60000; } Ox.getTimezoneOffsetString = function(date) { /* Time zone offset string ('-1200' - '+1200') >>> Ox.getTimezoneOffsetString(new Date('01/01/2000')).length 5 */ 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 = function(date, utc) { /* Week of the year (0-53, Sunday as first day) >>> Ox.getWeek(new Date("01/01/2000")) 0 >>> Ox.getWeek(new Date("01/02/2000")) 1 >>> Ox.getWeek(new Date("01/03/2000")) 1 */ date = Ox.makeDate(date); return Math.floor((Ox.getDayOfTheYear(date, utc) + Ox.getFirstDayOfTheYear(date, utc) - 1) / 7); }; Ox.isLeapYear = function(year, utc) { /* >>> Ox.isLeapYear(1900) false >>> Ox.isLeapYear('2000') true >>> Ox.isLeapYear(new Date('01/01/2004')) true */ year = Ox.makeYear(year, utc); return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); }; Ox.makeDate = function(date) { return Ox.isDate(date) ? date : Ox.isUndefined(date) ? new Date() : new Date(date); }; Ox.makeYear = function(date, utc) { return Ox.isDate(date) ? Ox.getFullYear(date, utc) : parseInt(date); }; [ 'FullYear', 'Month', 'Date', 'Day', 'Hours', 'Minutes', 'Seconds', 'Milliseconds' ].forEach(function(noun) { Ox['get' + noun] = function(date, utc) { return Ox.makeDate(date)['get' + (utc ? 'UTC' : '') + noun]() } Ox['set' + noun] = function(date, num, utc) { // new Date(date) makes a clone, so that // setSomething() doesn't have side effects return new Date( Ox.makeDate(date) )['set' + (utc ? 'UTC' : '') + noun](num); // fixme: maybe we _want_ set to have side effects? } }); /* ================================================================================ DOM functions ================================================================================ */ /*@ Ox.canvas Generic canvas object Returns an object with the properties: canvas, context, data and imageData. # Usage -------------------------------------------------------------------- Ox.canvas(width, height) -> canvas Ox.canvas(image) -> canvas # Arguments ---------------------------------------------------------------- width Width in px height Height in px image Image object @*/ Ox.canvas = function() { var c = {}, isImage = arguments.length == 1, image = isImage ? arguments[0] : { width: arguments[0], height: arguments[1] }; c.context = (c.canvas = Ox.element('').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 Calls a callback function once the DOM is ready (callback) -> If true, the document was ready callback Callback function @*/ Ox.documentReady = (function() { var callbacks = []; document.onreadystatechange = function() { if (document.readyState == 'complete') { //Ox.print('document has become ready', callbacks); 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 Generic HTML element, mimics jQuery (str) -> Element object str Tagname ('') or selector ('tagname', '.classname', '#id') > Ox.element("
").addClass("red").addClass("red")[0].className "red" > Ox.element("
").attr({id: "red"}).attr("id") "red" > Ox.element("
").css("color", "red").css("color") "red" > Ox.element("
").html("red").html() "red" @*/ Ox.element = function(str) { return { //@ 0 The DOM element itself 0: str[0] == '<' ? document.createElement(str.substr(1, str.length - 2)) : str[0] == '.' ? document.getElementsByClassName(str.substr(1))[0] : str[0] == '#' ? document.getElementById(str.substr(1)) : document.getElementsByTagName(str)[0], /*@ addClass Adds a class name (className) -> This element className Class name @*/ addClass: function(str) { this[0].className = this[0].className ? Ox.unique( (this[0].className + ' ' + str).split(' ') ) : str; return this; }, /*@ append Appends another element to this element (element) -> This element element Another element @*/ append: function(element) { this[0].appendChild(element[0]); return this; }, /*@ appendTo appends this element object to another element object (element) -> This element element Another element @*/ appendTo: function(element) { element[0].appendChild(this[0]); return this; }, /*@ attr Gets or sets an attribute (key) -> Value (key, value) -> This element ({key, value}) -> This element key Attribute name value 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; }, /*@ css Gets or sets a CSS attribute (key) -> Value (key, value) -> This element ({key, value}) -> This element key Attribute name value 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; }, /*@ html Gets or sets the inner HTML () -> The inner HTML (html) -> This element html 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 Binds a function to the mousedown event (callback) -> This element callback Callback function event The DOM event @*/ mousedown: function(callback) { this[0].onmousedown = callback; return this; }, /*@ removeClass Removes a class name (className) -> This element className Class name @*/ removeClass: function(str) { this[0].className = Ox.filter( this[0].className.split(' '), function(className) { return className != str; } ).join(' '); return this; } } }; /* ================================================================================ Encoding functions ================================================================================ */ (function() { var aliases = {'I': '1', 'L': '1', 'O': '0', 'U': 'V'}, digits = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; function cap(width, height) { // returns maximum encoding capacity of an image return parseInt(width * height * 3/8) - 4; } function seek(data, px) { // returns this, or the next, opaque pixel while (data[px * 4 + 3] < 255) { if (++px * 4 == data.length) { throwPNGError('de'); } } return px; } function xor(byte) { // returns "1"-bits-in-byte % 2 var xor = 0; Ox.range(8).forEach(function(i) { xor ^= byte >> i & 1; }); return xor; } function throwPNGError(str) { throw new RangeError( 'PNG codec can\'t ' + (str == 'en' ? 'encode data' : 'decode image') ); } function throwUTF8Error(byte, pos) { throw new RangeError( 'UTF-8 codec can\'t decode byte 0x' + byte.toString(16).toUpperCase() + ' at position ' + pos ); } Ox.encodeBase32 = function(num) { // see http://www.crockford.com/wrmg/base32.html /* >>> Ox.encodeBase32(15360) 'F00' >>> Ox.encodeBase32(33819) '110V' */ return Ox.map(num.toString(32), function(char) { return digits[parseInt(char, 32)]; }).join(''); } Ox.decodeBase32 = function(str) { /* >>> Ox.decodeBase32('foo') 15360 >>> Ox.decodeBase32('ilou') 33819 >>> Ox.decodeBase32('?').toString() 'NaN' */ return parseInt(Ox.map(str.toUpperCase(), function(char) { var index = digits.indexOf(aliases[char] || char); return (index == -1 ? ' ' : index).toString(32); }).join(''), 32); } Ox.encodeBase64 = function(num) { /* >>> Ox.encodeBase64(32394) 'foo' */ return btoa(Ox.encodeBase256(num)).replace(/=/g, ''); } Ox.decodeBase64 = function(str) { /* >>> Ox.decodeBase64('foo') 32394 */ return Ox.decodeBase256(atob(str)); } Ox.encodeBase128 = function(num) { /* >>> Ox.encodeBase128(1685487) 'foo' */ var str = ''; while (num) { str = Ox.char(num & 127) + str; num >>= 7; } return str; } Ox.decodeBase128 = function(str) { /* >>> Ox.decodeBase128('foo') 1685487 */ var num = 0, len = str.length; Ox.forEach(str, function(char, i) { num += char.charCodeAt(0) << (len - i - 1) * 7; }); return num; } Ox.encodeBase256 = function(num) { /* >>> Ox.encodeBase256(6713199) 'foo' */ var str = ''; while (num) { str = Ox.char(num & 255) + str; num >>= 8; } return str; } Ox.decodeBase256 = function(str) { /* >>> Ox.decodeBase256('foo') 6713199 */ var num = 0, len = str.length; Ox.forEach(str, function(char, i) { num += char.charCodeAt(0) << (len - i - 1) * 8; }); return num; } Ox.encodeDeflate = function(str) { // encodes string, using deflate /* in fact, the string is written to the rgb channels of a canvas element, then the dataURL is decoded from base64, and some head and tail cut off */ str = Ox.encodeUTF8(str); var len = str.length, c = Ox.canvas(Math.ceil((4 + len) / 3), 1), data; str = Ox.pad(Ox.encodeBase256(len), 4, Ox.char(0)) + str + Ox.repeat('\u00FF', (4 - len % 4) % 4); // simpler? Ox.pad()? /* fixme: why does map not work here? c.data = $.map(c.data, function(v, i) { return i % 4 < 3 ? str.charCodeAt(i - parseInt(i / 4)) : 255; }); */ for (i = 0; i < c.data.length; i += 1) { c.data[i] = i % 4 < 3 ? str.charCodeAt(i - parseInt(i / 4)) : 255; } c.context.putImageData(c.imageData, 0, 0); Ox.print(c.canvas.toDataURL()) data = atob(c.canvas.toDataURL().split(',')[1]); Ox.print('data', data); return data.substr(8, data.length - 20); } Ox.decodeDeflate = function(str) { var image = new Image(); image.src = 'data:image/png;base64,' + btoa('\u0089PNG\r\n\u001A\n' + str + Ox.repeat('\u0000', 4) + 'IEND\u00AEB`\u0082'); Ox.print(image.src); while (!image.width) {} // block until image data is available str = Ox.map(Ox.canvas(image).data, function(v, i) { return i % 4 < 3 ? Ox.char(v) : ''; }).join(''); return Ox.decodeUTF8(str.substr(4, Ox.decodeBase256(str.substr(0, 4)))); } Ox.encodeHTML = function(str) { /* >>> Ox.encodeHTML('\'<"&">\'') ''<"&">'' >>> Ox.encodeHTML('äbçdê') 'äbçdê' */ return Ox.map(str, function(v) { var code = v.charCodeAt(0); return code < 128 ? (v in Ox.HTML_ENTITIES ? Ox.HTML_ENTITIES[v] : v) : '&#x' + Ox.pad(code.toString(16).toUpperCase(), 4) + ';'; }).join(''); }; Ox.decodeHTML = function(str) { /* >>> Ox.decodeHTML(''<"&">'') '\'<"&">\'' >>> Ox.decodeHTML(''<"&">'') '\'<"&">\'' >>> Ox.decodeHTML('äbçdê') 'äbçdê' >>> Ox.decodeHTML('äbçdê') 'äbçdê' */ // relies on dom, but shorter than using this: // http://www.w3.org/TR/html5/named-character-references.html return Ox.element('
').html(str)[0].childNodes[0].nodeValue; }; Ox.encodePNG = function(img, str) { // encodes string into image, returns new image url /* the message is compressed with deflate (by proxy of canvas), then the string (four bytes length) + (length bytes message) is encoded bitwise into the r/g/b bytes of all opaque pixels by flipping, if necessary, the least significant bit, so that (number of "1"-bits of the byte) % 2 is the bit of the string wishlist: - only use deflate if it actually shortens the message - in deflate, strip and later re-insert the chunk types - encode a decoy message into the least significant bit (and flip the second least significant bit, if at all) - write an extra png chunk containing some key */ //str = Ox.encodeDeflate(str); currently broken str = Ox.encodeUTF8(str); var c = Ox.canvas(img), len = str.length, px = 0; if (len == 0 || len > cap(img.width, img.height)) { throwPNGError('en') } len = Ox.pad(Ox.encodeBase256(len), 4, Ox.char(0)); Ox.forEach(Ox.map(len + str, function(byte) { return Ox.map(Ox.range(8), function(i) { return byte.charCodeAt(0) >> 7 - i & 1; }).join(''); }).join(''), function(bit, i) { var index = parseInt((px = seek(c.data, px)) * 4 + i % 3), byte = c.data[index]; c.data[index] = bit == xor(byte) ? byte : byte & 254 | !(byte & 1); px += i % 3 == 2; }); c.context.putImageData(c.imageData, 0, 0); return c.canvas.toDataURL(); } Ox.decodePNG = function(img) { // decodes image, returns string var bits = '', data = Ox.canvas(img).data, flag = false, i = 0, len = 4, max = cap(img.width, img.height), px = 0, str = ''; do { bits += xor(data[parseInt((px = seek(data, px)) * 4 + i % 3)]); px += i % 3 == 2; if (++i % 8 == 0) { str += Ox.char(parseInt(bits, 2)); bits = ''; len--; if (len == 0 && !flag) { len = Ox.decodeBase256(str); if (len <= 0 || len > max) { Ox.print(len); throwPNGError('de'); } str = ''; flag = true; } } } while (len); try { //return Ox.decodeDeflate(str); currently broken return Ox.decodeUTF8(str); } catch(e) { Ox.print(e.toString()); throwPNGError('de'); } } /*@ Ox.encodeUTF8 Encodes a string as UTF-8 see http://en.wikipedia.org/wiki/UTF-8 (string) -> UTF-8 encoded string string Any string > Ox.encodeUTF8("foo") "foo" > 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 Decodes an UTF-8-encoded string see http://en.wikipedia.org/wiki/UTF-8 (utf8) -> string utf8 Any UTF-8-encoded string > Ox.decodeUTF8('foo') 'foo' > 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 functions ================================================================================ */ Ox.formatArea = function(num, dec) { return Ox.formatNumber(Ox.round(num / 1000000, dec)) + ' km\u00B2'; } Ox.formatColor = function() { }; Ox.formatCurrency = function(num, str, dec) { /* >>> Ox.formatCurrency(1000, '$', 2) '$1,000.00' */ return str + Ox.formatNumber(num, dec); }; Ox.formatDate = function(date, str, utc) { // fixme: date and utc are optional, date can be date, number or string /* See http://developer.apple.com/documentation/Darwin/Reference/ManPages/man3/strftime.3.html and http://en.wikipedia.org/wiki/ISO_8601 >>> _date = new Date("2005-01-02 00:03:04") "Sun Jan 02 2005 00:03:04 GMT+0100 (CET)" >>> Ox.formatDate(_date, "%A") // Full weekday "Sunday" >>> Ox.formatDate(_date, "%a") // Abbreviated weekday "Sun" >>> Ox.formatDate(_date, "%B") // Full month "January" >>> Ox.formatDate(_date, "%b") // Abbreviated month "Jan" >>> Ox.formatDate(_date, "%C") // Century "20" >>> Ox.formatDate(_date, "%c") // US time and date "01/02/05 12:03:04 AM" >>> Ox.formatDate(_date, "%D") // US date "01/02/05" >>> Ox.formatDate(_date, "%d") // Zero-padded day of the month "02" >>> Ox.formatDate(_date, "%e") // Space-padded day of the month " 2" >>> Ox.formatDate(_date, "%F") // Date "2005-01-02" >>> Ox.formatDate(_date, "%G") // Full ISO-8601 year "2004" >>> Ox.formatDate(_date, "%g") // Abbreviated ISO-8601 year "04" >>> Ox.formatDate(_date, "%H") // Zero-padded hour (24-hour clock) "00" >>> Ox.formatDate(_date, "%h") // Abbreviated month "Jan" >>> Ox.formatDate(_date, "%I") // Zero-padded hour (12-hour clock) "12" >>> Ox.formatDate(_date, "%j") // Zero-padded day of the year "002" >>> Ox.formatDate(_date, "%k") // Space-padded hour (24-hour clock) " 0" >>> Ox.formatDate(_date, "%l") // Space-padded hour (12-hour clock) "12" >>> Ox.formatDate(_date, "%M") // Zero-padded minute "03" >>> Ox.formatDate(_date, "%m") // Zero-padded month "01" >>> Ox.formatDate(_date, "%n") // Newline "\n" >>> Ox.formatDate(_date, "%p") // AM or PM "AM" >>> Ox.formatDate(_date, "%Q") // Quarter of the year "1" >>> Ox.formatDate(_date, "%R") // Zero-padded hour and minute "00:03" >>> Ox.formatDate(_date, "%r") // US time "12:03:04 AM" >>> Ox.formatDate(_date, "%S") // Zero-padded second "04" >/> Ox.formatDate(_date, "%s") // Number of seconds since the Epoch "1104620584" >>> Ox.formatDate(_date, "%T") // Time "00:03:04" >>> Ox.formatDate(_date, "%t") // Tab "\t" >>> Ox.formatDate(_date, "%U") // Zero-padded week of the year (00-53, Sunday as first day) "01" >>> Ox.formatDate(_date, "%u") // Decimal weekday (1-7, Monday as first day) "7" >>> Ox.formatDate(_date, "%V") // Zero-padded ISO-8601 week of the year "53" >>> Ox.formatDate(_date, "%v") // Formatted date " 2-Jan-2005" >>> Ox.formatDate(_date, "%W") // Zero-padded week of the year (00-53, Monday as first day) "00" >>> Ox.formatDate(_date, "%w") // Decimal weekday (0-6, Sunday as first day) "0" >>> Ox.formatDate(_date, "%X") // US time "12:03:04 AM" >>> Ox.formatDate(_date, "%x") // US date "01/02/05" >>> Ox.formatDate(_date, "%Y") // Full year "2005" >>> Ox.formatDate(_date, "%y") // Abbreviated year "05" >/> Ox.formatDate(_date, "%Z") // Time zone name "CET" >/> Ox.formatDate(_date, "%z") // Time zone offset "+0100" >/> Ox.formatDate(_date, "%+") // Formatted date and time "Sun Jan 2 00:03:04 CET 2005" >>> Ox.formatDate(_date, "%%") "%" >>> delete _date true >>> Ox.formatDate(new Date("01/01/2000"), "%W") "00" >>> Ox.formatDate(new Date("01/02/2000"), "%W") "00" >>> Ox.formatDate(new Date("01/03/2000"), "%W") "01" */ date = Ox.makeDate(date); var format = [ ['%', function() {return '%{%}';}], ['c', function() {return '%x %X';}], ['X', function() {return '%r';}], ['x', function() {return '%D';}], ['D', function() {return '%m/%d/%y';}], ['F', function() {return '%Y-%m-%d';}], ['h', function() {return '%b';}], ['R', function() {return '%H:%M';}], ['r', function() {return '%I:%M:%S %p';}], ['T', function() {return '%H:%M:%S';}], ['v', function() {return '%e-%b-%Y';}], ['\\+', function() {return '%a %b %e %H:%M:%S %Z %Y';}], ['A', function(d) {return 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);}], ['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.formatDuration = function(sec, dec, format) { /* >>> Ox.formatDuration(123456.789, 3) "1:10:17:36.789" >>> Ox.formatDuration(12345.6789) "03:25:46" >>> Ox.formatDuration(12345.6789, true) "0:03:25:46" >>> Ox.formatDuration(3599.999, 3) "00:59:59.999" >>> Ox.formatDuration(3599.999) "01:00:00" */ var format = arguments.length == 3 ? format : (Ox.isString(dec) ? dec : "short"), dec = (arguments.length == 3 || Ox.isNumber(dec)) ? dec : 0, sec = dec ? sec : Math.round(sec), val = [ Math.floor(sec / 31536000), Math.floor(sec % 31536000 / 86400), Math.floor(sec % 86400 / 3600), Math.floor(sec % 3600 / 60), format == "short" ? Ox.formatNumber(sec % 60, dec) : sec % 60 ], str = { medium: ["y", "d", "h", "m", "s"], long: ["year", "day", "hour", "minute", "second"] }, pad = [0, 3, 2, 2, dec ? dec + 3 : 2]; while (!val[0] && val.length > (format == "short" ? 3 : 1)) { val.shift(); str.medium.shift(); str.long.shift(); pad.shift(); } while (format != "short" && !val[val.length - 1] && val.length > 1) { val.pop(); str.medium.pop(); str.long.pop(); } return 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 = function(num, dec) { /* >>> Ox.formatNumber(123456789, 3) "123,456,789.000" >>> Ox.formatNumber(-2000000 / 3, 3) "-666,666.667" >>> Ox.formatNumber(666666.666) "666,667" */ var str = Math.abs(num).toFixed(dec || 0), spl = str.split('.'), arr = []; while (spl[0]) { arr.unshift(spl[0].substr(-3)); spl[0] = spl[0].substr(0, spl[0].length - 3); } spl[0] = arr.join(','); return (num < 0 ? '-' : '') + spl.join('.'); }; Ox.formatOrdinal = function(num) { /* >>> 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" */ 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 = function(num, total, dec) { /* >>> Ox.formatPercent(1, 1000, 2) "0.10%" */ return Ox.formatNumber(num / total * 100, dec) + '%' }; Ox.formatResolution = function(arr, str) { /* >>> Ox.formatResolution([1920, 1080], 'px') "1920 x 1080 px" */ return arr[0] + ' x ' + arr[1] + (str ? ' ' + str : ''); } Ox.formatString = function (str, obj) { /* >>> Ox.formatString('{0}{1}', ['foo', 'bar']) "foobar" >>> Ox.formatString('{a}{b}', {a: 'foo', b: 'bar'}) "foobar" */ return str.replace(/\{([^}]+)\}/g, function(str, match) { return obj[match]; }); } Ox.formatValue = function(num, str, bin) { /* >>> Ox.formatValue(0, "B") "0 KB" >>> Ox.formatValue(123456789, "B") "123.5 MB" >>> Ox.formatValue(1234567890, "B", true) "1.15 GiB" */ var base = bin ? 1024 : 1000, len = Ox.PREFIXES.length, val; Ox.forEach(Ox.PREFIXES, function(chr, i) { if (num < Math.pow(base, i + 2) || i == len - 1) { val = Ox.formatNumber(num / Math.pow(base, i + 1), i) + ' ' + chr + (bin ? 'i' : '') + str; return false; } }); return val; }; Ox.formatUnit = function(num, str) { return num + ' ' + str; }; /* ================================================================================ Geo functions ================================================================================ */ (function() { function rad(point) { return { lat: Ox.rad(point.lat), lng: Ox.rad(point.lng) }; } Ox.crossesDateline = function(point0, point1) { return point0.lng > point1.lng; } 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 = function(point0, point1) { /* >>> Ox.getBearing({lat: -45, lng: 0}, {lat: 45, lng: 0}) 0 */ 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 = function(point0, point1) { /* >>> Ox.values(Ox.getCenter({lat: -45, lng: -90}, {lat: 45, lng: 90})) [0, 0] */ 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 = function(lat) { /*** return degrees per meter at a given latitude >>> Ox.EARTH_CIRCUMFERENCE == 360 / Ox.getDegreesPerMeter(0) true ***/ return 360 / Ox.EARTH_CIRCUMFERENCE / Math.cos(lat * Math.PI / 180); }; Ox.getDistance = function(point0, point1) { /* >>> Ox.EARTH_CIRCUMFERENCE == Ox.getDistance({lat: -45, lng: -90}, {lat: 45, lng: 90}) * 2 true */ 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 = function(xy) { /* >>> Ox.values(Ox.getLatLngByXY({x: 0.5, y: 0.5})) [0, 0] */ 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 = function(lat) { /*** returns meters per degree at a given latitude >>> Ox.EARTH_CIRCUMFERENCE == Ox.getMetersPerDegree(0) * 360 true ***/ return Math.cos(lat * Math.PI / 180) * Ox.EARTH_CIRCUMFERENCE / 360; }; Ox.getXYByLatLng = function(latlng) { /* >>> Ox.values(Ox.getXYByLatLng({lat: 0, lng: 0})) [0.5, 0.5] */ 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 = 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 = 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 = 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 functions ================================================================================ */ Ox.parseEmailAddresses = function(html) { return html.replace( /\b([0-9A-Z\.\+\-_]+@(?:[0-9A-Z\-]+\.)+[A-Z]{2,6})\b/gi, '$1' ); }; Ox.parseHTML = (function() { /* >>> Ox.parseHTML('http://foo.com, bar') 'foo.com, bar' >>> Ox.parseHTML('(see: www.foo.com)') '(see: www.foo.com)' >>> Ox.parseHTML('foo@bar.com') 'foo@bar.com' >>> Ox.parseHTML('foo') 'foo' >>> Ox.parseHTML('foo') '<a href="javascript:alert()">foo</a>' >>> Ox.parseHTML('[http://foo.com foo]') 'foo' >>> Ox.parseHTML('foo') '
foo
' >>> Ox.parseHTML('') '<script>alert()</script>' */ var defaultTags = [ 'a', 'b', 'blockquote', 'cite', 'code', 'del', 'em', 'i', 'img', 'ins', 'li', 'ol', 'q', 'rtl', 's', 'strong', 'sub', 'sup', 'ul', '[]' ], parse = { a: { ']*?href="(https?:\/\/.+?)".*?>': '', '<\/a>': '' }, img: { ']*?src="(https?:\/\/.+?)".*?>': '' }, rtl: { '': '
', '<\/rtl>': '
' }, '*': 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, '$2'); 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, '
\n') // close extra opening (and remove extra closing) tags // return $('
').html(html).html(); // fixme: this converts '"' to '"' return Ox.element('
').html(html).html(); } }()); Ox.parseURL = (function() { // fixme: leak memory, like now, or create every time? ... benchmark?? var a = document.createElement('a'), keys = ['hash', 'host', 'hostname', 'origin', 'pathname', 'port', 'protocol', 'search']; return function(str) { var ret = {}; a.href = str; keys.forEach(function(key) { ret[key] = a[key]; }); return ret; }; }()); Ox.parseURLs = function(html) { return html.replace( /\b((https?:\/\/|www\.).+?)([\.,:;!\?\)\]]*?(\s|$))/gi, function(str, url, pre, end) { url = (pre == 'www.' ? 'http://' : '' ) + url; return Ox.formatString( '{host}{end}', { end: end, host: Ox.parseURL(url).hostname, url: url } ); } ); }; /* ================================================================================ JavaScript functions ================================================================================ */ /*@ Ox.doc Generates documentation for annotated JavaScript (source) Array of documentation objects source JavaScript source code > Ox.doc("//@ Ox.foo just some string") [{"name": "Ox.foo", "summary": "just some string", "type": "string"}] @*/ Ox.doc = (function() { var re = { item: /^(.+?) <(.+?)> (.+)$/, multiline: /^\/\*\@.*?\n([\w\W]+)\n.*?\@\*\/$/, script: /\n(\s*