oxjs/source/Ox/js/Core.js

361 lines
11 KiB
JavaScript
Raw Normal View History

// OxJS (c) 2012 0x2620, dual-licensed GPL/MIT, see http://oxjs.org for details
2011-11-05 16:46:53 +00:00
'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 <f> The <code>Ox</code> object
See <code>Ox.wrap</code> for details.
(value) -> <o> wrapped value
value <*> Any value
@*/
global.Ox = function(val) {
return Ox.wrap(val);
};
})(this);
/*@
Ox.Break <f> Breaks from <code>Ox.forEach</code> and <code>Ox.loop</code>
@*/
Ox.Break = function() {
throw Ox.BreakError;
};
Ox.BreakError = new SyntaxError('Illegal Ox.break() statement');
/*@
Ox.load <f> Loads a module
2012-03-30 01:41:47 +00:00
A module named "Foo" provides <code>Ox.Foo/Ox.Foo.js</code>, in which it
defines one method, <code>Ox.load.Foo</code>, that takes two arguments,
<code>options</code> and <code>callback</code>, and calls
<code>callback</code> with one argument, <code>true</code> for success or
<code>false</code> if an error occurred. Generally, the module should
2012-03-30 01:41:47 +00:00
define <code>Ox.Foo</code> and attach its own methods there.
(module, callback) -> <u> undefined
(module, options, callback) -> <u> undefined
(modules, callback) -> <u> undefined
module <s> Module name
modules <o> Multiple modules {name: options, ...}
options <o> Module options
callback <f> Callback function
success <b> If true, the module has been loaded successfully
@*/
Ox.load = function() {
var callback = arguments[arguments.length - 1],
counter = 0,
isObject = Ox.isObject(arguments[0]),
length,
modules = isObject ? arguments[0] : {},
success = 0;
if (!isObject) {
modules[arguments[0]] = Ox.isObject(arguments[1]) ? arguments[1] : {};
}
2011-11-01 14:45:47 +00:00
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);
});
});
});
};
2011-12-30 15:06:55 +00:00
/*@
Ox.localStorage <o> localStorage wrapper
2012-04-09 08:42:00 +00:00
(namespace) -> <f> localStorage object for a given namespace
FIXME: there is a bug in Ox.doc here,
will use "(namespace)" as function name
() -> <o> returns all key:value pairs
(key) -> <*> returns one value
(key, val) -> <f> sets one value, returns localStorage object
({key: val, ...}) -> <f> sets values, returns localStorage object
2011-12-30 15:06:55 +00:00
@*/
Ox.localStorage = function(namespace) {
2012-01-02 08:25:34 +00:00
if (!window.localStorage) {
window.localStorage = {};
}
2011-12-30 15:06:55 +00:00
function storage(key, val) {
2011-12-30 20:41:54 +00:00
var args, ret, value;
2011-12-30 15:37:41 +00:00
if (arguments.length == 0) {
2011-12-30 15:06:55 +00:00
ret = {};
Ox.forEach(localStorage, function(val, key) {
if (Ox.startsWith(key, namespace + '.')) {
2012-05-24 09:47:33 +00:00
ret[key.slice(namespace.length + 1)] = JSON.parse(val);
2011-12-30 15:06:55 +00:00
}
});
2011-12-30 15:51:33 +00:00
} else if (arguments.length == 1 && typeof key == 'string') {
// This gets called by Ox.Log before Type.js has loaded
2011-12-30 15:06:55 +00:00
value = localStorage[namespace + '.' + key];
2011-12-30 15:51:33 +00:00
ret = value === void 0 ? void 0 : JSON.parse(value);
2011-12-30 15:06:55 +00:00
} else {
2011-12-30 15:37:41 +00:00
args = Ox.makeObject(arguments);
Ox.forEach(args, function(val, key) {
localStorage[namespace + '.' + key] = JSON.stringify(val);
});
2011-12-30 15:06:55 +00:00
ret = this;
}
return ret;
};
// IE 8 doesn't like `storage.delete`
storage['delete'] = function(key) {
2011-12-30 15:37:41 +00:00
var keys = arguments.length == 0
2011-12-30 15:06:55 +00:00
? Object.keys(storage())
: [key];
keys.forEach(function(key) {
delete localStorage[namespace + '.' + key];
});
2012-04-09 08:42:00 +00:00
return storage;
2011-12-30 15:06:55 +00:00
};
return storage;
};
2011-11-04 18:11:45 +00:00
/*@
Ox.Log <f> Logging module
@*/
Ox.Log = (function() {
2011-12-30 15:06:55 +00:00
var storage = Ox.localStorage('Ox'),
2011-12-30 15:51:33 +00:00
log = storage('log') || {filter: [], filterEnabled: true},
that = function() {
2011-11-04 16:13:56 +00:00
var ret;
if (arguments.length == 0) {
2011-11-04 16:34:24 +00:00
ret = log;
2011-11-04 16:13:56 +00:00
} else {
ret = that.log.apply(null, arguments);
}
return ret;
};
2011-11-04 15:52:47 +00:00
that.filter = function(val) {
2011-11-04 16:34:24 +00:00
if (!Ox.isUndefined(val)) {
2011-11-04 18:11:45 +00:00
that.filter.enable();
log.filter = Ox.makeArray(val);
2011-12-30 15:51:33 +00:00
storage('log', log);
2011-11-04 15:52:47 +00:00
}
2011-11-04 16:34:24 +00:00
return log.filter;
2011-11-04 15:52:47 +00:00
};
that.filter.add = function(val) {
2012-05-24 07:45:33 +00:00
return that.filter(Ox.unique(log.filter.concat(Ox.makeArray(val))));
2011-11-04 15:52:47 +00:00
};
2011-11-04 16:34:24 +00:00
that.filter.disable = function() {
log.filterEnabled = false;
2011-12-30 15:51:33 +00:00
storage('log', log);
2011-11-04 16:34:24 +00:00
};
that.filter.enable = function() {
log.filterEnabled = true;
2011-12-30 15:51:33 +00:00
storage('log', log);
2011-11-04 16:34:24 +00:00
};
2011-11-04 15:52:47 +00:00
that.filter.remove = function(val) {
val = Ox.makeArray(val);
2011-11-04 16:04:46 +00:00
return that.filter(log.filter.filter(function(v) {
2011-11-04 15:52:47 +00:00
return val.indexOf(v) == -1;
}));
};
2011-11-04 16:34:24 +00:00
that.log = function() {
var args = Ox.toArray(arguments), date, ret;
2011-11-04 16:34:24 +00:00
if (!log.filterEnabled || log.filter.indexOf(args[0]) > -1) {
2011-11-04 15:52:47 +00:00
date = new Date();
args.unshift(
2012-05-24 09:47:33 +00:00
Ox.formatDate(date, '%H:%M:%S.') + (+date).toString().slice(-3)
);
2011-11-04 15:52:47 +00:00
window.console && window.console.log.apply(window.console, args);
ret = args.join(' ');
}
return ret;
};
return that;
}());
2012-01-07 07:20:02 +00:00
/*@
Ox.loop <f> For-loop, functional-style
Returning <code>false</code> from the iterator function acts like a
<code>break</code> statement. Unlike a <code>for</code> loop,
<code>Ox.loop</code> doesn't leak its counter variable to the outer scope,
but returns it.
2012-04-13 21:19:52 +00:00
(stop, fn) -> <n> Next value
equivalent to <code>for (var i = 0; i < stop; i++) { fn(i); }</code>
(start, stop, fn) -> <n> Next value
equivalent to <code>for (var i = start; i < stop; i++) { fn(i); }</code>
or, if <code>start</code> is larger than <code>stop</code>,
<code>for (var i = start; i > stop; i--) { fn(i); }</code>
(start, stop, step, fn) -> <n> Next value
equivalent to <code>for (var i = start; i < stop; i += step) { fn(i);
}</code> or, if <code>step</code> is negative, <code>for (var i = start;
i > stop; i += step) { fn(i); }</code>
2012-01-07 07:20:02 +00:00
start <n> Start value
stop <n> Stop value (exclusive)
step <n> Step value
2012-04-13 21:19:52 +00:00
fn <f> Iterator function
2012-01-07 07:20:02 +00:00
i <n> Counter value
2012-05-23 15:13:38 +00:00
> Ox.loop(10, function(i) { i == 4 && Ox.break() })
2012-01-07 07:20:02 +00:00
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),
2012-04-13 21:19:52 +00:00
fn = arguments[len - 1],
2012-01-07 07:20:02 +00:00
i;
try {
for (i = start; step > 0 ? i < stop : i > stop; i += step) {
// fn(i);
if (fn(i) === false) {
throw new Error('Returning false in Ox.loop is deprecated.');
}
}
} catch(e) {
if (e !== Ox.BreakError) {
throw e;
2012-04-08 12:19:34 +00:00
}
2012-01-07 07:20:02 +00:00
}
return i;
};
/*@
Ox.print <f> Prints its arguments to the console
(arg, ...) -> <s> String
The string contains the timestamp, the name of the caller function, and
any arguments, separated by spaces
arg <*> any value
> Ox.print('foo').split(' ').pop()
"foo"
@*/
Ox.print = function() {
var args = Ox.toArray(arguments),
date = new Date();
args.unshift(
2012-05-24 09:47:33 +00:00
Ox.formatDate(date, '%H:%M:%S.') + (+date).toString().slice(-3)/*,
(arguments.callee.caller && arguments.callee.caller.name)
2011-11-05 16:46:53 +00:00
|| '(anonymous)'*/
);
window.console && window.console.log.apply(window.console, args);
return args.join(' ');
};
/*@
Ox.uid <f> Returns a unique id
() -> <n> Unique id
> Ox.uid() != Ox.uid()
true
@*/
Ox.uid = (function() {
var uid = 0;
return function() {
return uid++;
};
}());
/*@
Ox.wrap <f> Wraps a value so that one can directly call any Ox function on it
<code>Ox(value)</code> is a shorthand for <code>Ox.wrap(value)</code>.
(value) -> <o> wrapped value
chain <f> Wrap return values to allow chaining
value <f> Unwrap the value wrapped by <code>chain()</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) {
2011-11-05 16:46:53 +00:00
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;
};