Skip to content

Instantly share code, notes, and snippets.

@kmnk
Created December 11, 2012 08:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kmnk/4256731 to your computer and use it in GitHub Desktop.
Save kmnk/4256731 to your computer and use it in GitHub Desktop.
brook.coffee
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