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