From 698d57abb06471ce8588345a0dab746c7a468b30 Mon Sep 17 00:00:00 2001 From: rolux Date: Sat, 7 May 2011 17:56:29 +0200 Subject: [PATCH] add documentation browser (Ox.DocPanel, demo, more documentation) --- demos/doc/deleteme.html | 18 - demos/doc/js/doc.js | 13 +- demos/doc2/index.html | 10 + demos/doc2/js/doc.js | 19 + source/Ox.Geo/Ox.Geo.js | 2 +- source/Ox.UI/Ox.UI.js | 5 +- source/Ox.UI/js/Core/Ox.DocPage.js | 25 +- source/Ox.UI/js/Core/Ox.DocPanel.js | 132 ++ source/Ox.UI/js/Core/Ox.Theme.js | 1 - source/Ox.UI/js/List/Ox.TreeList.js | 15 +- source/Ox.js | 2549 +++++++++++++++------------ 11 files changed, 1610 insertions(+), 1179 deletions(-) delete mode 100644 demos/doc/deleteme.html create mode 100644 demos/doc2/index.html create mode 100644 demos/doc2/js/doc.js create mode 100644 source/Ox.UI/js/Core/Ox.DocPanel.js diff --git a/demos/doc/deleteme.html b/demos/doc/deleteme.html deleted file mode 100644 index 8355b563..00000000 --- a/demos/doc/deleteme.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/demos/doc/js/doc.js b/demos/doc/js/doc.js index 84d50511..d60241a4 100644 --- a/demos/doc/js/doc.js +++ b/demos/doc/js/doc.js @@ -3,19 +3,26 @@ Ox.load('UI', { theme: 'classic' }, function() { Ox.Theme('classic'); + Ox.doc('../../build/Ox.js', function(items) { + items.forEach(function(item) { + Ox.DocPage({doc: item}).appendTo(Ox.UI.$body); + }); + }); + return; + Ox.get('../../build/Ox.js', function(source) { var doc = Ox.doc(source); doc.forEach(function(item) { Ox.DocPage({doc: item}).appendTo(Ox.UI.$body); }); - /* + // /* Ox.get('../../build/Ox.UI/js/Map/Ox.Map.js', function(source) { var doc = Ox.doc(source); doc.forEach(function(item) { Ox.DocPage({doc: item}).appendTo(Ox.UI.$body); }); + Ox.print('TEST', Ox.test(source)) }); - */ - Ox.print('TEST', Ox.test(source)) + // */ }); }); \ No newline at end of file diff --git a/demos/doc2/index.html b/demos/doc2/index.html new file mode 100644 index 00000000..7e3b8b03 --- /dev/null +++ b/demos/doc2/index.html @@ -0,0 +1,10 @@ + + + + OxJS Doc Demo + + + + + + \ No newline at end of file diff --git a/demos/doc2/js/doc.js b/demos/doc2/js/doc.js new file mode 100644 index 00000000..3ecb818f --- /dev/null +++ b/demos/doc2/js/doc.js @@ -0,0 +1,19 @@ +Ox.load('UI', { + debug: true, + theme: 'classic' +}, function() { + Ox.Theme('classic'); + Ox.DocPanel({ + files: ['Ox.js', 'Ox.UI/js/Map/Ox.Map.js'], + getModule: function(item) { + var file = item.file.replace(Ox.PATH, ''); + return file == 'Ox.js' ? 'Ox' : file.split('/')[0]; + }, + getSection: function(item) { + var file = item.file.replace(Ox.PATH, ''); + return item.section || file.split('/')[2]; + }, + path: Ox.PATH + }) + .appendTo(Ox.UI.$body); +}); \ No newline at end of file diff --git a/source/Ox.Geo/Ox.Geo.js b/source/Ox.Geo/Ox.Geo.js index 16ff7c46..77deef5f 100644 --- a/source/Ox.Geo/Ox.Geo.js +++ b/source/Ox.Geo/Ox.Geo.js @@ -1,6 +1,6 @@ Ox.load.Geo = function(options, callback) { - Ox.loadJSON(Ox.PATH + 'Ox.Geo/Ox.Geo.json', function(data) { + Ox.getJSON(Ox.PATH + 'Ox.Geo/json/Ox.Geo.json', function(data) { Ox.COUNTRIES = data; diff --git a/source/Ox.UI/Ox.UI.js b/source/Ox.UI/Ox.UI.js index 07a83712..d234bc19 100644 --- a/source/Ox.UI/Ox.UI.js +++ b/source/Ox.UI/Ox.UI.js @@ -41,6 +41,8 @@ Ox.load.UI = function(options, callback) { }); Ox.documentReady(function() { + Ox.element('body') + .addClass('OxTheme' + Ox.toTitleCase(options.theme || 'classic')); options.showScreen && showScreen(); }); @@ -48,8 +50,7 @@ Ox.load.UI = function(options, callback) { function showScreen() { - var body = Ox.element('body') - .addClass('OxTheme' + Ox.toTitleCase(options.theme)), + var body = Ox.element('body'), css = { position: 'absolute', left: 0, diff --git a/source/Ox.UI/js/Core/Ox.DocPage.js b/source/Ox.UI/js/Core/Ox.DocPage.js index d5d06a47..6401ff69 100644 --- a/source/Ox.UI/js/Core/Ox.DocPage.js +++ b/source/Ox.UI/js/Core/Ox.DocPage.js @@ -3,30 +3,29 @@ Ox.DocPage = function(options, self) { self = self || {}; var that = Ox.Element({}, self) .defaults({ - doc: '' + item: {} }) .options(options || {}) .addClass('OxText') .css({ - width: '640px', - height: '400px', overflow: 'auto' }); - $('body').css('overflowY', 'scroll') + Ox.print('-------------------item', self.options.item) - that.append($('

').append('' + self.options.doc.name + '')); + that.append($('

').append('' + self.options.item.name + '')); - getItem(self.options.doc, 0, '').forEach(function($element) { + getItem(self.options.item, 0).forEach(function($element) { that.append($element); - }) + }); - function getItem(item, level) { + function getItem(item, level, name) { var $elements = [$('
') .css({paddingLeft: ((level * 32) + 'px')}) .html( - '' + item.name + ' ' + + '' + (name || item.name) + ' ' + '<' + item.types.join('> or <') + '> ' + + (item.default ? 'default: ' + item.default + ' ' : '') + Ox.parseHTML(item.summary) ) ]; @@ -89,7 +88,6 @@ Ox.DocPage = function(options, self) { .html( '> ' + Ox.encodeHTML(example.statement) - //example.statement .replace(/ /g, ' ') .replace(/\n/g, '
\n  ') + '
' @@ -120,12 +118,11 @@ Ox.DocPage = function(options, self) { ); } else { item[section].forEach(function(v) { - if (section == 'usage') { - v.name = item.name + v.name + '
returns '; - } + var name = section == 'usage' ? + item.name + v.name + ' returns ' : null; $elements = Ox.merge( $elements, - Ox.map(getItem(v, level + 1), function($element) { + Ox.map(getItem(v, level + 1, name), function($element) { return $element.addClass(className); }) ); diff --git a/source/Ox.UI/js/Core/Ox.DocPanel.js b/source/Ox.UI/js/Core/Ox.DocPanel.js new file mode 100644 index 00000000..5ff597ca --- /dev/null +++ b/source/Ox.UI/js/Core/Ox.DocPanel.js @@ -0,0 +1,132 @@ +/*@ +Ox.DocPanel Documentation Panel +@*/ + +Ox.DocPanel = function(options, self) { + + self = self || {}; + var that = Ox.Element({}, self) + .defaults({ + collapsible: true, + files: [], + getModule: function(item) { + return item.file.replace(self.options.path, ''); + }, + getSection: function(item) { + return item.section; + }, + path: '', + resizable: true, + resize: [128, 256, 384], + size: 256 + }) + .options(options || {}); + + self.$list = Ox.Element(); + self.$page = Ox.Element(); + + that.$element = Ox.SplitPanel({ + elements: [ + { + collapsible: self.options.collapsible, + element: self.$list, + resizable: self.options.resizable, + resize: self.options.resize, + size: self.options.size + }, + { + element: self.$page + } + ], + orientation: 'horizontal' + }); + + loadList(function(docItems) { + self.items = docItems; + }); + + function loadList(callback) { + var counter = 0, + length = self.options.files.length; + docItems = []; + self.options.files.forEach(function(file) { + Ox.doc(self.options.path + file, function(fileItems) { + docItems = Ox.merge(docItems, fileItems); + if (++counter == length) { + makeTree(docItems); + callback(docItems); + } + }); + }); + function makeTree(docItems) { + var treeItems = []; + docItems.forEach(function(docItem) { + var moduleIndex, sectionIndex; + docItem.module = self.options.getModule(docItem); + moduleIndex = Ox.getPositionById(treeItems, '_' + docItem.module); + if (moduleIndex == -1) { + treeItems.push({ + id: '_' + docItem.module, + items: [], + title: docItem.module + }); + moduleIndex = treeItems.length - 1; + } + docItem.section = self.options.getSection(docItem); + if (docItem.section) { + sectionIndex = Ox.getPositionById( + treeItems[moduleIndex].items, '_' + docItem.section + ); + if (sectionIndex == -1) { + treeItems[moduleIndex].items.push({ + id: '_' + docItem.section, + items: [], + title: docItem.section + }); + sectionIndex = treeItems[moduleIndex].items.length - 1; + } + } + ( + docItem.section ? + treeItems[moduleIndex].items[sectionIndex] : + treeItems[moduleIndex] + ).items.push({ + id: docItem.name, + title: docItem.name + }); + }); + self.$list = Ox.TreeList({ + items: treeItems, + width: self.options.width + }) + .bindEvent({ + select: selectItem + }); + that.$element.replaceElement(0, self.$list); + } + } + + function getItemByName(name) { + var item = {}; + Ox.forEach(self.items, function(v) { + if (v.name == name) { + item = v; + return false; + } + }); + return item; + } + + function selectItem(data) { + var selected = data.ids[0]; + if (selected[0] != '_') { + self.$page = Ox.DocPage({ + item: getItemByName(selected) + }); + that.$element.replaceElement(1, self.$page); + } + } + + return that; + +}; \ No newline at end of file diff --git a/source/Ox.UI/js/Core/Ox.Theme.js b/source/Ox.UI/js/Core/Ox.Theme.js index a04e3aaa..31b228f4 100644 --- a/source/Ox.UI/js/Core/Ox.Theme.js +++ b/source/Ox.UI/js/Core/Ox.Theme.js @@ -22,7 +22,6 @@ Ox.Theme = function(theme) { return false; } }); - Ox.print('getTheme', theme) return theme; } diff --git a/source/Ox.UI/js/List/Ox.TreeList.js b/source/Ox.UI/js/List/Ox.TreeList.js index 6a270679..d5920b8d 100644 --- a/source/Ox.UI/js/List/Ox.TreeList.js +++ b/source/Ox.UI/js/List/Ox.TreeList.js @@ -156,7 +156,7 @@ Ox.TreeList = function(options, self) { } function toggleItem(item, expanded) { - var $img, $item, pos; + var $img, pos; item.expanded = expanded; $.each(that.$element.find('.OxItem'), function(i, v) { var $item = $(v); @@ -166,12 +166,23 @@ Ox.TreeList = function(options, self) { return false; } }); - Ox.print('i.e', item.expanded) + Ox.print('i.e', item.expanded, $img, $img.attr('src')); + // FIXME: why does this not work?? + ///* $img.attr({ src: Ox.UI.getImagePath( 'symbol' + (item.expanded ? 'Down' : 'Right') + '.svg' ) }); + //*/ + /* + Ox.print($img.attr('src')) + $img.attr({ + src: $img.attr('src') == Ox.UI.getImagePath('symbolDown.svg') ? + Ox.UI.getImagePath('symbolRight.svg') : + Ox.UI.getImagePath('symbolDown.svg') + }); + */ item.expanded ? that.$element.addItems(pos + 1, parseItems(item.items, item.level + 1)) : that.$element.removeItems(pos + 1, parseItems(item.items, item.level + 1).length); diff --git a/source/Ox.js b/source/Ox.js index a5e182e5..e440933d 100644 --- a/source/Ox.js +++ b/source/Ox.js @@ -1,10 +1,11 @@ // vim: et:ts=4:sw=4:sts=4:ft=js -// OxJS (c) 2007-2011 Ox2620, dual-licensed (GPL/MIT), see oxjs.org for details +// OxJS (c) 2011 Ox2620, dual-licensed GPL/MIT, see http://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 +//@ Core ----------------------------------------------------------------------- /*@ Ox The Ox object @@ -17,214 +18,6 @@ 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 @@ -246,85 +39,6 @@ Ox.load = function(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 @@ -400,59 +114,136 @@ Ox.wrap = function(val, chained) { return wrapper; }; -/* -================================================================================ -Array and Object functions -================================================================================ -*/ +//@ Array ---------------------------------------------------------------------- /*@ -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.compact Returns an array w/o nullundefined + > Ox.compact([null,,1,,2,,3]) + [1, 2, 3] @*/ -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 = {}; +/*@ + Ox.flatten Flattens an array + > Ox.flatten([1, [2, [3], 4], 5]) + [1, 2, 3, 4, 5] +@*/ +Ox.flatten = function(arr) { + // fixme: can this work for objects too? + 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.merge Merges an array with one or more other arrays + > Ox.merge(['foo'], ['foo', 'bar'], ['bar']) + ['foo', 'foo', 'bar', 'bar'] +@*/ +Ox.merge = function(arr) { + Ox.forEach(Array.prototype.slice.call(arguments, 1), function(arg) { + Ox.forEach(arg, function(val) { + arr.push(val); + }); + }); + return arr; +}; + +/*@ +Ox.unique Returns an array without duplicate values + > Ox.unique([1, 2, 3, 2, 1]) + [1, 2, 3] +@*/ + +Ox.unique = function(arr) { + var unique = []; + Ox.forEach(arr, function(val) { + unique.indexOf(val) == -1 && unique.push(val); + }); + return unique; +}; + +/*@ +Ox.zip 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_, i_) { + arr[i].push(v_[i]); + }); + }); + return arr; +}; + +//@ Collections ---------------------------------------------------------------- + +/*@ +Ox.avg Returns the average of an array's values, or an object's properties + (collection) -> 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 = function(obj) { + return Ox.sum(obj) / Ox.len(obj); +}; + +/*@ +Ox.clone Returns a (shallow) copy or 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(obj) { + return Ox.isArray(obj) ? obj.slice() : Ox.extend({}, obj); +}; + +/*@ +Ox.count Counts the occurences of values in an array, object or string + > 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.each (deprecated) Ox.each = function(obj, fn) { // fixme: deprecate! /* @@ -476,47 +267,38 @@ Ox.each = function(obj, fn) { return obj; }; +/*@ +Ox.every Returns true if a condition holds for every element of a collection + Unlike [].every(), Ox.every() 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(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 Filters a collection by a given condition + Unlike [].filter(), Ox.filter() 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(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) { @@ -533,14 +315,17 @@ Ox.filter = function(obj, fn) { return ret; }; +/*@ +Ox.find Returns array elements that match a string, leading matches first + > Ox.find(['foo', 'bar', 'foobar', 'barfoo'], 'foo') + [['foo", 'foobar'], ['barfoo']] +@*/ 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(); @@ -553,23 +338,6 @@ Ox.find = function(arr, str) { 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 @@ -604,6 +372,10 @@ Ox.forEach = function(obj, fn) { return obj; }; +/*@ +Ox.getObjectById Returns an array element with a given id +@*/ +// fixme: shouldn't this be getElementById() ? Ox.getObjectById = function(arr, id) { /*** >>> Ox.getObjectById([{id: "foo", title: "Foo"}, {id: "bar", title: "Bar"}], "foo").title @@ -619,6 +391,10 @@ Ox.getObjectById = function(arr, id) { return ret; }; +/*@ +Ox.getPositionById Returns the index of an array element with a given id +@*/ +// fixme: shouldn't this be getIndexById() ? Ox.getPositionById = function(arr, id) { /*** >>> Ox.getPositionById([{id: "foo", title: "Foo"}, {id: "bar", title: "Bar"}], "bar") @@ -634,67 +410,101 @@ Ox.getPositionById = function(arr, id) { return ret; }; +// fixme: and what about getElementBy() and getIndexBy() ? + +/*@ +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.isEmpty Returns true if an array, object or string is empty + > Ox.isEmpty([]) + true + > Ox.isEmpty({}) + true + > Ox.isEmpty('') + true +@*/ 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 Returns the keys of an array, object or string + Unlike Object.keys(), Ox.keys() works for arrays, + objects and strings. + > Ox.keys([1, 2, 3]) + [0, 1, 2] + > Ox.keys([1,,3]) + [0, 2] + > 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) { - /* - 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); @@ -702,13 +512,19 @@ Ox.keys = function(obj) { return keys.sort(); }; -Ox.last = function(arr, val) { - /*** - >>> Ox.last([1, 2, 3]) +/*@ +Ox.last Gets or sets the last element of an array + Unlike foobarbaz[foobarbaz.length - 1], + Ox.last(foobarbaz) is short. + > Ox.test.array = [1, 2, 3] + > Ox.last(Ox.test.array) 3 - >>> Ox.last([1, 2, 3], 4) + > 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]; @@ -719,19 +535,23 @@ Ox.last = function(arr, val) { return ret; }; -Ox.len = function(obj) { - /* - >>> Ox.len([1, 2, 3]) +/*@ +Ox.len Returns the length of an array, function, object or string + Not to be confused with Ox.length, which is the + length property of the Ox function + (1 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([,]) + > Ox.len([,]) 1 - */ + > Ox.len({a: 1, b: 2, c: 3}) + 3 + > Ox.len(function(x, y, z) { return x + y + z; }) + 3 + > Ox.len('abc') + 3 +@*/ +Ox.len = function(obj) { return Ox.isObject(obj) ? Ox.values(obj).length : obj.length; }; @@ -791,17 +611,20 @@ Ox.makeArray = function(obj) { return Array.prototype.slice.call(obj); } +/*@ +Ox.makeObject Takes an array and returns an object + Ox.makeObject is a helper for functions with two alternative + signatures like ('key', 'val') and ({key: 'val'}). + > (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) { - /* - >>> (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'}) @@ -813,19 +636,24 @@ Ox.makeObject = function(obj) { 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; }) +/* +Ox.map Transforms the values of an array, object or string + Unlike [].map(), Ox.map() works for arrays, + objects and strings. Returning null 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] + > 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 ? {} : []; @@ -837,39 +665,32 @@ Ox.map = function(obj, fn) { return ret; }; -Ox.max = function(obj) { - /* - >>> Ox.max([-1, 0, 1]) - 1 - >>> Ox.max({a: 1, b: 2, c: 3}) +/*@ +Ox.max 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(obj) { 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}) +/*@ +Ox.min 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(obj) { 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 @@ -1032,18 +853,6 @@ Ox.toArray = function(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 @@ -1087,29 +896,7 @@ Ox.walk = function(obj, 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 -================================================================================ -*/ +//@ Color ---------------------------------------------------------------------- /*@ Ox.hsl Takes RGB values and returns HSL values @@ -1198,11 +985,125 @@ Ox.rgb = function(hsl) { }); }; -/* -================================================================================ -Date functions -================================================================================ -*/ +//@ 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); +}); + +//@ Date ----------------------------------------------------------------------- + +//@ Ox.getDate Get the day of a date, optionally UTC /*@ Ox.getDateInWeek Get the date that falls on a given weekday in the same week @@ -1221,7 +1122,7 @@ Ox.getDateInWeek Get the date that falls on a given weekday in the same week > 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) { /* */ @@ -1236,6 +1137,8 @@ Ox.getDateInWeek = function(date, weekday, utc) { return Ox.setDate(date, Ox.getDate(date, utc) - sourceWeekday + targetWeekday, utc); } +//@ Ox.getDay Get the weekday of a date, optionally UTC + /*@ Ox.getDayOfTheYear Get the day of the year for a given date # Usage @@ -1262,14 +1165,17 @@ Ox.getDayOfTheYear = function(date, utc) { })) + Ox.getDate(date, utc); }; +/*@ +Ox.getDaysInMonth 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) { /* - >>> 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 : @@ -1279,61 +1185,71 @@ Ox.getDaysInMonth = function(year, month, utc) { return new Date(year, month, 0).getDate(); } -Ox.getDaysInYear = function(year, utc) { - /* - >>> Ox.getDaysInYear(1900) +/* +Ox.getDaysInYear Get the number of days in a given year + > Ox.getDaysInYear(1900) 365 - >>> Ox.getDaysInYear('2000') + > Ox.getDaysInYear('2000') 366 - >>> Ox.getDaysInYear(new Date('01/01/2004')) + > Ox.getDaysInYear(new Date('01/01/2004')) 366 - */ +*/ +Ox.getDaysInYear = function(year, utc) { 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")) +/*@ +Ox.getFirstDayOfTheYear 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 Get the year of a date, optionally UTC + +//@ Ox.getHours Get the hours of a date, optionally UTC + +/*@ +Ox.getISODate 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) { - /* - >>> 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")) +/*@ +Ox.getISODay 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")) + > Ox.getISODay(new Date('01/02/2000')) 7 - >>> Ox.getISODay(new Date("01/03/2000")) + > Ox.getISODay(new Date('01/03/2000')) 1 - */ +@*/ +Ox.getISODay = function(date, utc) { 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")) +/*@ +Ox.getISOWeek Get the ISO week of a given date + See ISO 8601 + > Ox.getISOWeek(new Date('01/01/2000')) 52 - >>> Ox.getISOWeek(new Date("01/02/2000")) + > Ox.getISOWeek(new Date('01/02/2000')) 52 - >>> Ox.getISOWeek(new Date("01/03/2000")) + > 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( @@ -1341,16 +1257,18 @@ Ox.getISOWeek = function(date, 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")) +/*@ +Ox.getISOYear Get the ISO year of a given date + See ISO 8601 + > Ox.getISOYear(new Date("01/01/2000")) 1999 - >>> Ox.getISOYear(new Date("01/02/2000")) + > Ox.getISOYear(new Date("01/02/2000")) 1999 - >>> Ox.getISOYear(new Date("01/03/2000")) + > 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( @@ -1358,64 +1276,103 @@ Ox.getISOYear = function(date, utc) { )); }; +//@ Ox.getMilliseconds Get the milliseconds of a date +//@ Ox.getMinutes Get the minutes of a date, optionally UTC +//@ Ox.getMonth Get the month of a date, optionally UTC +//@ Ox.getSeconds Get the seconds of a date + +//@ Ox.getTime Alias for +new Date() (deprecated) Ox.getTime = function() { // fixme: needed? return +new Date(); -} +}; +/*@ +Ox.getTimezoneOffset Get the local time zone offset in milliseconds +@*/ 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 +/*@ +Ox.getTimezoneOffsetString 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 = function(date, utc) { - /* - Week of the year (0-53, Sunday as first day) - >>> Ox.getWeek(new Date("01/01/2000")) +/*@ +Ox.getWeek 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")) + > Ox.getWeek(new Date('01/02/2000')) 1 - >>> Ox.getWeek(new Date("01/03/2000")) + > 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 = function(year, utc) { - /* - >>> Ox.isLeapYear(1900) +/*@ +Ox.isLeapYear Returns true if a given year is a leap year + > Ox.isLeapYear(1900) false - >>> Ox.isLeapYear('2000') + > Ox.isLeapYear('2000') true - >>> Ox.isLeapYear(new Date('01/01/2004')) + > 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 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) { return Ox.isDate(date) ? date : Ox.isUndefined(date) ? new Date() : new Date(date); }; +/*@ +Ox.makeYear 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.setDate Set the day of a date, optionally UTC +//@ Ox.setDay Set the weekday of a date, optionally UTC +//@ Ox.setFullYear Set the year of a date, optionally UTC +//@ Ox.setHours Set the hours of a date, optionally UTC +//@ Ox.setMilliseconds Set the milliseconds of a date +//@ Ox.setMinutes Set the minutes of a date, optionally UTC +//@ Ox.setMonth Set the month of a date, optionally UTC +//@ Ox.setSeconds Set the seconds of a date + [ 'FullYear', 'Month', 'Date', 'Day', 'Hours', 'Minutes', 'Seconds', 'Milliseconds' @@ -1433,11 +1390,7 @@ Ox.makeYear = function(date, utc) { } }); -/* -================================================================================ -DOM functions -================================================================================ -*/ +//@ DOM ------------------------------------------------------------------------ /*@ Ox.canvas Generic canvas object @@ -1625,11 +1578,7 @@ Ox.element = function(str) { } }; -/* -================================================================================ -Encoding functions -================================================================================ -*/ +//@ Encoding ------------------------------------------------------------------- (function() { @@ -1674,55 +1623,61 @@ Encoding functions ); } - Ox.encodeBase32 = function(num) { - // see http://www.crockford.com/wrmg/base32.html - /* - >>> Ox.encodeBase32(15360) + /*@ + Ox.encodeBase32 Encode a number as base32 + See Base 32. + > Ox.encodeBase32(15360) 'F00' - >>> Ox.encodeBase32(33819) + > Ox.encodeBase32(33819) '110V' - */ + @*/ + Ox.encodeBase32 = function(num) { return Ox.map(num.toString(32), function(char) { return digits[parseInt(char, 32)]; }).join(''); } - Ox.decodeBase32 = function(str) { - /* - >>> Ox.decodeBase32('foo') + /*@ + Ox.decodeBase32 Decodes a base32-encoded number + See Base 32. + > Ox.decodeBase32('foo') 15360 - >>> Ox.decodeBase32('ilou') + > Ox.decodeBase32('ILOU') 33819 - >>> Ox.decodeBase32('?').toString() + > 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 = function(num) { - /* - >>> Ox.encodeBase64(32394) + /*@ + Ox.encodeBase64 Encode a number as base64 + > Ox.encodeBase64(32394) 'foo' - */ + @*/ + Ox.encodeBase64 = function(num) { return btoa(Ox.encodeBase256(num)).replace(/=/g, ''); } - Ox.decodeBase64 = function(str) { - /* - >>> Ox.decodeBase64('foo') + /*@ + Ox.decodeBase64 Decodes a base64-encoded number + > Ox.decodeBase64('foo') 32394 - */ + @*/ + Ox.decodeBase64 = function(str) { return Ox.decodeBase256(atob(str)); } - Ox.encodeBase128 = function(num) { - /* - >>> Ox.encodeBase128(1685487) + /*@ + Ox.encodeBase128 Encode a number as base128 + > Ox.encodeBase128(1685487) 'foo' - */ + @*/ + Ox.encodeBase128 = function(num) { var str = ''; while (num) { str = Ox.char(num & 127) + str; @@ -1731,11 +1686,12 @@ Encoding functions return str; } - Ox.decodeBase128 = function(str) { - /* - >>> Ox.decodeBase128('foo') + /*@ + Ox.decodeBase128 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; @@ -1743,11 +1699,12 @@ Encoding functions return num; } - Ox.encodeBase256 = function(num) { - /* - >>> Ox.encodeBase256(6713199) + /*@ + Ox.encodeBase256 Encode a number as base256 + > Ox.encodeBase256(6713199) 'foo' - */ + @*/ + Ox.encodeBase256 = function(num) { var str = ''; while (num) { str = Ox.char(num & 255) + str; @@ -1756,11 +1713,12 @@ Encoding functions return str; } - Ox.decodeBase256 = function(str) { - /* - >>> Ox.decodeBase256('foo') + /*@ + Ox.decodeBase256 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; @@ -1768,6 +1726,7 @@ Encoding functions return num; } + //@ Ox.encodeDeflate (undocumented) Ox.encodeDeflate = function(str) { // encodes string, using deflate /* @@ -1793,6 +1752,7 @@ Encoding functions return data.substr(8, data.length - 20); } + //@ Ox.decodeDeflate (undocumented) Ox.decodeDeflate = function(str) { var image = new Image(); image.src = 'data:image/png;base64,' + btoa('\u0089PNG\r\n\u001A\n' + @@ -1805,13 +1765,14 @@ Encoding functions return Ox.decodeUTF8(str.substr(4, Ox.decodeBase256(str.substr(0, 4)))); } - Ox.encodeHTML = function(str) { - /* - >>> Ox.encodeHTML('\'<"&">\'') + /*@ + Ox.encodeHTML HTML-encodes a string + > Ox.encodeHTML('\'<"&">\'') ''<"&">'' - >>> Ox.encodeHTML('äbçdê') + > Ox.encodeHTML('äbçdê') 'äbçdê' - */ + @*/ + Ox.encodeHTML = function(str) { return Ox.map(str, function(v) { var code = v.charCodeAt(0); return code < 128 ? (v in Ox.HTML_ENTITIES ? Ox.HTML_ENTITIES[v] : v) : @@ -1819,24 +1780,25 @@ Encoding functions }).join(''); }; + /*@ + Ox.decodeHTML 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) { - /* - >>> 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 Encodes a string into an image, returns a new image URL 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) @@ -1872,8 +1834,8 @@ Encoding functions return c.canvas.toDataURL(); } + //@ Ox.decodePNG Decodes an image, returns a string 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 { @@ -1990,126 +1952,129 @@ Format functions ================================================================================ */ +/*@ +Ox.formatArea Formats a number of meters as square kilometers + > Ox.formatArea(1000000) + '1 km\u00B2' +@*/ + Ox.formatArea = function(num, dec) { return Ox.formatNumber(Ox.round(num / 1000000, dec)) + ' km\u00B2'; } +/*@ +Ox.formatColor (not implemented) +@*/ Ox.formatColor = function() { }; -Ox.formatCurrency = function(num, str, dec) { - /* - >>> Ox.formatCurrency(1000, '$', 2) +/*@ +Ox.formatCurrency 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 Formats a date according to a format string + See + strftime + and ISO 8601. + > Ox.test.date = new Date('2005-01-02 00:03:04') + > 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') // US time + '12:03:04 AM' + > Ox.formatDate(Ox.test.date, '%x') // US date + '01/02/05' + > 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 - - /* - 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 '%{%}';}], @@ -2167,19 +2132,20 @@ Ox.formatDate = function(date, str, utc) { return str; }; -Ox.formatDuration = function(sec, dec, format) { - /* - >>> Ox.formatDuration(123456.789, 3) +/*@ +Ox.formatDuration Formats a duration as a string + > Ox.formatDuration(123456.789, 3) "1:10:17:36.789" - >>> Ox.formatDuration(12345.6789) + > Ox.formatDuration(12345.6789) "03:25:46" - >>> Ox.formatDuration(12345.6789, true) + > Ox.formatDuration(12345.6789, true) "0:03:25:46" - >>> Ox.formatDuration(3599.999, 3) + > Ox.formatDuration(3599.999, 3) "00:59:59.999" - >>> Ox.formatDuration(3599.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), @@ -2213,15 +2179,17 @@ Ox.formatDuration = function(sec, dec, format) { }).join(format == "short" ? ":" : " "); }; -Ox.formatNumber = function(num, dec) { - /* - >>> Ox.formatNumber(123456789, 3) +/*@ +Ox.formatNumber Formats a number with thousands separators + > Ox.formatNumber(123456789, 3) "123,456,789.000" - >>> Ox.formatNumber(-2000000 / 3, 3) + > Ox.formatNumber(-2000000 / 3, 3) "-666,666.667" - >>> Ox.formatNumber(666666.666) + > Ox.formatNumber(666666.666) "666,667" - */ +@*/ +Ox.formatNumber = function(num, dec) { + // fixme: specify decimal and thousands separators var str = Math.abs(num).toFixed(dec || 0), spl = str.split('.'), arr = []; @@ -2233,23 +2201,24 @@ Ox.formatNumber = function(num, dec) { return (num < 0 ? '-' : '') + spl.join('.'); }; -Ox.formatOrdinal = function(num) { - /* - >>> Ox.formatOrdinal(1) +/*@ +Ox.formatOrdinal Formats a number as an ordinal + > Ox.formatOrdinal(1) "1st" - >>> Ox.formatOrdinal(2) + > Ox.formatOrdinal(2) "2nd" - >>> Ox.formatOrdinal(3) + > Ox.formatOrdinal(3) "3rd" - >>> Ox.formatOrdinal(4) + > Ox.formatOrdinal(4) "4th" - >>> Ox.formatOrdinal(11) + > Ox.formatOrdinal(11) "11th" - >>> Ox.formatOrdinal(12) + > Ox.formatOrdinal(12) "12th" - >>> Ox.formatOrdinal(13) + > 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'; @@ -2265,43 +2234,50 @@ Ox.formatOrdinal = function(num) { return str; }; -Ox.formatPercent = function(num, total, dec) { - /* - >>> Ox.formatPercent(1, 1000, 2) +/*@ +Ox.formatPercent 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 = function(arr, str) { - /* - >>> Ox.formatResolution([1920, 1080], 'px') +/*@ +Ox.formatResolution 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 Basic string formatting + > Ox.formatString('{0}{1}', ['foo', 'bar']) + foobar' + > Ox.formatString('{a}{b}', {a: 'foo', b: 'bar'}) + 'foobar' +@*/ + 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") +/*@ +Ox.formatValue Formats a numerical value + > Ox.formatValue(0, "B") "0 KB" - >>> Ox.formatValue(123456789, "B") + > Ox.formatValue(123456789, "B") "123.5 MB" - >>> Ox.formatValue(1234567890, "B", true) + > 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; @@ -2315,18 +2291,20 @@ Ox.formatValue = function(num, str, bin) { return val; }; +/*@ +Ox.formatUnit Formats a number with a unit +@*/ Ox.formatUnit = function(num, str) { return num + ' ' + str; }; -/* -================================================================================ -Geo functions -================================================================================ -*/ +//* 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), @@ -2334,10 +2312,16 @@ Geo functions }; } + /*@ + Ox.crossesDateline Returns true if a given rectangle crosses the dateline + @*/ Ox.crossesDateline = function(point0, point1) { return point0.lng > point1.lng; } + /*@ + Ox.getArea Returns the area in square meters of a given rectancle + @*/ Ox.getArea = function(point0, point1) { /* area of a ring between two latitudes: @@ -2361,11 +2345,12 @@ Geo functions Math.abs(point0.lng - point1.lng); }; - Ox.getBearing = function(point0, point1) { - /* - >>> Ox.getBearing({lat: -45, lng: 0}, {lat: 45, lng: 0}) + /*@ + Ox.getBearing 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) - @@ -2376,11 +2361,12 @@ Geo functions return (Ox.deg(Math.atan2(y, x)) + 360) % 360; }; + /*@ + Ox.getCenter 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) { - /* - >>> 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) * @@ -2399,20 +2385,21 @@ Geo functions return {lat: lat, lng: lng}; }; + /*@ + Ox.getDegreesPerMeter Returns degrees per meter at a given latitude + > 360 / Ox.getDegreesPerMeter(0) + Ox.EARTH_CIRCUMFERENCE + @*/ 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 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) { - /* - >>> 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( @@ -2422,11 +2409,12 @@ Geo functions ) * Ox.EARTH_RADIUS; }; + /*@ + Ox.getLatLngByXY Returns lat/lng for a given x/y on a 1x1 mercator projection + > Ox.values(Ox.getLatLngByXY({x: 0.5, y: 0.5})) + {lat: 0, lng: 0} + @*/ 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; } @@ -2436,20 +2424,21 @@ Geo functions } }; + /*@ + Ox.getMetersPerDegree Returns meters per degree at a given latitude + > Ox.getMetersPerDegree(0) * 360 + Ox.EARTH_CIRCUMFERENCE + @*/ 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 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) { - /* - >>> Ox.values(Ox.getXYByLatLng({lat: 0, lng: 0})) - [0.5, 0.5] - */ function getVal(val) { return (val / (2 * Math.PI) + 0.5) } @@ -2461,6 +2450,7 @@ Geo functions }()); +//@ Ox.Line (undocumented) Ox.Line = function(point0, point1) { var self = { @@ -2519,6 +2509,7 @@ Ox.Line = function(point0, point1) { }; +//@ Ox.Point (undocumented) Ox.Point = function(lat, lng) { var self = {lat: lat, lng: lng}, @@ -2552,6 +2543,7 @@ Ox.Point = function(lat, lng) { }; +//@ Ox.Rectangle (undocumented) Ox.Rectangle = function(point0, point1) { var self = { @@ -2588,13 +2580,12 @@ Ox.Rectangle = function(point0, point1) { }; +//@ HTML ----------------------------------------------------------------------- -/* -================================================================================ -HTML functions -================================================================================ -*/ - +/*@ +Ox.parseEmailAddresses 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, @@ -2602,25 +2593,26 @@ Ox.parseEmailAddresses = function(html) { ); }; -Ox.parseHTML = (function() { - /* - >>> Ox.parseHTML('http://foo.com, bar') +/*@ +Ox.parseHTML Takes HTML from an untrusted source and returns something sane + > Ox.parseHTML('http://foo.com, bar') 'foo.com, bar' - >>> Ox.parseHTML('(see: www.foo.com)') + > Ox.parseHTML('(see: www.foo.com)') '(see: www.foo.com)' - >>> Ox.parseHTML('foo@bar.com') + > Ox.parseHTML('foo@bar.com') 'foo@bar.com' - >>> Ox.parseHTML('foo') + > Ox.parseHTML('foo') 'foo' - >>> Ox.parseHTML('foo') + > Ox.parseHTML('foo') '<a href="javascript:alert()">foo</a>' - >>> Ox.parseHTML('[http://foo.com foo]') + > Ox.parseHTML('[http://foo.com foo]') 'foo' - >>> Ox.parseHTML('foo') + > Ox.parseHTML('foo') '
foo
' - >>> Ox.parseHTML('') - '<script>alert()</script>' - */ + > Ox.parseHTML('') + '<script>alert()</script>' +@*/ +Ox.parseHTML = (function() { var defaultTags = [ 'a', 'b', 'blockquote', 'cite', 'code', 'del', 'em', 'i', 'img', 'ins', @@ -2681,6 +2673,28 @@ Ox.parseHTML = (function() { }()); +/*@ +Ox.parseURL Takes a URL, returns its components + (url) -> URL components + url URL + > Ox.test.object = Ox.parseURL('http://www.foo.com:8080/bar/index.html?a=0&b=1#c') + > 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'), @@ -2696,6 +2710,11 @@ Ox.parseURL = (function() { }; }()); +/*@ +Ox.parseURLs 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, @@ -2713,18 +2732,44 @@ Ox.parseURLs = function(html) { ); }; -/* -================================================================================ -JavaScript functions -================================================================================ -*/ +//@ JavaScript ----------------------------------------------------------------- /*@ Ox.doc Generates documentation for annotated JavaScript + (file, callback) -> undefined + file JavaScript file + callback Callback function + doc <[o]> Array of doc objects + arguments <[o]|u> Arguments (array of doc objects) + Present if the type of the item is + "function". + description 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 File name + line Line number + name Name of the item + properties <[o]|u> Properties (array of doc objects) + Present if the type of the item is + "event", "function" + or "object". + section Section in the file + source <[o]> Source code (array of tokens) + length Length of the token + offset Offset of the token + type Type of the token + See Ox.tokenize for list of types + summary One-line summary + usage <[o]> Usage (array of doc objects) + Present if the type of the item is + "function". + type Type of the item + (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("//@ Ox.foo just some string") + # [{"name": "Ox.foo", "summary": "just some string", "type": "string"}] @*/ Ox.doc = (function() { @@ -2741,224 +2786,242 @@ Ox.doc = (function() { e: 'element', f: 'function', n: 'number', o: 'object', r: 'regexp', s: 'string', u: 'undefined', '*': 'any', '!': 'event' - } - return function(source) { - var blocks = [], - items = [], - tokens = []; - Ox.tokenize(source).forEach(function(token) { - var match; - token.source = source.substr(token.offset, token.length); - if (token.type == 'comment' && (match = - re.multiline(token.source) || re.singleline(token.source) - )) { - blocks.push(match[1]); - tokens.push([]); - } else if (tokens.length) { - tokens[tokens.length - 1].push(token); + }; + 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(str); + // to tell a variable with default value, like + // name foo'> summary + // from a line of description with tags, like + // some description 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(str); + return matches ? matches[0] : str; + } + function parseNode(node) { + var item = parseItem(node.line), subitem; + Ox.print(node, node.line, 'item', item); + node.nodes && node.nodes.forEach(function(node) { + var key, line = node.line, subitem; + if (!/^#/.test(node.line)) { + if (/^