Skip to content

Instantly share code, notes, and snippets.

@kmnk
Created December 11, 2012 08:10
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/4256754 to your computer and use it in GitHub Desktop.
Save kmnk/4256754 to your computer and use it in GitHub Desktop.
brook.part.js (has no htmltemplate namespace)
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