Created
December 11, 2012 08:08
-
-
Save kmnk/4256739 to your computer and use it in GitHub Desktop.
brook.js by CoffeeScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var __indexOf = Array.prototype.indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; | |
Namespace('brook').define(function(ns) { | |
var Promise, VERSION, promise; | |
VERSION = '0.01'; | |
Promise = (function() { | |
function Promise(next) { | |
this.next = next || function(next, val) { | |
return next(val); | |
}; | |
} | |
Promise.prototype.concat = function(after) { | |
var next, _before; | |
_before = this; | |
next = function(n, val) { | |
return _before.subscribe(after.ready(n), val); | |
}; | |
return new Promise(next); | |
}; | |
Promise.prototype.bind = function() { | |
var r, s, _i, _len; | |
r = this; | |
for (_i = 0, _len = arguments.length; _i < _len; _i++) { | |
s = arguments[_i]; | |
s = s instanceof Promise ? s : promise(s); | |
r = r.concat(s); | |
} | |
return r; | |
}; | |
Promise.prototype.ready = function(n) { | |
var promise; | |
promise = this; | |
return function(val) { | |
return promise.subscribe(n, val); | |
}; | |
}; | |
Promise.prototype.run = function(val) { | |
return this.subscribe(void 0, val); | |
}; | |
Promise.prototype.subscribe = function(next, val) { | |
next = next ? next : function() {}; | |
if (!this.errorHandler) return this.next(next, val); | |
try { | |
return this.next(next, val); | |
} catch (e) { | |
return this.onError(e); | |
} | |
}; | |
Promise.prototype.forEach = Promise.subscribe; | |
Promise.prototype.setErrorHandler = function(promise) { | |
return this.errorHandler = promise; | |
}; | |
Promise.prototype.onError = function(e) { | |
return (this.errorHandler || new Promise()).subscribe(function() {}, e); | |
}; | |
return Promise; | |
})(); | |
promise = function(next) { | |
return new Promise(next); | |
}; | |
return ns.provide({ | |
promise: promise, | |
VERSION: VERSION | |
}); | |
}); | |
Namespace('brook.util').use('brook promise').define(function(ns) { | |
var EMIT_INTERVAL_MAP, LOCK_MAP, cond, debug, emitInterval, filter, from, lock, mapper, match, now, scatter, stopEmitInterval, takeBy, through, unlock, wait, waitUntil, _arrayWalk; | |
mapper = function(f) { | |
return ns.promise(function(next, val) { | |
return next(f(val)); | |
}); | |
}; | |
through = function(f) { | |
return ns.promise(function(next, val) { | |
f(val); | |
return next(val); | |
}); | |
}; | |
filter = function(f) { | |
return ns.promise(function(next, val) { | |
if (f(val)) return next(val); | |
}); | |
}; | |
takeBy = function(takeBy) { | |
var num, queue; | |
num = 1; | |
queue = []; | |
return ns.promise(function(next, val) { | |
queue.push(val); | |
if (num++ % takeBy === 0) { | |
next(queue); | |
return queue = []; | |
} | |
}); | |
}; | |
now = Date.now ? function() { | |
return Date.now(); | |
} : function() { | |
return +new Date(); | |
}; | |
_arrayWalk = function(list, func, limit) { | |
var index, length; | |
index = 0; | |
length = list.length; | |
return (function() { | |
var startTime; | |
startTime = now(); | |
while (length > index && limit > (now() - startTime)) { | |
func(list[index++]); | |
} | |
if (length > index) return setTimeout(arguments.callee, 10); | |
})(); | |
}; | |
scatter = function(limit) { | |
return ns.promise(function(next, list) { | |
return _arrayWalk(list, next, limit || 400); | |
}); | |
}; | |
wait = function(msec) { | |
var msecFunc; | |
msecFunc = typeof msec === 'function' ? msec : function() { | |
return msec; | |
}; | |
return ns.promise(function(next, val) { | |
return setTimeout(function() { | |
return next(val); | |
}, msecFunc()); | |
}); | |
}; | |
waitUntil = function(f) { | |
var p; | |
return p = function(next, val) { | |
if (f()) return next(val); | |
return setTimeout(function() { | |
return p(next, val); | |
}, 100); | |
}; | |
}; | |
debug = function(sig) { | |
sig = sig ? sig : 'debug'; | |
return through(function(val) { | |
return console.log("" + sig + ":", val); | |
}); | |
}; | |
cond = function(f, promise) { | |
return ns.promise(function(next, val) { | |
if (!f(val)) return next(val); | |
return promise.subscribe(function(val) { | |
return next(val); | |
}, val); | |
}); | |
}; | |
match = function(dispatchTable, matcher) { | |
return ns.promise(function(next, val) { | |
var promise; | |
if (matcher) promise = dispatchTable[matcher(val)]; | |
if (!promise) { | |
promise = dispatchTable[val] || dispatchTable.__default__ || ns.promise(); | |
} | |
return promise.subscribe(function(v) { | |
return next(v); | |
}, val); | |
}); | |
}; | |
LOCK_MAP = {}; | |
unlock = function(name) { | |
return ns.promise(function(next, val) { | |
LOCK_MAP[name] = false; | |
return next(val); | |
}); | |
}; | |
lock = function(name) { | |
var tryLock; | |
tryLock = function(next, val) { | |
if (!LOCK_MAP[name]) { | |
LOCK_MAP[name] = true; | |
return next(val); | |
} | |
return setTimeout(function() { | |
return tryLock(next, val); | |
}, 100); | |
}; | |
return ns.promise(tryLock); | |
}; | |
from = function(value) { | |
if (value.observe) { | |
return ns.promise(function(next, val) { | |
return value.observe(ns.promise(function(n, v) { | |
return next(v); | |
})); | |
}); | |
} else { | |
return ns.promise(function(next, val) { | |
return next(value); | |
}); | |
} | |
}; | |
EMIT_INTERVAL_MAP = {}; | |
emitInterval = function(msec, name) { | |
var msecFunc; | |
msecFunc = typeof msec === 'function' ? msec : function() { | |
return msec; | |
}; | |
return ns.promise(function(next, val) { | |
var id; | |
id = setInterval(function() { | |
return next(val); | |
}, msecFunc()); | |
if (name) return EMIT_INTERVAL_MAP[name] = id; | |
}); | |
}; | |
stopEmitInterval = function(name) { | |
return ns.promise(function(next, value) { | |
clearInterval(EMIT_INTERVAL_MAP[name]); | |
return next(val); | |
}); | |
}; | |
return ns.provide({ | |
mapper: mapper, | |
through: through, | |
filter: filter, | |
scatter: scatter, | |
takeBy: takeBy, | |
wait: wait, | |
cond: cond, | |
match: match, | |
debug: debug, | |
lock: lock, | |
unlock: unlock, | |
from: from, | |
waitUntil: waitUntil, | |
emitInterval: emitInterval, | |
stopEmitInterval: stopEmitInterval | |
}); | |
}); | |
Namespace('brook.lambda').define(function(ns) { | |
var cache, hasArg, lambda, parseExpression; | |
cache = {}; | |
hasArg = function(expression) { | |
return expression.indexOf('->' >= 0); | |
}; | |
parseExpression = function(expression) { | |
var argsExp, bodyExp, fixed, splitted; | |
fixed = hasArg(expression) ? expression : "$->" + expression; | |
splitted = fixed.split('->'); | |
argsExp = splitted.shift(); | |
bodyExp = splitted.join('->'); | |
return { | |
argumentNames: argsExp.split(','), | |
body: hasArg(bodyExp) ? lambda(bodyExp).toString() : bodyExp | |
}; | |
}; | |
lambda = function(expression) { | |
var func, parsed; | |
if (cache[expression]) return cache[expression]; | |
parsed = parseExpression(expression); | |
func = new Function(parsed.argumentNames, "return (" + parsed.body + ");"); | |
cache[expression] = funct; | |
return func; | |
}; | |
return ns.provide({ | |
lambda: lambda | |
}); | |
}); | |
Namespace('brook.channel').use('brook promise').use('brook.util scatter').define(function(ns) { | |
var Channel, NAMED_CHANNEL, channel, getNamedChannel, indexOf, observeChannel, sendChannel, stopObservingChannel; | |
indexOf = function(list, value) { | |
var i, _ref; | |
for (i = 0, _ref = list.length; 0 <= _ref ? i <= _ref : i >= _ref; 0 <= _ref ? i++ : i--) { | |
if (list[i] === value) return i; | |
} | |
return -1; | |
}; | |
Channel = (function() { | |
function Channel() { | |
this.queue = []; | |
this.promises = []; | |
} | |
Channel.prototype.send = function(func) { | |
var _self; | |
func = func ? func : function(k) { | |
return k; | |
}; | |
_self = this; | |
return ns.promise(function(next, val) { | |
_self.sendMessage(func(val)); | |
return next(val); | |
}); | |
}; | |
Channel.prototype.sendMessage = function(msg) { | |
var makeRunner, message, runner, scatter, sendError; | |
scatter = ns.scatter(1000); | |
sendError = sendChannel('error'); | |
this.queue.push(msg); | |
makeRunner = function(message) { | |
return ns.promise(function(next, promise) { | |
return promise.run(message); | |
}); | |
}; | |
while (this.queue.length) { | |
message = this.queue.shift(); | |
runner = makeRunner(message); | |
runner.setErrorHandler(sendError); | |
scatter.bind(runner).run(this.promises); | |
} | |
}; | |
Channel.prototype.observe = function(promise) { | |
if (indexOf(this.promises, promise) > -1) return; | |
return this.promises.push(promise); | |
}; | |
Channel.prototype.stopObserving = function(promise) { | |
var index; | |
index = indexOf(this.promises, promise); | |
if (index > -1) return this.promises.splice(index, 1); | |
}; | |
return Channel; | |
})(); | |
channel = function(name) { | |
if (name) { | |
return getNamedChannel(name); | |
} else { | |
return new Channel(); | |
} | |
}; | |
NAMED_CHANNEL = {}; | |
getNamedChannel = function(name) { | |
if (NAMED_CHANNEL[name]) return NAMED_CHANNEL[name]; | |
NAMED_CHANNEL[name] = new Channel(); | |
return NAMED_CHANNEL[name]; | |
}; | |
observeChannel = function(name, promise) { | |
return getNamedChannel(name).observe(promise); | |
}; | |
stopObservingChannel = function(name, promise) { | |
return getNamedChannel(name).stopObserving(promise); | |
}; | |
sendChannel = function(name, func) { | |
var namedChannel; | |
namedChannel = getNamedChannel(name); | |
return namedChannel.send(func); | |
}; | |
return ns.provide({ | |
channel: channel, | |
sendChannel: sendChannel, | |
observeChannel: observeChannel, | |
stopObservingChannel: stopObservingChannel, | |
createChannel: function() { | |
return new Channel(); | |
} | |
}); | |
}); | |
Namespace('brook.model').use('brook promise').use('brook.util *').use('brook.channel *').use('brook.lambda *').define(function(ns) { | |
var Model, createModel; | |
Model = (function() { | |
function Model(obj) { | |
var prop; | |
this.methods = {}; | |
this.channels = {}; | |
for (prop in obj) { | |
if (obj.hasOwnProperty(prop)) this.addMethod(prop, obj[prop]); | |
} | |
} | |
Model.prototype.addMethod = function(method, promise) { | |
var channel; | |
if (this.methods[method]) throw "already " + method + " defined"; | |
channel = ns.createChannel(); | |
this.methods[method] = promise.bind(channel.send()); | |
this.channels[method] = channel; | |
return this; | |
}; | |
Model.prototype.notify = function(method) { | |
return ns.promise().bind(this.methods[method]); | |
}; | |
Model.prototype.method = function(method) { | |
if (!this.channels[method]) throw 'do not observe undefined method'; | |
return this.channels[method]; | |
}; | |
return Model; | |
})(); | |
createModel = function(obj) { | |
return new Model(obj); | |
}; | |
return ns.provide({ | |
createModel: createModel | |
}); | |
}); | |
Namespace('brook.dom.compat').define(function(ns) { | |
var ClassList, check, classList, dataset, getElementsByClassName, hasClassName; | |
dataset = (function() { | |
var camelize, proto, wrapper; | |
wrapper = function(element) { | |
return element.dataset; | |
}; | |
if (__indexOf.call(window, 'HTMLElement') >= 0 && HTMLElement.prototype) { | |
proto = HTMLElement.prototype; | |
if (proto.dataset) return wrapper; | |
if (proto.__lookupGetter__ && proto.__lookupGetter__('dataset')) { | |
return wrapper; | |
} | |
} | |
camelize = function(string) { | |
return string.replace(/-+(.)?/g, function(match, chr) { | |
if (chr) { | |
return chr.toUpperCase(); | |
} else { | |
return ''; | |
} | |
}); | |
}; | |
return function(element) { | |
var attr, sets, _i, _len, _ref; | |
sets = {}; | |
_ref = element.attributes; | |
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
attr = _ref[_i]; | |
if (attr.name.match(/^data-/)) { | |
sets[camelize(attr.name.replace(/^data-/, ''))] = attr.value; | |
} | |
} | |
return sets; | |
}; | |
})(); | |
check = function(token) { | |
if (token === '') throw 'SYNTAX_ERR'; | |
if (!token.indexOf(/\s/ === -1)) throw 'INVALID_CHARACTER_ERR'; | |
}; | |
ClassList = (function() { | |
function ClassList(element) { | |
this._element = element; | |
this._refresh(); | |
} | |
ClassList.prototype._fake = true; | |
ClassList.prototype._refresh = function() { | |
var classes; | |
classes = (this._element.className || '').split(/\s+/); | |
if (classes.length && classes[0] === '') classes.shift(); | |
if (classes.length && classes[classes.length - 1] === '') classes.pop(); | |
this._classList = classes; | |
this.length = classes.length; | |
return this; | |
}; | |
ClassList.prototype.item = function(i) { | |
return this._classList[i] || null; | |
}; | |
ClassList.prototype.contains = function(token) { | |
var i, _ref; | |
check(token); | |
for (i = 0, _ref = this.length; 0 <= _ref ? i <= _ref : i >= _ref; 0 <= _ref ? i++ : i--) { | |
if (this._classList[i] === token) return true; | |
} | |
return false; | |
}; | |
ClassList.prototype.add = function(token) { | |
var i, _ref; | |
check(token); | |
for (i = 0, _ref = this.length; 0 <= _ref ? i <= _ref : i >= _ref; 0 <= _ref ? i++ : i--) { | |
if (this._classList[i] === token) return; | |
} | |
this._classList.push(token); | |
this.length = this._classList.length; | |
return this._element.className = this._classList.join(' '); | |
}; | |
ClassList.prototype.remove = function(token) { | |
var i, _ref; | |
check(token); | |
for (i = 0, _ref = this._classList.length; 0 <= _ref ? i <= _ref : i >= _ref; 0 <= _ref ? i++ : i--) { | |
if (this._classList[i] === token) { | |
this._classList.splice(i, 1); | |
this._element.className = this._classList.join(' '); | |
} | |
} | |
return this.length = this._classList.length; | |
}; | |
ClassList.prototype.toggle = function(token) { | |
var i, _ref; | |
check(token); | |
for (i = 0, _ref = this.length; 0 <= _ref ? i <= _ref : i >= _ref; 0 <= _ref ? i++ : i--) { | |
if (this._classList[i] === token) { | |
this.remove(token); | |
return false; | |
} | |
} | |
this.add(token); | |
return true; | |
}; | |
return ClassList; | |
})(); | |
classList = function(element) { | |
return new ClassList(element); | |
}; | |
hasClassName = function(element, className) { | |
var classSyntax; | |
classSyntax = element.className; | |
if (!(classSyntax && className)) return false; | |
return new RegExp("(^|\\s)" + className + "(\\s|$)").test(classSyntax); | |
}; | |
getElementsByClassName = function(className) { | |
var allElements, element, ret, _i, _len; | |
if (document.getElementsByClassName) { | |
return document.getElementsByClassName(className); | |
} | |
allElements = document.getElementsByTagName('*'); | |
ret = []; | |
for (_i = 0, _len = allElements.length; _i < _len; _i++) { | |
element = allElements[_i]; | |
if (hasClassName(element, className)) ret.push(element); | |
} | |
return ret; | |
}; | |
return ns.provide({ | |
getElementsByClassName: getElementsByClassName, | |
hasClassName: hasClassName, | |
dataset: dataset, | |
classList: classList | |
}); | |
}); | |
Namespace('brook.dom.gateway').define(function(ns) { | |
return ns.provide({}); | |
}); | |
Namespace('brook.widget').use('brook promise').use('brook.channel *').use('brook.util *').use('brook.dom.compat *').define(function(ns) { | |
var TARGET_CLASS_NAME, applyNamespace, classList, dataset, elementsByClassName, errorChannel, mapByNamespace, mapToPairs, registerElements, removeClassName, updater, widgetChannel; | |
TARGET_CLASS_NAME = 'widget'; | |
classList = ns.classList; | |
dataset = ns.dataset; | |
widgetChannel = ns.channel('widget'); | |
errorChannel = ns.channel('error'); | |
removeClassName = function(className, element) { | |
return classList(element).remove(className); | |
}; | |
elementsByClassName = ns.promise(function(n, v) { | |
v = v || TARGET_CLASS_NAME; | |
return n([v, Array.prototype.slice.call(ns.getElementsByClassName(v))]); | |
}); | |
mapByNamespace = ns.promise(function(n, val) { | |
var data, map, targetClassName, widget, widgetElements, widgetNamespace, _i, _len; | |
targetClassName = val[0]; | |
widgetElements = val[1]; | |
map = {}; | |
for (_i = 0, _len = widgetElements.length; _i < _len; _i++) { | |
widget = widgetElements[_i]; | |
removeClassName(targetClassName || TARGET_CLASS_NAME, widget); | |
data = dataset(widget); | |
widgetNamespace = data.widgetNamespace; | |
if (!widgetNamespace) continue; | |
if (!map[widgetNamespace]) map[widgetNamespace] = []; | |
map[widgetNamespace].push([widget, data]); | |
} | |
return n(map); | |
}); | |
mapToPairs = ns.promise(function(n, map) { | |
var namespace, pairs; | |
pairs = []; | |
for (namespace in map) { | |
if (map.hasOwnProperty(namespace)) pairs.push([namespace, map[namespace]]); | |
} | |
return n(pairs); | |
}); | |
applyNamespace = ns.promise(function(n, pair) { | |
return Namespace.use([pair[0], '*'].join(' ')).apply(function(ns) { | |
return n([ns, pair[1]]); | |
}); | |
}); | |
registerElements = ns.promise(function(n, v) { | |
var elements, widget, widgets, _i, _j, _len, _len2, _ns; | |
_ns = v[0]; | |
widgets = v[1]; | |
try { | |
if (_ns.registerElement) { | |
for (_i = 0, _len = widgets.length; _i < _len; _i++) { | |
widget = widgets[_i]; | |
_ns.registerElement.apply(null, widget); | |
} | |
} else if (_ns.registerElements) { | |
elements = []; | |
for (_j = 0, _len2 = widgets.length; _j < _len2; _j++) { | |
widget = widgets[_j]; | |
elements.push(widget[0]); | |
} | |
_ns.registerElements(elements); | |
} else { | |
throw "registerElement or registerElements not defined in " + _ns.CURRENT_NAMESPACE; | |
} | |
} catch (e) { | |
errorChannel.sendMessage(e); | |
} | |
}); | |
updater = ns.promise().bind(ns.lock('class-seek'), elementsByClassName, mapByNamespace, mapToPairs, ns.unlock('class-seek'), ns.scatter(), applyNamespace, registerElements); | |
widgetChannel.observe(updater); | |
return ns.provide({ | |
bindAllWidget: widgetChannel.send() | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment