// OxJS (c) 2012 0x2620, dual-licensed GPL/MIT, see http://oxjs.org for details 'use strict'; /* Some conventions: Functions - only one var statement, in the first line of the function - return only once, from the last line of the function Variable names arg argument args arguments arr array canFoo boolean callback callback function col collection (array, string or object) date date fn function hasFoo boolean i index (integer key) isFoo boolean k key (of a key/value pair) key key (of a key/value pair) max maximum value min minimum value num number obj object re regexp ret return value v value (of a key/value pair) val value (of a key/value pair) Indentation Variable definitions var a = { key: value, key: value, key: value }, b = {key: value}, c = {key: value}; Method chaining Obj.fnA({ key: value, key: value, key: value }) .fnB({key: val}) .fnC({key: val}); Simple conditionals condition && expression; Conditionals if (condition) { expression; } Conditionals with long conditions if ( condition && condition && condition ) { expression; } Ternary operator condition ? expression : expression; Ternary operator with long conditions or expressions condition ? expression : condition ? expression : expression; */ // 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 (function(global) { /*@ Ox The Ox object See Ox.wrap for details. (value) -> wrapped value value <*> Any value @*/ global.Ox = function(val) { return Ox.wrap(val); }; })(this); /*@ Ox.load Loads a module A module named "Foo" provides Ox.Foo/Ox.Foo.js, in which it defines one method, Ox.load.Foo, that takes two arguments, options and callback, and calls callback with one argument, true for success or false if an error occurred. Generally, the module should define Ox.Foo and attach its own methods there. (module, callback) -> undefined (module, options, callback) -> undefined (modules, callback) -> undefined module Module name modules Multiple modules {name: options, ...} options Module options callback Callback function success If true, the module has been loaded successfully @*/ Ox.load = function() { var callback = arguments[arguments.length - 1], counter = 0, isObject = Ox.isObject(arguments[0]), length, modules = isObject ? arguments[0] : {}, success = 0; if (!isObject) { modules[arguments[0]] = Ox.isObject(arguments[1]) ? arguments[1] : {}; } length = Ox.len(modules); Ox.forEach(modules, function(options, module) { Ox.loadFile(Ox.PATH + 'Ox.' + module + '/Ox.' + module + '.js', function() { Ox.load[module](options, function(s) { success += s; ++counter == length && callback(success == counter); }); }); }); }; /*@ Ox.localStorage localStorage wrapper (namespace) -> localStorage object for a given namespace FIXME: there is a bug in Ox.doc here, will use "(namespace)" as function name () -> returns all key:value pairs (key) -> <*> returns one value (key, val) -> sets one value, returns localStorage object ({key: val, ...}) -> sets values, returns localStorage object @*/ Ox.localStorage = function(namespace) { if (!window.localStorage) { window.localStorage = {}; } function storage(key, val) { var args, ret, value; if (arguments.length == 0) { ret = {}; Ox.forEach(localStorage, function(val, key) { if (Ox.startsWith(key, namespace + '.')) { ret[key.substr(namespace.length + 1)] = JSON.parse(val); } }); } else if (arguments.length == 1 && typeof key == 'string') { // This gets called by Ox.Log before Type.js has loaded value = localStorage[namespace + '.' + key]; ret = value === void 0 ? void 0 : JSON.parse(value); } else { args = Ox.makeObject(arguments); Ox.forEach(args, function(val, key) { localStorage[namespace + '.' + key] = JSON.stringify(val); }); ret = this; } return ret; }; storage.delete = function(key) { var keys = arguments.length == 0 ? Object.keys(storage()) : [key]; keys.forEach(function(key) { delete localStorage[namespace + '.' + key]; }); return storage; }; return storage; }; /*@ Ox.Log Logging module @*/ Ox.Log = (function() { var storage = Ox.localStorage('Ox'), log = storage('log') || {filter: [], filterEnabled: true}, that = function() { var ret; if (arguments.length == 0) { ret = log; } else { ret = that.log.apply(null, arguments); } return ret; }; that.filter = function(val) { if (!Ox.isUndefined(val)) { that.filter.enable(); log.filter = Ox.makeArray(val); storage('log', log); } return log.filter; }; that.filter.add = function(val) { return that.filter(Ox.unique(log.filter.concat(Ox.makeArray(val)))); }; that.filter.disable = function() { log.filterEnabled = false; storage('log', log); }; that.filter.enable = function() { log.filterEnabled = true; storage('log', log); }; that.filter.remove = function(val) { val = Ox.makeArray(val); return that.filter(log.filter.filter(function(v) { return val.indexOf(v) == -1; })); }; that.log = function() { var args = Ox.toArray(arguments), date, ret; if (!log.filterEnabled || log.filter.indexOf(args[0]) > -1) { date = new Date(); args.unshift( Ox.formatDate(date, '%H:%M:%S.') + (+date).toString().substr(-3) ); window.console && window.console.log.apply(window.console, args); ret = args.join(' '); } return ret; }; return that; }()); /*@ Ox.loop For-loop, functional-style Returning false from the iterator 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, fn) -> Next value equivalent to for (var i = 0; i < stop; i++) { fn(i); } (start, stop, fn) -> Next value equivalent to for (var i = start; i < stop; i++) { fn(i); } or, if start is larger than stop, for (var i = start; i > stop; i--) { fn(i); } (start, stop, step, fn) -> Next value equivalent to for (var i = start; i < stop; i += step) { fn(i); } or, if step is negative, for (var i = start; i > stop; i += step) { fn(i); } start Start value stop Stop value (exclusive) step Step value fn Iterator function i Counter value > Ox.loop(10, function(i) { i == 4 && Ox.break() }) 4 > Ox.loop(0, 3, 2, function() {}) 4 @*/ Ox.loop = function() { var len = arguments.length, start = len > 2 ? arguments[0] : 0, stop = arguments[len > 2 ? 1 : 0], step = len == 4 ? arguments[2] : (start <= stop ? 1 : -1), fn = arguments[len - 1], i; try { for (i = start; step > 0 ? i < stop : i > stop; i += step) { if (fn(i) === false) { console.warn('Returning false in Ox.loop is deprecated.') break; } } } catch(e) { if (e !== Ox.BreakError) { throw e; } } return i; }; Ox._loop = function() { var type = Ox.makeArray(arguments).map(function(arg) { return Ox.typeOf(arg); }), fnIndex = type.indexOf('function'), callbackIndex = type.lastIndexOf('function'), nIndex = callbackIndex - 1, hasStart = type[1] == 'number', hasStep = hasStart && type[2] == 'number', hasCallback = fnIndex != callbackIndex, hasN = hasCallback && type[nIndex] == 'number', start = hasStart ? arguments[0] : 0, stop = arguments[hasStart ? 1 : 0], step = hasStep ? arguments[2] : (start <= stop ? 1 : -1), fn = arguments[fnIndex], n = hasN ? arguments[nIndex] / step : 1, callback = hasCallback ? arguments[callbackIndex] : null; function loop(start_, stop_, callback_) { var i, isFalse; for (i = start_; step > 0 ? i < stop_ : i > stop_; i += step) { if (isFalse = fn(i) === false) { break; } } callback_ && callback_(i, !isFalse); return i; } function next(start_) { setTimeout(function() { loop(start_, Math.min(start_ + n, stop), function(i, ret) { (ret && i < stop ? next : callback)(i); }); }); } if (hasCallback) { next(start); } else { return loop(start, stop); } }; /*@ 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').split(' ').pop() "foo" @*/ Ox.print = function() { var args = Ox.toArray(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 Ox(value) is a shorthand for Ox.wrap(value). (value) -> wrapped value chain Wrap return values to allow chaining value Unwrap the value wrapped by chain() value <*> Any value > Ox("foobar").repeat(2) "foobarfoobar" > Ox("foobar").chain().reverse().toTitleCase().value() "Raboof" > Ox.wrap("foobar").value() "foobar" @*/ Ox.wrap = function(val, chained) { // somewhat inspired by underscore.js var wrapper = { chain: function() { wrapper.chained = true; return wrapper; }, chained: chained || false, value: function() { return val; } }; Object.getOwnPropertyNames(Ox).forEach(function(name) { if ( ['arguments', 'callee', 'caller', 'length'].indexOf(name) == -1 && 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; };