language-ecmascript-0.10: tests/parse-pretty/flapjax-fxinternal-22dec2008.js
function flapjaxInit(options) {
// compress via http://alex.dojotoolkit.org/shrinksafe/
// make sure to change final eval call to use exported library renamed
// name
var defaultOptions = {
includeSynonyms: true,
exportMisc: true,
exportCore: true,
exportDOM: true,
exportDOMMisc: true,
exportWS: true,
hide: [ ],
show: [ ],
redefine: false
};
if (options === false) {
options = { hide: [ ], show: [ ] };
}
else if (options === true || !options) {
options = { };
}
// Fill in any missing options using the defaults.
for (var option in defaultOptions) {
if (options[option] === undefined) {
options[option] = defaultOptions[option];
}
};
// Transform hide and show lists into tables
var hideArray = options.hide;
options.hide = { };
for (var i = 0; i < hideArray.length; i++) {
options.hide[hideArray[i]] = true;
}
var showArray = options.show;
options.show = { };
for (var i = 0; i < showArray.length; i++) {
options.show[showArray[i]] = true;
}
var warn = function(s) {
/*if (console && console.warn) {
console.warn(s);
}*/
};
var flapjax = {
version: 4,
base: {}, //pulses
combinators: {}, // combinators yielding nodes
behaviours: {},
dom: {} // dom convenience methods and combinators
};
var d = flapjax.dom;
flapjax.pub = {util:flapjax};
var annotate = function(fn,names,protoArg,protoObjs,protoNames) {
for(var i=0; i<names.length;i++) {
flapjax.pub[names[i]] = fn;
}
if(protoArg != undefined) {
var pf = function() {
var args = slice(arguments,0);
args.splice(protoArg,0,this);
return fn.apply(this,args);
};
for(var i=0; i<protoObjs.length; i++) {
for(var j=0; j<protoNames.length;j++) {
protoObjs[i][protoNames[j]] = pf;
}
}
}
};
var exports = { };
var synonyms = { };
var misc = { }; /* Used as a symbol */
var core = { }; /* Used as a symbol */
var dom = { }; /* Used as a symbol */
var domMisc = { }; /* Used as a symbol */
var ws = { }; /* Used as a symbol */
var fxExport = function(exportCategory,exportVal,
exportName /* , synonyms */) {
if (!(exportCategory instanceof Object)) {
throw 'fxExport: category is ' + exportCategory;
};
if (typeof(exportName) != 'string') {
console.log(exportVal);
throw 'fxExport: name is ' + exportName;
};
exportVal.__flapjaxCategory = exportCategory;
exports[exportName] = exportVal;
for (var i = 3; i < arguments.length; i++) {
exports[arguments[i]] = exportVal;
synonyms[arguments[i]] = true;
}
};
var fxMethodSynonyms = function(obj,method) {
if (options.includeSynonyms === true) {
for (var i = 2; i < arguments.length; i++) {
obj.prototype[arguments[i]] = obj.prototype[method];
};
};
};
//////////////////////////////////////////////////////////////////////////////
// Miscellaneous functions
//credit 4umi
//slice: Array a * Integer * Integer -> Array a
var slice = function (arr, start, stop) {
var i, len = arr.length, r = [];
if( !stop ) { stop = len; }
if( stop < 0 ) { stop = len + stop; }
if( start < 0 ) { start = len - start; }
if( stop < start ) { i = start; start = stop; stop = i; }
for( i = 0; i < stop - start; i++ ) { r[i] = arr[start+i]; }
return r;
}
var isEqual = function (a,b) {
return (a == b) ||
( (((typeof(a) == 'number') && isNaN(a)) || a == 'NaN') &&
(((typeof(b) == 'number') && isNaN(b)) || b == 'NaN') );
};
//member: a * Array b -> Boolean
var member = function(elt, lst) {
for (var i = 0; i < lst.length; i++) {
if (isEqual(lst[i], elt)) {return true;}
}
return false;
};
var zip = function(arrays) {
if (arrays.length == 0) return [];
var ret = [];
for(var i=0; i<arrays[0].length;i++) {
ret.push([]);
for(var j=0; j<arrays.length;j++)
ret[i].push(arrays[j][i]);
}
return ret;
};
//map: (a * ... -> z) * [a] * ... -> [z]
var map = function (fn) {
var arrays = slice(arguments, 1);
if (arrays.length === 0) { return []; }
else if (arrays.length === 1) {
var ret = [];
for(var i=0; i<arrays[0].length; i++) {ret.push(fn(arrays[0][i]));}
return ret;
}
else {
var ret = zip(arrays);
var o = new Object();
for(var i=0; i<ret.length; i++) {ret[i] = fn.apply(o,ret[i]);}
return ret;
}
};
//filter: (a -> Boolean) * Array a -> Array a
var filter = function (predFn, arr) {
var res = [];
for (var i = 0; i < arr.length; i++) {
if (predFn(arr[i])) { res.push(arr[i]); }
}
return res;
};
//fold: (a * .... * accum -> accum) * accum * [a] * ... -> accum
//fold over list(s), left to right
var fold = function(fn, init /* arrays */) {
var lists = slice(arguments, 2);
if (lists.length === 0) { return init; }
else if(lists.length === 1) {
var acc = init;
for(var i = 0; i < lists[0].length; i++) {
acc = fn(lists[0][i],acc);
}
return acc;
}
else {
var acc = init;
for (var i = 0; i < lists[0].length; i++) {
var args = map( function (lst) { return lst[i];},
lists);
args.push(acc);
acc = fn.apply({}, args);
}
return acc;
}
};
//foldR: (a * .... * accum -> accum) * accum * [a] * ... -> accum
//fold over list(s), right to left, fold more memory efficient (left to right)
var foldR = function (fn, init /* arrays */) {
var lists = slice(arguments, 2);
if (lists.length === 0) { return init; }
else if(lists.length === 1) {
var acc = init;
for(var i=lists[0].length - 1; i > -1; i--)
acc = fn(lists[0][i],acc);
return acc;
}
else {
var acc = init;
for (var i = lists[0].length - 1; i > -1; i--) {
var args = map( function (lst) { return lst[i];},
lists);
args.push(acc);
acc = fn.apply({}, args);
}
return acc;
}
};
var pushAll = function(destArray,srcArray) {
for (var i = 0; i < srcArray.length; i++) {
destArray.push(srcArray[i]);
}
return destArray;
};
fxExport(misc,slice,'slice');
fxExport(misc,member,'member');
fxExport(misc,map,'map');
fxExport(misc,map,'forEach');
fxExport(misc,filter,'filter');
fxExport(misc,fold,'fold');
fxExport(misc,foldR,'foldR');
//////////////////////////////////////////////////////////////////////////////
// Flapjax core
//Pulse: Stamp * Path * Obj
var Pulse = function (stamp, value) {
this.stamp = stamp;
this.value = value;
};
//Probably can optimize as we expect increasing insert runs etc
var PQ = function () {
var ctx = this;
ctx.val = [];
this.insert = function (kv) {
ctx.val.push(kv);
var kvpos = ctx.val.length-1;
while(kvpos > 0 && kv.k < ctx.val[Math.floor((kvpos-1)/2)].k) {
var oldpos = kvpos;
kvpos = Math.floor((kvpos-1)/2);
ctx.val[oldpos] = ctx.val[kvpos];
ctx.val[kvpos] = kv;
}
};
this.isEmpty = function () {
return ctx.val.length === 0;
};
this.pop = function () {
if(ctx.val.length == 1) {
return ctx.val.pop();
}
var ret = ctx.val.shift();
ctx.val.unshift(ctx.val.pop());
var kvpos = 0;
var kv = ctx.val[0];
while(1) {
var leftChild = (kvpos*2+1 < ctx.val.length ? ctx.val[kvpos*2+1].k : kv.k+1);
var rightChild = (kvpos*2+2 < ctx.val.length ? ctx.val[kvpos*2+2].k : kv.k+1);
if(leftChild > kv.k && rightChild > kv.k)
break;
else if(leftChild < rightChild) {
ctx.val[kvpos] = ctx.val[kvpos*2+1];
ctx.val[kvpos*2+1] = kv;
kvpos = kvpos*2+1;
}
else {
ctx.val[kvpos] = ctx.val[kvpos*2+2];
ctx.val[kvpos*2+2] = kv;
kvpos = kvpos*2+2;
}
}
return ret;
};
};
var lastRank = 0;
var stamp = 1;
var nextStamp = function () { return ++stamp; };
//propagatePulse: Pulse * Array Node ->
//Send the pulse to each node
var propagatePulse = function (pulse, node) {
var queue = new PQ(); //topological queue for current timestep
queue.insert({k:node.rank,n:node,v:pulse});
while(!(queue.isEmpty())) {
var qv = queue.pop();
qv.n.updater(function(nextPulse) {
for(var i=0; i<qv.n.sendsTo.length;i++)
queue.insert({k:qv.n.sendsTo[i].rank,n:qv.n.sendsTo[i],v:nextPulse});
},new Pulse(qv.v.stamp,qv.v.value));
}
};
//Event: Array Node b * ( (Pulse a -> Void) * Pulse b -> Void)
var EventStream = function (nodes,updater) {
this.updater = updater;
this.sendsTo = []; //forward link
for (var i = 0; i < nodes.length; i++) {
nodes[i].sendsTo.push(this);
}
this.rank = ++lastRank;
};
EventStream.prototype = new Object();
fxExport(core,EventStream,'EventStream');
//createNode: Array Node a * ( (Pulse b ->) * (Pulse a) -> Void) -> Node b
var createNode = function (nodes, updater) {
return new EventStream(nodes,updater);
};
//attachListenerNode: Node * Node -> Voids
//flow from node to dependent
//note: does not add flow as counting for rank nor updates parent ranks
var attachListener = function (node, dependent) {
if (!(node instanceof EventStream)) { throw 'attachListenenerNode: expects event as first arg';} //SAFETY
if (!(dependent instanceof EventStream)) { throw 'attachListenenerNode: expects event as second arg';} //SAFETY
node.sendsTo.push(dependent);
if(node.rank > dependent.rank) {
var lowest = lastRank+1;
var q = [dependent];
while(q.length) {
var cur = q.splice(0,1)[0];
cur.rank = ++lastRank;
q = q.concat(cur.sendsTo);
}
}
};
//removeListenerNode: Node * Node -> Boolean
//remove flow from node to dependent
//note: does not remove flow as counting for rank nor updates parent ranks
var removeListener = function (node, dependent)
{
if (!(node instanceof EventStream)) { throw 'removeListenerNode: expects event as first arg';} //SAFETY
if (!(dependent instanceof EventStream)) { throw 'removeListenenerNode: expects event as second arg';} //SAFETY
var foundSending = false;
for (var i = 0; i < node.sendsTo.length && !foundSending; i++) {
if (node.sendsTo[i] == dependent) {
node.sendsTo.splice(i, 1);
foundSending = true;
}
}
return foundSending;
};
// An internal_e is a node that simply propagates all pulses it receives. It's used internally by various
// combinators.
var internal_e = function(dependsOn) {
return createNode(dependsOn || [ ],function(send,pulse) { send(pulse); });
}
var zero_e = function() {
return createNode([],function(send,pulse) {
throw ('zero_e : received a value; zero_e should not receive a value; the value was ' + pulse.value);
});
};
fxExport(core,zero_e,'zero_e');
var one_e = function(val) {
var sent = false;
var evt = createNode([],function(send,pulse) {
if (sent) {
throw ('one_e : received an extra value');
}
sent = true;
send(pulse);
});
window.setTimeout(function() { sendEvent(evt,val); },0);
return evt;
};
fxExport(core,one_e,'one_e');
// a.k.a. mplus; merge_e(e1,e2) == merge_e(e2,e1)
var merge_e = function() {
if (arguments.length == 0) {
return zero_e();
}
else {
var deps = slice(arguments,0);
return internal_e(deps);
}
};
fxExport(core,merge_e,'merge_e');
EventStream.prototype.merge_e = function() {
var deps = slice(arguments,0);
deps.push(this);
return internal_e(deps);
};
fxMethodSynonyms(EventStream,'merge_e','merge');
EventStream.prototype.constant_e = function(constantValue) {
return createNode([this],function(send,pulse) {
pulse.value = constantValue;
send(pulse);
});
};
fxMethodSynonyms(EventStream,'constant_e','constant','replaceValue_e','replaceValue');
var constant_e = function(e) { return e.constant_e(); };
fxExport(core,constant_e,'constant_e','replaceValue_e');
var createTimeSyncNode = function(nodes) {
var nqs = map(function(n) {
var qpulse = [];
return {q:qpulse,v:createNode([n],function(s,p) {qpulse.push(p.value); s(p);},nodes)};
},nodes);
return createNode(
map(function(n) {return n.v;},nqs), function(s,p) {
var allfull = fold(function(n,acc) {return n.q.length && acc;},true,nqs);
if(allfull) {
p.value = map(function(n) {return n.q.shift();},nqs);
s(p);
}});
};
//This is up here so we can add things to its prototype that are in flapjax.combinators
var Behaviour = function (event, init, updater) {
if (!(event instanceof EventStream)) {
throw 'Behaviour: expected event as second arg';
}
var behave = this;
this.last = init;
//sendEvent to this might impact other nodes that depend on this event
//sendBehaviour defaults to this one
this.underlyingRaw = event;
//unexposed, sendEvent to this will only impact dependents of this behaviour
this.underlying =
createNode(
[event],
(updater ?
function (s, p) {behave.last = updater(p.value); p.value = behave.last; s(p);} :
function (s, p) {behave.last = p.value; s(p);}));
};
Behaviour.prototype = new Object();
fxExport(core,Behaviour,'Behaviour');
fxExport(core,Behaviour,'Behavior');
var receiver_e = function() {
var evt = internal_e();
evt.sendEvent = function(value) {
propagatePulse(new Pulse(nextStamp(), value),evt);
};
return evt;
};
fxExport(core,receiver_e,'receiver_e');
//note that this creates a new timestamp and new event queue
var sendEvent = function (node, value) {
if (!(node instanceof EventStream)) { throw 'sendEvent: expected Event as first arg'; } //SAFETY
propagatePulse(new Pulse(nextStamp(), value),node);
};
fxExport(core,sendEvent,'sendEvent');
synonyms['sendEvent'] = true; // Hack: sendEvent simply should not be exported; calling it a synonym deprecates it.
// bind_e :: EventStream a * (a -> EventStream b) -> EventStream b
EventStream.prototype.bind_e = function(k) {
/* m.sendsTo result_e
* result_e.sendsTo prev_e
* prev_e.sendsTo return_e
*/
var m = this;
var prev_e = false;
var out_e = createNode([],function(send,pulse) { send(pulse); });
out_e.name = "bind out_e";
var in_e = createNode([m], function (send,pulse) {
if (prev_e) {
removeListener(prev_e,out_e);
}
prev_e = k(pulse.value);
if (prev_e instanceof EventStream) {
attachListener(prev_e,out_e);
}
else {
throw "bind_e : expected EventStream";
}
});
in_e.name = "bind in_e";
return out_e;
};
/* Could be written as:
*
* e.bind_e(function(v) { return one_e(f(v)); })
*/
EventStream.prototype.lift_e = function(f) {
if (!(f instanceof Function)) {
throw ('lift_e : expected a function as the first argument; received ' + f);
};
return createNode([this],function(send,pulse) {
pulse.value = f(pulse.value);
send(pulse);
});
};
fxMethodSynonyms(EventStream,'lift_e',
'transform_e','map_e','apply_e','lift','transform','map','apply');
EventStream.prototype.not_e = function() { return this.lift_e(function(v) { return !v; }); };
fxMethodSynonyms(EventStream,'not_e','not');
var not_e = function(e) { return e.not_e(); };
fxExport(core,not_e,'not_e','not');
EventStream.prototype.filter_e = function(pred) {
if (!(pred instanceof Function)) {
throw ('filter_e : expected predicate; received ' + pred);
};
// Can be a bind_e
return createNode([this],
function(send,pulse) {
if (pred(pulse.value)) { send(pulse); }
});
};
fxMethodSynonyms(EventStream,'filter_e','filter');
var filter_e = function(e,p) { return e.filter_e(p); };
fxExport(core,filter_e,'filter_e');
// Fires just once.
EventStream.prototype.once_e = function() {
var done = false;
// Alternately: this.collect_e(0,\n v -> (n+1,v)).filter_e(\(n,v) -> n == 1).lift_e(fst)
return createNode([this],function(send,pulse) {
if (!done) { done = true; send(pulse); }
});
};
fxMethodSynonyms(EventStream,'once_e','once');
var once_e = function(e) { return e.once_e(); };
fxExport(core,once_e,'once_e');
EventStream.prototype.skipFirst_e = function() {
var skipped = false;
return createNode([this],function(send,pulse) {
if (skipped)
{ send(pulse); }
else
{ skipped = true; }
});
};
fxMethodSynonyms(EventStream,'skipFirst_e','skipFirst');
var skipFirst_e = function(e) { return e.skipFirst_e(); };
fxExport(core,skipFirst_e,'skipFirst_e');
EventStream.prototype.collect_e = function(init,fold) {
var acc = init;
return this.lift_e(
function (n) {
var next = fold(n, acc);
acc = next;
return next;
});
};
fxMethodSynonyms(EventStream,'collect_e','collect','transformWithMemory');
var collect_e = function(e,i,f) { return e.collect_e(i,f); };
fxExport(core,collect_e,'collect_e');
// a.k.a. join
EventStream.prototype.switch_e = function() {
return this.bind_e(function(v) { return v; });
};
fxMethodSynonyms(EventStream,'switch_e','forwardLatest');
var switch_e = function(e) { return e.switch_e(); };
fxExport(core,switch_e,'switch_e');
EventStream.prototype.if_e = function(thenE,elseE) {
var testStamp = -1;
var testValue = false;
createNode([this],function(_,pulse) { testStamp = pulse.stamp; testValue = pulse.value; });
return merge_e(
createNode([thenE],function(send,pulse) { if (testValue && (testStamp == pulse.stamp)) { send(pulse); } }),
createNode([elseE],function(send,pulse) { if (!testValue && (testStamp == pulse.stamp)) { send(pulse); } }));
};
fxMethodSynonyms(EventStream,'if_e','choose_e','choose');
var if_e = function(test,thenE,elseE) {
if (test instanceof EventStream)
{ return test.if_e(thenE,elseE); }
else
{ return test ? thenE : elseE; }
};
fxExport(core,if_e,'if_e','choose_e');
var cond_e = function (/*. predValArrays */) {
var predValArrays = slice(arguments, 0);
var acc = zero_e();
for (var i = predValArrays.length - 1; i > -1; i--) {
acc = if_e(predValArrays[i][0],predValArrays[i][1], acc);
}
return acc;
};
fxExport(core,cond_e,'cond_e');
var and_e = function (/* . nodes */) {
var nodes = slice(arguments, 0);
var acc = (nodes.length > 0)?
nodes[nodes.length - 1] : one_e(true);
for (var i = nodes.length - 2; i > -1; i--) {
acc = if_e(
nodes[i],
acc,
nodes[i].constant_e(false));
}
return acc;
};
fxExport(core,and_e,'and_e');
EventStream.prototype.and_e = function( /* others */ ) {
var deps = [this].concat(slice(arguments,0));
return and_e.apply(this,deps);
};
fxMethodSynonyms(EventStream,'and_e','and');
var or_e = function () {
var nodes = slice(arguments, 0);
var acc = (nodes.length > 2)?
nodes[nodes.length - 1] : one_e(false);
for (var i = nodes.length - 2; i > -1; i--) {
acc = if_e(
nodes[i],
nodes[i],
acc);
}
return acc;
};
fxExport(core,or_e,'or_e');
EventStream.prototype.or_e = function(/*others*/) {
var deps = [this].concat(slice(arguments,0));
return or_e.apply(this,deps);
};
fxMethodSynonyms(EventStream,'or_e','or');
var delayStatic_e = function (event, time) {
var resE = internal_e();
createNode(
[event],
function (s, p) {
setTimeout(
function () { sendEvent(resE, p.value);},
time ); });
return resE;
};
//delay_e: Event a * [Behaviour] Number -> Event a
EventStream.prototype.delay_e = function (time) {
var event = this;
if (time instanceof Behaviour) {
var receiverEE = internal_e();
var link =
{
from: event,
towards: delayStatic_e(event, valueNow(time))
};
//TODO: Change semantics such that we are always guaranteed to get an event going out?
var switcherE =
createNode(
[changes(time)],
function (s, p) {
removeListener(link.from, link.towards);
link =
{
from: event,
towards: delayStatic_e(event, p.value)
};
sendEvent(receiverEE, link.towards);
});
var resE = receiverEE.switch_e();
sendEvent(switcherE, valueNow(time));
return resE;
} else { return delayStatic_e(event, time); }
};
fxMethodSynonyms(EventStream,'delay_e','delay');
var delay_e = function(sourceE,interval) {
return sourceE.delay_e(interval);
};
fxExport(core,delay_e,'delay_e');
//lift_e: ([Event] (. Array a -> b)) . Array [Event] a -> [Event] b
var lift_e = function (fn /*, [node0 | val0], ...*/) {
// if (!(fn instanceof Function)) { throw 'lift_e: expected fn as second arg'; } //SAFETY
var valsOrNodes = slice(arguments, 0);
//selectors[i]() returns either the node or real val, optimize real vals
var selectors = [];
var selectI = 0;
var nodes = [];
for (var i = 0; i < valsOrNodes.length; i++) {
if (valsOrNodes[i] instanceof EventStream) {
nodes.push(valsOrNodes[i]);
selectors.push(
(function(ii) {
return function(realArgs) {
return realArgs[ii];
};
})(selectI));
selectI++;
} else {
selectors.push(
(function(aa) {
return function () {
return aa;
};
})(valsOrNodes[i]));
}
}
var context = this;
var nofnodes = slice(selectors,1);
if (nodes.length === 0) {
return one_e(fn.apply(context, valsOrNodes));
} else if ((nodes.length === 1) && (fn instanceof Function)) {
return nodes[0].lift_e(
function () {
var args = arguments;
return fn.apply(
context,
map(function (s) {return s(args);}, nofnodes));
});
} else if (nodes.length === 1) {
return fn.lift_e(
function (v) {
var args = arguments;
return v.apply(
context,
map(function (s) {return s(args);}, nofnodes));
});
} else if (fn instanceof Function) {
return createTimeSyncNode(nodes).lift_e(
function (arr) {
return fn.apply(
this,
map(function (s) { return s(arr); }, nofnodes));
});
} else if (fn instanceof EventStream) {
return createTimeSyncNode(nodes).lift_e(
function (arr) {
return arr[0].apply(
this,
map(function (s) {return s(arr); }, nofnodes));
});
} else {throw 'unknown lift_e case';}
};
fxExport(core,lift_e,'lift_e','transform_e','map_e','apply_e');
EventStream.prototype.snapshot_e = function (valueB) {
return createNode(
[this],
function (s, p) {
p.value = valueNow(valueB);
s(p);
}
);
};
fxMethodSynonyms(EventStream,'snapshot_e','snapshot','takeSnapshot');
var snapshot_e = function(triggerE,valueB) {
return triggerE.snapshot_e(valueB);
};
fxExport(core,snapshot_e,'snapshot_e');
EventStream.prototype.filterRepeats_e = function(optStart) {
var hadFirst = optStart === undefined ? false : true;
var prev = optStart;
return this.filter_e(function (v) {
if (!hadFirst || !(isEqual(prev,v))) {
hadFirst = true;
prev = v;
return true;
}
else {
return false;
}
});
};
fxMethodSynonyms(EventStream,'filterRepeats_e','filterRepeats');
var filterRepeats_e = function(sourceE,optStart) {
return sourceE.filterRepeats_e(optStart);
};
fxExport(core,filterRepeats_e,'filterRepeats_e','filterRepeats');
//credit Pete Hopkins
var calmStatic_e = function (triggerE, time) {
var out = internal_e();
createNode(
[triggerE],
function() {
var towards = null;
return function (s, p) {
if (towards !== null) { clearTimeout(towards); }
towards = setTimeout( function () { towards = null; sendEvent(out,p.value) }, time );
};
}());
return out;
};
//calm_e: Event a * [Behaviour] Number -> Event a
EventStream.prototype.calm_e = function(time) {
if (time instanceof Behaviour) {
var out = internal_e();
createNode(
[this],
function() {
var towards = null;
return function (s, p) {
if (towards !== null) { clearTimeout(towards); }
towards = setTimeout( function () { towards = null; sendEvent(out,p.value);}, valueNow(time));
};
}());
return out;
} else {
return calmStatic_e(this,time);
}
};
fxMethodSynonyms(EventStream,'calm_e','calm');
var calm_e = function(sourceE,interval) {
return sourceE.calm_e(interval);
};
fxExport(core,calm_e,'calm_e');
EventStream.prototype.blind_e = function (time) {
return createNode(
[this],
function () {
var intervalFn =
time instanceof Behaviour?
function () { return valueNow(time); }
: function () { return time; };
var lastSent = (new Date()).getTime() - intervalFn() - 1;
return function (s, p) {
var curTime = (new Date()).getTime();
if (curTime - lastSent > intervalFn()) {
lastSent = curTime;
s(p);
}
};
}());
};
fxMethodSynonyms(EventStream,'blind_e','blind');
var blind_e = function(sourceE,interval) {
return sourceE.blind_e(interval);
};
fxExport(core,blind_e,'blind_e');
EventStream.prototype.hold = function(init) {
return new Behaviour(this,init);
};
fxMethodSynonyms(EventStream,'hold','startsWith','toBehavior');
var hold = function(e,init) {
if (!(e instanceof EventStream)) {
throw 'hold: expected EventStream; received ' + e;
}
return e.hold(init);
};
fxExport(core,hold,'hold');
Behaviour.prototype.valueNow = function() {
return this.last;
};
var valueNow = function(behavior) { return behavior.valueNow(); };
fxExport(core,valueNow,'valueNow');
Behaviour.prototype.changes = function() {
return this.underlying;
};
fxMethodSynonyms(Behaviour,'changes','toEvent');
var changes = function (behave) { return behave.changes(); };
fxExport(core,changes,'changes');
Behaviour.prototype.switch_b = function() {
var behaviourCreatorsB = this;
var init = valueNow(behaviourCreatorsB);
var prevSourceE = null;
var receiverE = new internal_e();
//XXX could result in out-of-order propagation! Fix!
var makerE =
createNode(
[changes(behaviourCreatorsB)],
function (_, p) {
if (!(p.value instanceof Behaviour)) { throw 'switch_b: expected Behaviour as value of Behaviour of first argument'; } //SAFETY
if (prevSourceE != null) {
removeListener(prevSourceE, receiverE);
}
prevSourceE = changes(p.value);
attachListener(prevSourceE, receiverE);
sendEvent(receiverE, valueNow(p.value));
});
if (init instanceof Behaviour) {
sendEvent(makerE, init);
}
return hold(
receiverE,
init instanceof Behaviour? valueNow(init) : init);
};
fxMethodSynonyms(Behaviour,'switch_b','forwardLatest');
var switch_b = function (b) { return b.switch_b(); };
fxExport(core,switch_b,'switch_b');
//TODO test, signature
var timer_b = function(interval) {
return hold(timer_e(interval), (new Date()).getTime());
};
fxExport(dom,timer_b,'timer_b','asTimer_b');
//TODO test, signature
var delayStatic_b = function (triggerB, time, init) {
return hold(delayStatic_e(changes(triggerB), time), init);
};
//TODO test, signature
Behaviour.prototype.delay_b = function (time, init) {
var triggerB = this;
if (time instanceof Behaviour) {
return hold(
delay_e(
changes(triggerB),
time),
arguments.length > 3 ? init : valueNow(triggerB));
} else {
return delayStatic_b(
triggerB,
time,
arguments.length > 3 ? init : valueNow(triggerB));
}
};
fxMethodSynonyms(Behaviour,'delay_b','delay');
var delay_b = function(srcB, timeB, init) {
return srcB.delay_b(timeB,init);
};
fxExport(core,delay_b,'delay_b');
//artificially send a pulse to underlying event node of a behaviour
//note: in use, might want to use a receiver node as a proxy or an identity map
Behaviour.prototype.sendBehaviour = function(val) {
sendEvent(this.underlyingRaw,val);
};
Behaviour.prototype.sendBehavior = Behaviour.prototype.sendBehaviour;
var sendBehaviour = function (b,v) { b.sendBehaviour(v); };
fxExport(core,sendBehaviour,'sendBehaviour');
fxExport(core,sendBehaviour,'sendBehavior');
Behaviour.prototype.if_b = function(trueB,falseB) {
var testB = this;
//TODO auto conversion for behaviour funcs
if (!(trueB instanceof Behaviour)) { trueB = constant_b(trueB); }
if (!(falseB instanceof Behaviour)) { falseB = constant_b(falseB); }
return lift_b(function(te,t,f) { return te ? t : f; },testB,trueB,falseB);
};
fxMethodSynonyms(Behaviour,'if_b','choose_b','choose');
var if_b = function(test,cons,altr) {
if (!(test instanceof Behaviour)) { test = constant_b(test); };
return test.if_b(cons,altr);
};
fxExport(core,if_b,'if_b','choose_b');
//cond_b: . [Behaviour boolean, Behaviour a] -> Behaviour a
var cond_b = function (/* . pairs */ ) {
var pairs = slice(arguments, 0);
return lift_b.apply({},[function() {
for(var i=0;i<pairs.length;i++) {
if(arguments[i]) return arguments[pairs.length+i];
}
return undefined;
}].concat(map(function(pair) {return pair[0];},pairs).concat(map(function(pair) {return pair[1];},pairs))));
};
fxExport(core,cond_b,'cond_b');
//TODO optionally append to objects
//createConstantB: a -> Behaviour a
var constant_b = function (val) {
return new Behaviour(internal_e(), val);
};
fxExport(core,constant_b,'constant_b','receiver_b');
var lift_b = function (fn /* . behaves */) {
var args = slice(arguments, 1);
//dependencies
var constituentsE =
map(changes,
filter(function (v) { return v instanceof Behaviour; },
arguments));
//calculate new vals
var getCur = function (v) {
return v instanceof Behaviour ? v.last : v;
};
var ctx = this;
var getRes = function () {
return getCur(fn).apply(ctx, map(getCur, args));
};
if(constituentsE.length == 1) {
return new Behaviour(constituentsE[0],getRes(),getRes);
}
//gen/send vals @ appropriate time
var prevStamp = -1;
var mid = createNode(constituentsE, function (s, p) {
if (p.stamp != prevStamp) {
prevStamp = p.stamp;
s(p);
}});
return new Behaviour(mid,getRes(),getRes);
};
fxExport(core,lift_b,'lift_b','transform_b','apply_b');
Behaviour.prototype.lift_b = function(/* args */) {
var args= slice(arguments,0).concat([this]);
return lift_b.apply(this,args);
};
fxMethodSynonyms(Behaviour,'lift_b','transform_b','apply_b','transform',
'lift');
var and_b = function (/* . behaves */) {
return lift_b.apply({},[function() {
for(var i=0; i<arguments.length; i++) {if(!arguments[i]) return false;}
return true;
}].concat(slice(arguments,0)));
};
fxExport(core, and_b, 'and_b');
Behaviour.prototype.and_b = function() {
return and_b([this].concat(arguments));
};
fxMethodSynonyms(Behaviour,'and_b','and');
var or_b = function (/* . behaves */ ) {
return lift_b.apply({},[function() {
for(var i=0; i<arguments.length; i++) {if(arguments[i]) return true;}
return false;
}].concat(slice(arguments,0)));
};
fxExport(core,or_b,'or_b');
Behaviour.prototype.or_b = function () {
return or_b([this].concat(arguments));
};
fxMethodSynonyms(Behaviour, 'or_b', 'or');
Behaviour.prototype.not_b = function() {
return this.lift_b(function(v) { return !v; });
};
fxMethodSynonyms(Behaviour,'not_b','not');
var not_b = function(b) { return b.not_b(); };
fxExport(core,not_b,'not_b');
Behaviour.prototype.blind_b = function (intervalB) {
return changes(this).blind_e(intervalB).hold(this.valueNow());
};
fxMethodSynonyms(Behaviour,'blind_b','blind');
var blind_b = function(srcB,intervalB) {
return srcB.blind_b(intervalB);
};
fxExport(core,blind_b,'blind_b');
Behaviour.prototype.calm_b = function (intervalB) {
return this.changes().calm_e(intervalB).hold(this.valueNow());
};
fxMethodSynonyms(Behaviour,'calm_b','calm');
var calm_b = function (srcB,intervalB) {
return srcB.calm_b(intervalB);
};
fxExport(core,calm_b,'calm_b');
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// DOM Utilities
//credit Scott Andrew
//usage: addEvent(myDomObj, "mouseover", event->void )
//warning: do not use 'this' as meaning depends on browser (myDomObj vs window)
//addEvent: Dom * String DomEventEnum * (DomEvent -> a) -> Void
var addEvent = function (obj, evType, fn) {
//TODO encode mouseleave evt, formalize new evt interface
if (obj.addEventListener) {
obj.addEventListener(evType, fn, false); //TODO true fails on Opera
return true;
} else if (obj.attachEvent) {
//some reason original had code bloat here
return obj.attachEvent("on"+evType, fn);
} else {
return false;
}
};
fxExport(domMisc,addEvent,'addEvent');
// The Flapjax library does not use this function
//credit Dustin Diaz
//note: node/tag optional
//getElementsByClass: Regexp CSSSelector * Dom * String DomNodeEnum -> Array Dom
var getElementsByClass = function (searchClass, node, tag) {
var classElements = [];
if ( (node === null) || (node === undefined) ) { node = document; }
if ( (tag === null) || (tag === undefined) ) { tag = '*'; }
var els = node.getElementsByTagName(tag);
var elsLen = els.length;
var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)");
for (var i = 0, j = 0; i < elsLen; i++) {
if ( pattern.test(els[i].className) ) {
classElements.push(els[i]);
}
}
return classElements;
};
fxExport(domMisc,getElementsByClass,'getElementsByClass','$$');
//assumes IDs already preserved
//swapDom: (Dom a U String) [* (Dom b U String)] -> Dom a
var swapDom = function(replaceMe, withMe) {
if ((replaceMe === null) || (replaceMe === undefined)) { throw ('swapDom: expected dom node or id, received: ' + replaceMe); } //SAFETY
var replaceMeD = getObj(replaceMe);
if (!(replaceMeD.nodeType > 0)) { throw ('swapDom expected a Dom node as first arg, received ' + replaceMeD); } //SAFETY
if (withMe) {
var withMeD = getObj(withMe);
if (!(withMeD.nodeType > 0)) { throw 'swapDom: can only swap with a DOM object'; } //SAFETY
try {
if(withMeD != replaceMeD) replaceMeD.parentNode.replaceChild(withMeD, replaceMeD);
} catch (e) {
throw('swapDom error in replace call: withMeD: ' + withMeD + ', replaceMe Parent: ' + replaceMeD + ', ' + e + ', parent: ' + replaceMeD.parentNode);
}
} else {
replaceMeD.parentNode.removeChild(replaceMeD); //TODO isolate child and set innerHTML to "" to avoid psuedo-leaks?
}
return replaceMeD;
};
fxExport(domMisc,swapDom,'swapDom');
//getObj: String U Dom -> Dom
//throws
// 'getObj: expects a Dom obj or Dom id as first arg'
// 'getObj: flapjax: cannot access object'
// 'getObj: no obj to get
//also known as '$'
//TODO Maybe alternative?
var getObj = function (name) {
if (typeof(name) == 'object') { return name; }
else if ((typeof(name) == 'null') || (typeof(name) == 'undefined')) {
throw 'getObj: expects a Dom obj or Dom id as first arg';
} else {
var res =
document.getElementById ? document.getElementById(name) :
document.all ? document.all[name] :
document.layers ? document.layers[name] :
(function(){ throw 'getObj: flapjax: cannot access object';})();
if ((res === null) || (res === undefined)) {
throw ('getObj: no obj to get: ' + name);
}
return res;
}
};
fxExport(domMisc,getObj,'getObj');
fxExport(domMisc,getObj,'$');
//helper to reduce obj look ups
//getDynObj: domNode . Array (id) -> domObj
//obj * [] -> obj
//obj * ['position'] -> obj
//obj * ['style', 'color'] -> obj.style
var getMostDom = function (domObj, indices) {
var acc = getObj(domObj);
if ( (indices === null) || (indices === undefined) || (indices.length < 1)) {
return acc;
} else {
for (var i = 0; i < indices.length - 1; i++) {
acc = acc[indices[i]];
}
return acc;
}
};
var getDomVal = function (domObj, indices) {
var val = getMostDom(domObj, indices);
if (indices && indices.length > 0) {
val = val[indices[indices.length - 1]];
}
return val;
};
//TODO: manual timer management stinks.
// TODO: Name turn off or somethin
var ___timerID = 0;
var __getTimerId = function () { return ++___timerID; };
var timerDisablers = [];
var disableTimerNode = function (node) { timerDisablers[node.__timerId](); };
var disableTimer = function (v) {
if (v instanceof Behaviour) {
disableTimerNode(v.underlyingRaw);
} else if (v instanceof EventStream) {
disableTimerNode(v);
}
};
fxExport(dom,disableTimer,'disableTimer');
var createTimerNodeStatic = function (interval) {
var node = internal_e();
node.__timerId = __getTimerId();
var fn = function () { sendEvent(node, (new Date()).getTime());};
var timer = setInterval(fn, interval);
timerDisablers[node.__timerId] = function () {clearInterval(timer); };
return node;
};
var timer_e = function (interval) {
if (interval instanceof Behaviour) {
var receiverE = internal_e();
//the return
var res = receiverE.switch_e();
//keep track of previous timer to disable it
var prevE = createTimerNodeStatic(valueNow(interval));
//init
sendEvent(receiverE, prevE);
//interval changes: collect old timer
createNode(
[changes(interval)],
function (_, p) {
disableTimerNode(prevE);
prevE = createTimerNodeStatic(p.value);
sendEvent(receiverE, prevE);
});
res.__timerId = __getTimerId();
timerDisablers[res.__timerId] = function () {
disableTimerNode[prevE]();
return;
};
return res;
} else {
return createTimerNodeStatic(interval);
}
};
fxExport(dom,timer_e,'timer_e','asTimer_e');
d.TagB = function(tagName,args) {
this.resE = internal_e();
this.currentTag = document.createElement(tagName);
this.extractParameters(args);
this.insertChildrenNodes();
this.styleHooks = [];
this.styleChangedE = internal_e();
var ctx = this;
this.styleChangedE.lift_e(function(_) {
var oldTag = ctx.currentTag;
ctx.currentTag = document.createElement(tagName);
while (oldTag.firstChild) {
ctx.currentTag.appendChild(oldTag.removeChild(oldTag.firstChild));
}
while(ctx.styleHooks.length) removeListener(ctx.styleHooks.pop(),ctx.styleChangedE);
ctx.enstyle(ctx.currentTag,ctx.attribs);
sendEvent(ctx.resE, ctx.currentTag);
});
this.enstyle(this.currentTag,this.attribs);
this.resB = hold(this.resE, this.currentTag);
};
d.TagB.prototype = {
// Array [[Behaviour] Object *] [[Behaviour] Array] [Behaviour] Dom U String U undefined
// --> {attribs: Array [Behaviour] Object, arrs: Array [Behaviour] Array [Behaviour] Dom }
// split an arguments array into:
// 1. arrs: (coalesced, and possibly time varying) arrays of dom objects
// 2. attribs: attribute objects
extractParameters: function(args) {
this.arrs = [];
var attribs = [];
var curarr = [];
this.arrs.push(curarr);
for (var i = 0; i < args.length; i++) {
if (args[i] instanceof Behaviour) {
var vn = valueNow(args[i]);
if (vn instanceof Array) {
this.arrs.push(args[i]);
curarr = [];
this.arrs.push(curarr);
} else {
if ( ((typeof(vn) == 'object') && (vn.nodeType == 1)) ||
(typeof(vn) == 'string') || (vn == undefined)) {
curarr.push(args[i]);
} else if (typeof(vn) == 'object') {
attribs.push(args[i]);
}
else { throw ('createParameterizedTagB: unknown behaviour argument argument ' + i); } //SAFETY
}
} else {
if (args[i] instanceof Array) {
var arr = args[i];
for (var j = 0; j < arr.length; j++) { curarr.push(arr[j]); }
} else {
var vn = args[i];
if ( ((typeof(vn) == 'object') && (vn.nodeType == 1)) ||
(typeof(vn) == 'string') || (vn == undefined)) {
curarr.push(args[i]);
} else if (typeof(vn) == 'object') {
attribs.push(args[i]);
}
}
}
};
if(attribs.length > 1) throw ('createParameterizedTagB ' + tagName + ': more than one attribute (' + attribs.length + ')');
this.attribs = attribs.length > 0 ? attribs[0] : {};
},
insertChildrenNodes: function() {
var ctx = this;
function quickNode(e) {
if ((typeof(e) == 'object') && (e.nodeType))
return e;
else if ( e == undefined )
return document.createTextNode('');
else
return document.createTextNode(e);
}
function unBehaviourize(arr) {
return map(function(n) {return (n instanceof Behaviour) ? valueNow(n) : n;},arr)
}
var lnodes = map(function() {return [];},this.arrs);
var arrLastVals = map(unBehaviourize,unBehaviourize(this.arrs));
var arrChangesE = internal_e();
var nodeChangesE = internal_e();
function attachNodes(i,arr) {
for(var j=0;j<arr.length;j++) {
var cnode = arr[j];
if(cnode instanceof Behaviour) {
var newnode = (function(jj) {
return changes(cnode).lift_e(function(n) {return {index:i,jdex:jj,val:n};});
})(j);
lnodes[i].push(newnode);
attachListener(newnode,nodeChangesE);
cnode = valueNow(cnode);
}
}
}
var childChangesE = merge_e(
// Behaviour arrays change
arrChangesE.lift_e(function(ai) {
var i = ai.index;
var newarr = ai.val;
while(lnodes[i].length) {
var ln = lnodes[i].pop();
removeListener(ln,nodeChangesE);
}
var newvals = map(function(n) {return quickNode(n);},unBehaviourize(newarr));
for(var j=0;j<arrLastVals[i].length;j++)
try {
ctx.currentTag.removeChild(arrLastVals[i][j]);
}
catch(e) {}
if(newvals.length) {
var nextNode = null;
for(ii = i+1; ii < arrLastVals.length && !(arrLastVals[ii].length) ; ii++);
if(ii < arrLastVals.length) nextNode = arrLastVals[ii][0];
for(var j=0; j<newvals.length; j++) ctx.currentTag.insertBefore(newvals[j],nextNode);
}
arrLastVals[i] = newvals;
attachNodes(i,newarr);
return ctx.currentTag;
}),
// Behaviour nodes change
nodeChangesE.lift_e(function(ni) {
var i = ni.index;
var j = ni.jdex;
var newnode = quickNode(ni.val);
swapDom(arrLastVals[i][j],newnode);
arrLastVals[i][j] = newnode;
return ctx.currentTag;
}));
childChangesE.lift_e(function(cc) {sendEvent(ctx.resE,cc);});
for(var i=0; i<this.arrs.length;i++) {
for(var j=0; j<arrLastVals[i].length; j++) {
arrLastVals[i][j] = quickNode(arrLastVals[i][j]);
this.currentTag.appendChild(arrLastVals[i][j]);
}
if(this.arrs[i] instanceof Behaviour) {
attachNodes(i,valueNow(this.arrs[i]));
var newnode = (function(ii) {return changes(ctx.arrs[ii]).lift_e(function(na) {return {index:ii,val:na};});})(i);
attachListener(newnode,arrChangesE);
}
else {
attachNodes(i,this.arrs[i]);
}
}
},
enstyle: function(obj,vals) {
//build & record hook if dynamic collection
if (vals instanceof Behaviour) {
if (!(typeof(valueNow(vals)) == 'object')) { throw 'enstyle: expected object literal as behaviour value'; } //SAFETY
this.styleHooks.push(changes(vals));
attachListener(changes(vals),this.styleChangedE);
}
var valsV = vals instanceof Behaviour ? valueNow(vals) : vals;
if (typeof(valsV) == 'object') {
for (var i in valsV) {
if (!(Object.prototype) || !(Object.prototype[i])) {
this.enstyleProperty(obj,valsV, i);
}
}
}
else { throw 'enstyle: expected object literals'; } //SAFETY
},
enstyleProperty: function (obj, vals, i) {
if (vals[i] instanceof Behaviour) {
if (typeof(valueNow(vals[i])) == 'object') {
this.enstyle(obj[i], vals[i]);
}
else {
obj[i] = valueNow(vals[i]);
changes(vals[i]).lift_e(function(v) {obj[i] = v;});
}
}
else {
if (typeof(vals[i]) == 'object') {
this.enstyle(obj[i], vals[i]);
} else {
obj[i] = vals[i];
}
}
}
};
d.createParameterizedTagB = function(tagName) {
return new d.TagB(tagName,slice(arguments,1)).resB;
};
d.enstyleStaticProperty = function (obj, props, index) {
if (typeof(props[index]) == 'object') {
for (var i in props[index]) {
if (!(Object.prototype) || !(Object.prototype[i])) {
d.enstyleStaticProperty(obj[index], props[index], i);
}
}
} else {
obj[index] = props[index];
if (index == 'checked') obj['defaultChecked'] = props[index]; /* TODO: this should maybe be elsewhere? */
if (index == 'selected') obj['defaultSelected'] = props[index]; /* TODO: this should maybe be elsewhere? */
}
};
d.staticTagMaker = function (tagName) {
return function () {
var tagD = document.createElement(tagName);
if (!(tagD.nodeType > 0)) { throw (tagName + ': invalid tag name'); } //SAFETY
//partition input
// if (arguments[1] === null || arguments[1] === undefined) { arguments[1] = {}; }
var attribs = [];
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] instanceof Array) {
for (var j = 0; j < arguments[i].length; j++) {
if (arguments[i][j]) {
tagD.appendChild(
(typeof(arguments[i][j]) == 'object' &&
arguments[i][j].nodeType > 0)?
arguments[i][j] :
document.createTextNode(arguments[i][j]));
}
}
} else if (!arguments[i]) {
//ignore
} else if ((typeof(arguments[i]) == 'object') &&
(arguments[i].nodeType > 0)) {
tagD.appendChild(arguments[i]);
} else if (typeof(arguments[i]) == 'object') {
attribs.push(arguments[i]);
} else {
tagD.appendChild(document.createTextNode(arguments[i]));
}
}
if (attribs.length == 1) {
for (var k in attribs[0]) {
if (!(Object.prototype) || !(Object.prototype[k])) {
d.enstyleStaticProperty(tagD, attribs[0], k);
}
}
} else if (attribs.length > 0) {
throw 'static enstyle: expected object literals'; //SAFETY
} /* else {
alert('no attribs on: ' + tagName);
} */
return tagD;
};
};
var generatedTags =
[ "a", "b", "blockquote", "br", "button", "canvas", "div", "fieldset",
"form", "font", "h1", "h2", "h3", "h4", "hr", "img", "iframe", "input",
"label", "legend", "li", "ol", "optgroup", "option",
"p", "pre", "select", "span", "strong", "table", "tbody",
"td", "textarea", "tfoot", "th", "thead", "tr", "tt", "ul" ];
map(
function (tagName) {
var upper = tagName.toUpperCase();
//d.<TAG>B
d[upper + 'B'] = function () {
return d.createParameterizedTagB.apply(this, [tagName].concat(slice(arguments,0)));
};
annotate(d[upper+'B'],[upper+'B']);
//d.<TAG>
d[upper] = d.staticTagMaker(tagName); //faster, simple
/* function () {
// 8/16/06 - leo - arguments bug
// more forgiving: allows reactive children (just doesn't propagate them)
var args = [b.maybeEmpty].concat(slice(arguments, 0));
return valueNow(d[upper + 'B'].apply(this, args));
}; */
annotate(d[upper],[upper]);
},
generatedTags);
//TEXTB: Behaviour a -> Behaviour Dom TextNode
d.TEXTB = function (strB) {
// if (!(strB instanceof Behaviour || typeof(strB) == 'string')) { throw 'TEXTB: expected Behaviour as second arg'; } //SAFETY
if (!(strB instanceof Behaviour)) { strB = constant_b(strB); }
return hold(
changes(strB).lift_e(
function (txt) { return document.createTextNode(txt); }),
document.createTextNode(valueNow(strB)));
};
annotate(d.TEXTB,['TEXTB']);
var TEXT = function (str) {
return document.createTextNode(str);
};
fxExport(dom,TEXT,'TEXT');
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Reactive DOM
//tagRec: Array (EventName a) *
// ( .Array (Event a) * Array (Event a) -> Behaviour Dom) -> Behaviour Dom
var tagRec = function (eventNames, maker) {
if (!(eventNames instanceof Array)) { throw 'tagRec: expected array of event names as first arg'; } //SAFETY
if (!(maker instanceof Function)) { throw 'tagRec: expected function as second arg'; } //SAFETY
var numEvents = eventNames.length;
var internals = [ ];
var switches = [ ];
for (var i = 0; i < numEvents; i++) {
internals[i] = internal_e();
switches[i] = internals[i].switch_e();
};
var domB = maker.apply(this,switches);
var prevValue;
var interceptE = createNode([domB.changes()],function (_,p) {
if (isEqual(p.value,prevValue)) { return; }
prevValue = p.value;
for (var i = 0; i < numEvents; i++) {
sendEvent(internals[i],extractEvent_e(prevValue,eventNames[i]));
};
});
sendEvent(interceptE,domB.valueNow());
return domB;
};
fxExport(dom,tagRec,'tagRec','nodeWithOwnEvents');
//extractEventStatic_e: Dom * String -> Event
var extractEventStatic_e = function (domObj, eventName) {
if (!eventName) { throw 'extractEvent_e : no event name specified'; }
if (!domObj) { throw 'extractEvent_e : no DOM element specified'; }
domObj = getObj(domObj);
var primEventE = internal_e();
addEvent(domObj,eventName,function(evt) {
sendEvent(primEventE, evt || window.event);
// Important for IE; false would prevent things like a checkbox actually
// checking.
return true;
});
return primEventE;
};
//extractEvent_e: [Behaviour] Dom * String -> Event
var extractEvent_e = function (domB, eventName) {
if (!(domB instanceof Behaviour)) {
return extractEventStatic_e(domB,eventName);
}
else {
var domE = domB.changes();
var eventEE = domE.lift_e(function(dom) {
return extractEventStatic_e(dom,eventName);
});
var resultE = eventEE.switch_e();
sendEvent(domE,domB.valueNow());
return resultE;
};
};
fxExport(dom,extractEvent_e,'extractEvent_e','$EVT');
//extractEvents_e:
// [Behaviour] Dom
// . Array String
// -> Event
// ex: extractEvents_e(m, 'body', 'mouseover', 'mouseout')
d.extractEvents_e = function (domObj /* . eventNames */) {
var eventNames = slice(arguments, 1);
var events = map(
function (eventName) {
return extractEvent_e(domObj, eventName);
},
eventNames.length === 0 ? [] : eventNames);
return merge_e.apply(this, events);
};
annotate(d.extractEvents_e,['extractEvents_e'],0,[Behaviour.prototype],['extractEvents_e']);
//value of dom form object during trigger
d.extractValueOnEvent_e = function (triggerE, domObj) {
if (!(triggerE instanceof EventStream)) { throw 'extractValueOnEvent_e: expected Event as first arg'; } //SAFETY
return changes(d.extractValueOnEvent_b.apply(this, arguments));
};
annotate(d.extractValueOnEvent_e,['extractValueOnEvent_e']);
//extractDomFieldOnEvent_e: Event * Dom U String . Array String -> Event a
d.extractDomFieldOnEvent_e = function (triggerE, domObj /* . indices */) {
if (!(triggerE instanceof EventStream)) { throw 'extractDomFieldOnEvent_e: expected Event as first arg'; } //SAFETY
var indices = slice(arguments, 2);
var res =
triggerE.lift_e(
function () { return getDomVal(domObj, indices); });
return res;
};
d.extractValue_e = function (domObj) {
return changes(d.extractValue_b.apply(this, arguments));
};
annotate(d.extractValue_e,['extractValue_e','$E']);
//extractValueOnEvent_b: Event * DOM -> Behaviour
// value of a dom form object, polled during trigger
d.extractValueOnEvent_b = function (triggerE, domObj) {
return d.extractValueStatic_b(domObj, triggerE);
};
annotate(d.extractValueOnEvent_b,['extractValueOnEvent_b']);
//extractValueStatic_b: DOM [ * Event ] -> Behaviour a
//If no trigger for extraction is specified, guess one
d.extractValueStatic_b = function (domObj, triggerE) {
var objD;
try {
objD = getObj(domObj);
//This is for IE
if(typeof(domObj) == 'string' && objD.id != domObj) {
throw 'Make a radio group';
}
} catch (e) {
objD = {type: 'radio-group', name: domObj};
}
var getter; // get value at any current point in time
switch (objD.type) {
//TODO: checkbox.value instead of status?
case 'checkbox':
return hold(
filterRepeats_e(
d.extractDomFieldOnEvent_e(
triggerE ? triggerE :
d.extractEvents_e(
objD,
'click', 'keyup', 'change'),
objD,
'checked'),objD.checked),
objD.checked);
case 'select-one':
getter = function (_) {
return objD.selectedIndex > -1 ?
(objD.options[objD.selectedIndex].value ?
objD.options[objD.selectedIndex].value :
objD.options[objD.selectedIndex].innerText)
: undefined;
};
return hold(
filterRepeats_e(
(triggerE ? triggerE :
d.extractEvents_e(
objD,
'click', 'keyup', 'change')).lift_e(getter)),getter(),
getter());
case 'select-multiple':
//TODO ryan's cfilter adapted for equality check
getter = function (_) {
var res = [];
for (var i = 0; i < objD.options.length; i++) {
if (objD.options[i].selected) {
res.push(objD.options[i].value ? objD.options[i].value : objD.options[i].innerText);
}
}
return res;
};
return hold(
(triggerE ? triggerE :
d.extractEvents_e(
objD,
'click', 'keyup', 'change')).lift_e(getter),
getter());
case 'text':
case 'textarea':
case 'hidden':
case 'password':
return hold(
filterRepeats_e(
d.extractDomFieldOnEvent_e(
triggerE ? triggerE :
d.extractEvents_e(
objD,
'click', 'keyup', 'change'),
objD,
'value'),objD.value),
objD.value);
case 'button': //same as above, but don't filter repeats
return hold(
d.extractDomFieldOnEvent_e(
triggerE ? triggerE :
d.extractEvents_e(
objD,
'click', 'keyup', 'change'),
objD,
'value'),
objD.value);
case 'radio':
case 'radio-group':
//TODO returns value of selected button, but if none specified,
// returns 'on', which is ambiguous. could return index,
// but that is probably more annoying
var radiosAD = filter(
function (elt) {
return (elt.type == 'radio') &&
(elt.getAttribute('name') == objD.name);
},
document.getElementsByTagName('input'));
getter =
objD.type == 'radio' ?
function (_) {
return objD.checked;
} :
function (_) {
for (var i = 0; i < radiosAD.length; i++) {
if (radiosAD[i].checked) {
return radiosAD[i].value;
}
}
return undefined; //TODO throw exn?
};
var actualTriggerE = triggerE ? triggerE :
merge_e.apply(
this,
map(
function (radio) {
return d.extractEvents_e(
radio,
'click', 'keyup', 'change'); },
radiosAD));
return hold(
filterRepeats_e(
actualTriggerE.lift_e(getter),getter()),
getter());
default:
throw ('extractValueStatic_b: unknown value type "' + objD.type + '"');
}
};
d.extractValue_b = function (domObj) {
if (domObj instanceof Behaviour) {
return lift_b(function (dom) { return d.extractValueStatic_b(dom); },
domObj)
.switch_b();
} else {
return d.extractValueStatic_b(domObj);
}
};
annotate(d.extractValue_b,['extractValue_b','$B'],0,[Behaviour.prototype],['extractValue_b','$B']);
//into[index] = deepValueNow(from) via descending from object and mutating each field
d.deepStaticUpdate = function (into, from, index) {
var fV = (from instanceof Behaviour)? valueNow(from) : from;
if (typeof(fV) == 'object') {
for (var i in fV) {
if (!(Object.prototype) || !(Object.prototype[i])) {
d.deepStaticUpdate(index? into[index] : into, fV[i], i);
}
}
} else {
var old = into[index];
into[index] = fV;
}
};
//note: no object may be time varying, just the fields
//into[index] = from
//only updates on changes
d.deepDynamicUpdate = function (into, from, index) {
var fV = (from instanceof Behaviour)? valueNow(from) : from;
if (typeof(fV) == 'object') {
if (from instanceof Behaviour) {
throw 'deepDynamicUpdate: dynamic collections not supported';
}
for (var i in fV) {
if (!(Object.prototype) || !(Object.prototype[i])) {
d.deepDynamicUpdate(index? into[index] : into, fV[i], i);
}
}
} else {
if (from instanceof Behaviour) {
createNode(
[changes(from)],
function (s, p) {
if (index) {
var old = into[index];
into[index] = p.value;
}
else { into = p.value; } //TODO notify topE?
});
}
}
};
d.insertValue = function (val, domObj /* . indices */) {
var indices = slice(arguments, 2);
var parent = getMostDom(domObj, indices);
d.deepStaticUpdate(parent, val, indices ? indices[indices.length - 1] : undefined);
};
annotate(d.insertValue,['insertValue']);
//TODO convenience method (default to firstChild nodeValue)
d.insertValueE = function (triggerE, domObj /* . indices */) {
if (!(triggerE instanceof EventStream)) { throw 'insertValueE: expected Event as first arg'; } //SAFETY
var indices = slice(arguments, 2);
var parent = getMostDom(domObj, indices);
triggerE.lift_e(function (v) {
d.deepStaticUpdate(parent, v, indices? indices[indices.length - 1] : undefined);
});
};
annotate(d.insertValueE,['insertValueE'],0,[EventStream.prototype],['insertValueE']);
//insertValueB: Behaviour * domeNode . Array (id) -> void
//TODO notify adapter of initial state change?
d.insertValueB = function (triggerB, domObj /* . indices */) {
var indices = slice(arguments, 2);
var parent = getMostDom(domObj, indices);
//NOW
d.deepStaticUpdate(parent, triggerB, indices ? indices[indices.length - 1] : undefined);
//LATER
d.deepDynamicUpdate(parent, triggerB, indices? indices[indices.length -1] : undefined);
};
annotate(d.insertValueB,['insertValueB'],0,[Behaviour.prototype],['insertValueB']);
//TODO copy dom event call backs of original to new? i don't thinks so
// complication though: registration of call backs should be scoped
d.insertDomE = function (triggerE, domObj) {
if (!(triggerE instanceof EventStream)) { throw 'insertDomE: expected Event as first arg'; } //SAFETY
var objD = getObj(domObj);
var res = triggerE.lift_e(
function (newObj) {
//TODO safer check
if (!((typeof(newObj) == 'object') && (newObj.nodeType == 1))) {
newObj = d.SPAN({}, newObj);
}
swapDom(objD, newObj);
objD = newObj;
return newObj; // newObj;
});
return res;
};
annotate(d.insertDomE,['insertDomE'],0,[EventStream.prototype],['insertDomE']);
//insertDom: dom
// * dom
// [* (null | undefined | 'over' | 'before' | 'after' | 'leftMost' | 'rightMost' | 'beginning' | 'end']
// -> void
// TODO: for consistency, switch replaceWithD, hookD argument order
d.insertDom = function (hookD, replaceWithD, optPosition) {
switch (optPosition)
{
case undefined:
case null:
case 'over':
swapDom(hookD,replaceWithD);
break;
case 'before':
hookD.parentNode.insertBefore(replaceWithD, hookD);
break;
case 'after':
if (hookD.nextSibling) {
hookD.parentNode.insertBefore(replaceWithD, hookD.nextSibling);
} else {
hookD.parentNode.appendChild(replaceWithD);
}
break;
case 'leftMost':
if (hookD.parentNode.firstChild) {
hookD.parentNode.insertBefore(
replaceWithD,
hookD.parentNode.firstChild);
} else { hookD.parentNode.appendChild(replaceWithD); }
break;
case 'rightMost':
hookD.parentNode.appendChild(replaceWithD);
break;
case 'beginning':
if (hookD.firstChild) {
hookD.insertBefore(
replaceWithD,
hookD.firstChild);
} else { hookD.appendChild(replaceWithD); }
break;
case 'end':
hookD.appendChild(replaceWithD);
break;
default:
throw ('domInsert: unknown position: ' + optPosition);
}
};
//insertDom: dom
// * dom U String domID
// [* (null | undefined | 'over' | 'before' | 'after' | 'leftMost' | 'rightMost' | 'beginning' | 'end']
// -> void
d.insertDomClean = function (replaceWithD, hook, optPosition) {
//TODO span of textnode instead of textnode?
d.insertDom(
getObj(hook),
((typeof(replaceWithD) == 'object') && (replaceWithD.nodeType > 0)) ? replaceWithD :
document.createTextNode(replaceWithD),
optPosition);
};
annotate(d.insertDomClean,['insertDom']);
//TODO test
//insertDomB:
// [Behaviour] String U Dom
// [* ( id U null U undefined )
// [* ('before' U 'after' U 'leftMost' U 'rightMost' U 'over' U 'beginning' U 'end')]]
// -> Behaviour a
//if optID not specified, id must be set in init val of trigger
//if position is not specified, default to 'over'
//performs initial swap onload
d.insertDomB = function (initTriggerB, optID, optPosition) {
if (!(initTriggerB instanceof Behaviour)) {
initTriggerB = constant_b(initTriggerB);
}
var triggerB =
lift_b(
function (d) {
if ((typeof(d) == 'object') && (d.nodeType > 0)) {
return d;
} else {
var res = document.createElement('span'); //TODO createText instead
res.appendChild(document.createTextNode(d));
return res;
}
},
initTriggerB);
var initD = valueNow(triggerB);
if (!((typeof(initD) == 'object') && (initD.nodeType == 1))) { throw ('insertDomB: initial value conversion failed: ' + initD); } //SAFETY
d.insertDom(
optID === null || optID === undefined ? getObj(initD.getAttribute('id')) : getObj(optID),
initD,
optPosition);
var resB = hold(
d.insertDomE(
changes(triggerB),
initD),
initD);
return resB;
};
annotate(d.insertDomB,['insertDomB'],0,[Behaviour.prototype],['insertDomB']);
d.extractId_b = function (id, start)
{
return hold(
createNode( start instanceof Behaviour? [changes(start)] :
[],
function (s, p) {
p.value = getObj(id);
s(p);
}),
getObj(id));
};
annotate(d.insertDomB,['extractId_b'],1,[Behaviour.prototype],['extractId_b']);
var mouse_e = function(elem) {
return extractEvent_e(elem,'mousemove')
.lift_e(function(evt) {
if (evt.pageX | evt.pageY) {
return { left: evt.pageX, top: evt.pageY };
}
else if (evt.clientX || evt.clientY) {
return { left : evt.clientX + document.body.scrollLeft,
top: evt.clientY + document.body.scrollTop };
}
else {
return { left: 0, top: 0 };
}
});
};
var mouse_b = function(elem) {
return mouse_e(elem).hold({ left: 0, top: 0 });
}
fxExport(dom,mouse_b,'mouse_b');
var mouseLeft_b = function(elem) {
return lift_b(function(v) { return v.left; },mouse_b(elem));
};
fxExport(dom,mouseLeft_b,'mouseLeft_b');
var mouseTop_b = function(elem) {
return mouse_b(elem).lift_b(function(v) { return v.top; });
};
fxExport(dom,mouseTop_b,'mouseTop_b');
var clicks_e = function(elem) {
return extractEvent_e(elem,'click');
};
fxExport(dom,clicks_e,'clicks_e');
//////////////////////////////////////////////////////////////////////////////
// Combinators for web services
//credit Matt White
var getURLParam = function (param) {
var lparam = param.toLowerCase();
var aReturn = [];
var strHref = window.location.href;
var endstr = (strHref.indexOf('#') > -1) ? strHref.indexOf('#') : strHref.length;
if ( strHref.indexOf("?") > -1 ) {
var strQueryString = strHref.substring(strHref.indexOf("?")+1,endstr);
map(function(qp) {
var eq = qp.indexOf('=');
var qname = qp.substr(0,eq+1).toLowerCase();
if(qname == lparam+"=") aReturn.push(decodeURIComponent(qp.substr(eq+1)));
},strQueryString.split("&"));
}
if (aReturn.length == 0) return undefined;
else if(aReturn.length == 1) return aReturn[0];
else return aReturn;
};
fxExport(domMisc,getURLParam,'getURLParam','$URL');
//credit Quirksmode
//readCookie: String -> String U Undefined
var readCookie = function (name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for (var i=0; i < ca.length; i++) {
var co = ca[i];
while (co.charAt(0) == ' ') { co = co.substring(1, co.length); }
if (co.indexOf(nameEQ) === 0) {
return co.substring(nameEQ.length, co.length);
}
}
return undefined;
};
fxExport(domMisc,readCookie,'readCookie');
//========== dynamic scripts ==========
var scriptCounter = 0;
var deleteScript = function (scriptID) {
var scriptD = getObj(scriptID);
scriptD.parentNode.removeChild(scriptD); //TODO isolate child and set innerHTML to "" to avoid psuedo-leaks?
};
// optional fn/param that gets polled until parm is defined
var runScript = function (url, fn, param) {
var script = document.createElement("script");
script.src = url;
var scriptID = 'scriptFnRPC' + scriptCounter++;
script.setAttribute('id', scriptID);
document.getElementsByTagName("head").item(0).appendChild(script);
var timer = {};
var check =
function () {
eval("try { if (" + param + "!== undefined) {var stat = " + param + ";}} catch (e) {}");
if (stat !== undefined) {
eval(param + " = undefined;");
clearInterval(timer.timer);
clearInterval(timer.timeout);
if (fn instanceof Function) {
fn(stat);
}
deleteScript(scriptID);
}
};
timer.timer = setInterval(check, 3500);
timer.timeout =
setTimeout(
function () {
try { clearInterval(timer.timer); }
catch (e) {}
},
5000); //TODO make parameter?
};
// Node {url, globalArg} -> Node a
//load script @ url and poll until param is set, then pass it along
var evalForeignScriptVal_e = function(urlArg_e) {
var result = receiver_e();
urlArg_e.lift_e(function(urlArg) {
runScript(urlArg.url,
function(val) { result.sendEvent(val); },
urlArg.globalArg);
});
return result;
};
fxExport(ws,evalForeignScriptVal_e,'evalForeignScriptVal_e');
var ajaxRequest = function(method,url,body,async,useFlash,callback) {
var xhr;
if (useFlash) {
xhr = new FlashXMLHttpRequest();
xhr.onload = function() { callback(xhr); };
}
else if (window.XMLHttpRequest && !(window.ActiveXObject)) {
xhr = new window.XMLHttpRequest();
xhr.onload = function() { callback(xhr); };
}
else if (window.ActiveXObject) {
try { xhr = new ActiveXObject("Msxml2.XMLHTTP"); }
catch(e) { xhr = new ActiveXObject("Microsoft.XMLHTTP"); }
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) { callback(xhr); }
};
};
xhr.open(method,url,async);
if (method == 'POST') {
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
}
xhr.send(body);
return xhr;
};
var encodeREST = function(obj) {
var str = "";
for (var field in obj) {
if (typeof(obj[field]) !== 'function') { // skips functions in the object
if (str != '') { str += '&'; }
str += field + '=' + encodeURIComponent(obj[field]);
}
}
return str;
};
// From json.org
var parseJSON = function(str) {
try {
return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
str.replace(/"(\\.|[^"\\])*"/g, ''))) &&
eval('(' + str + ')');
} catch (e) {
throw 'cannot parse JSON string: ' + e;
}
};
var toJSONString = (function() {
var m = {
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
};
var s = {
array: function (x) {
var a = ['['], b, f, i, l = x.length, v;
for (i = 0; i < l; i += 1) {
v = x[i];
f = s[typeof v];
if (f) {
v = f(v);
if (typeof v == 'string') {
if (b) {
a[a.length] = ',';
}
a[a.length] = v;
b = true;
}
}
}
a[a.length] = ']';
return a.join('');
},
'boolean': function (x) {
return String(x);
},
'null': function (x) {
return "null";
},
number: function (x) {
return isFinite(x) ? String(x) : 'null';
},
object: function (x) {
if (x) {
if (x instanceof Array) {
return s.array(x);
}
var a = ['{'], b, f, i, v;
for (i in x) {
v = x[i];
f = s[typeof v];
if (f) {
v = f(v);
if (typeof v == 'string') {
if (b) {
a[a.length] = ',';
}
a.push(s.string(i), ':', v);
b = true;
}
}
}
a[a.length] = '}';
return a.join('');
}
return 'null';
},
string: function (x) {
if (/["\\\x00-\x1f]/.test(x)) {
x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
var c = m[b];
if (c) {
return c;
}
c = b.charCodeAt();
return '\\u00' +
Math.floor(c / 16).toString(16) +
(c % 16).toString(16);
});
}
return '"' + x + '"';
}
};
return function(val) {
var f = s[typeof val];
if (f) { return f(val); }
else { throw 'parseJSON: unknown object type: ' + (typeof val); }
};
})();
var serverRequest_e = function(useFlash,requestE) {
var response_e = receiver_e();
requestE.lift_e(function (obj) {
var body = '';
var method = 'GET';
var url = obj.url;
var reqType = obj.request ? obj.request : (obj.fields ? 'post' : 'get');
if (obj.request == 'get') {
url += "?" + encodeREST(obj.fields);
body = '';
method = 'GET';
} else if (obj.request == 'post') {
body = toJSONString(obj.fields);
method = 'POST';
} else if (obj.request == 'rawPost') {
body = obj.body;
method = 'POST';
}
else if (obj.request == 'rest') {
body = encodeREST(obj.fields);
method = 'POST';
}
else {
raise ("Invalid request type: " + obj.request);
}
var async = obj.async;
var xhr;
// Branch on the response type to determine how to parse it
if (obj.response == 'json') {
xhr = ajaxRequest(method,url,body,async,useFlash,
function(xhr) {
response_e.sendEvent(parseJSON(xhr.responseText));
});
}
else if (obj.response == 'xml') {
ajaxRequest(method,url,body,async,useFlash,
function(xhr) {
response_e.sendEvent(xhr.responseXML);
});
}
else if (obj.response == 'plain' || !obj.response) {
ajaxRequest(method,url,body,async,useFlash,
function(xhr) {
response_e.sendEvent(xhr.responseText);
});
}
else {
raise('Unknown response format: ' + obj.response);
}
});
return response_e;
};
var getWebServiceObject_e = function(requestE) {
return serverRequest_e(false,requestE);
};
fxExport(ws,getWebServiceObject_e,'getWebServiceObject_e');
var getForeignWebServiceObject_e = function(requestE) {
return serverRequest_e(true,requestE);
};
fxExport(ws,getForeignWebServiceObject_e,'getForeignWebServiceObject_e');
var cumulativeOffset = function(element) {
var valueT = 0, valueL = 0;
do {
valueT += element.offsetTop || 0;
valueL += element.offsetLeft || 0;
element = element.offsetParent;
} while (element);
return { left: valueL, top: valueT };
};
fxExport(domMisc,cumulativeOffset,'cumulativeOffset');
///////// OPTIONALLY EXPORT PUBLIC FUNCTIONS TO GLOBAL NAMESPACE
if (!options.hideAll) {
for (var zz in flapjax.pub) {
if (!options[zz]) {
window[zz] = flapjax.pub[zz]; // use eval instead so as to achieve Flash compatibility
}
}
}
for (var id in exports) {
if (window[id]) {
if (options.redefine === false) {
warn('window.' + id + ' is already defined; Flapjax will not ' +
'redefine it.');
}
else {
warn('Flapjax is redefining window.' + id + '.');
}
}
if (window[id] && options.redefine === false) {
// Don't redefine id
}
else if (options.show && options.hide && options.show[id] === true &&
options.hide[id] === true) {
throw('flapjaxInit: invalid options: ' + id + " specified as 'show' and 'hide'");
}
else if (options.show && options.show[id]) {
window[id] = exports[id];
}
else if (options.hide && options.hide[id]) {
// do nothing -- don't export
}
// Default behavior--check if it is a synonym first ...
else if (!synonyms[id] || options.includeSynonyms) {
// ... then export if its of the right category
if (options.exportMisc && exports[id].__flapjaxCategory === misc) {
window[id] = exports[id];
}
else if (options.exportDOMMisc && exports[id].__flapjaxCategory === domMisc) {
window[id] = exports[id];
}
else if (options.exportDOM && exports[id].__flapjaxCategory === dom) {
window[id] = exports[id];
}
else if (options.exportCore && exports[id].__flapjaxCategory === core) {
window[id] = exports[id];
}
else if (options.exportWS && exports[id].__flapjaxCategory === ws) {
window[id] = exports[id];
}
}
}
return exports;
}