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