diff options
author | AndreyChudnov <> | 2012-10-28 17:43:24 (GMT) |
---|---|---|
committer | hdiff <hdiff@luite.com> | 2012-10-28 17:43:24 (GMT) |
commit | dd7d66ebb5eac482b20b4e9b7e2e72e4264f85b6 (patch) | |
tree | c6cd9964969e3a4dd81468740789afcf988bfcff | |
parent | 406c97fe855a778acd2e025b6ce6df9a2bf862a8 (diff) |
version 0.9.10.9.1
-rw-r--r-- | CHANGELOG | 5 | ||||
-rw-r--r-- | language-ecmascript.cabal | 13 | ||||
-rw-r--r-- | tests/parse-pretty/add.js | 1 | ||||
-rw-r--r-- | tests/parse-pretty/do-while.js | 3 | ||||
-rw-r--r-- | tests/parse-pretty/flapjax-fxinternal-22dec2008.js | 2552 | ||||
-rw-r--r-- | tests/parse-pretty/neg.js | 2 | ||||
-rw-r--r-- | tests/parse-pretty/numbers.js | 2 | ||||
-rw-r--r-- | tests/parse-pretty/object-lit-1.js | 4 | ||||
-rw-r--r-- | tests/parse-pretty/object-lit-2.js | 4 | ||||
-rw-r--r-- | tests/parse-pretty/prefix-chain.js | 4 | ||||
-rw-r--r-- | tests/parse-pretty/strings.js | 6 |
11 files changed, 2590 insertions, 6 deletions
diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..63e11c8 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,5 @@ +Version change log. + +=0.9.1= +Increased the upper bound on data-default in dependencies. See +https://github.com/jswebtools/language-ecmascript/pull/5 diff --git a/language-ecmascript.cabal b/language-ecmascript.cabal index 07c4857..09ca7c6 100644 --- a/language-ecmascript.cabal +++ b/language-ecmascript.cabal @@ -1,5 +1,5 @@ Name: language-ecmascript -Version: 0.9 +Version: 0.9.1 Cabal-Version: >= 1.10 Copyright: (c) 2007-2012 Brown University, (c) 2008-2010 Claudiu Saftoiu, (c) 2012 Stevens Institute of Technology @@ -10,8 +10,9 @@ Author: Andrey Chudnov, Arjun Guha, Spiridon Aristides Eliopoulos, Maintainer: Andrey Chudnov <oss@chudnov.com> Homepage: http://github.com/jswebtools/language-ecmascript Bug-reports: http://github.com/jswebtools/language-ecmascript/issues -Stability: provisional -Tested-with: GHC==7.0.4, GHC==7.4.1 +Stability: experimental +Tested-with: GHC==7.4.1 +Extra-Source-Files: tests/parse-pretty/*.js, CHANGELOG Category: Language Build-Type: Simple Synopsis: JavaScript analysis tools @@ -20,7 +21,7 @@ Description: Includes a parser, pretty-printer, and basic building blocks for more sophisticated tools. - This package supercedes package WebBits. + This package used to be called WebBits. Source-repository head type: git @@ -42,7 +43,7 @@ Library containers >= 0.1, syb >= 0.1, uniplate >= 1.6 && <1.7, - data-default >= 0.4 && <0.5 + data-default >= 0.4 && <0.6 ghc-options: -fwarn-incomplete-patterns Exposed-Modules: @@ -72,7 +73,7 @@ Test-Suite unittest directory, filepath, HUnit, - data-default >=0.4 && <0.5 + data-default >=0.4 && <0.6 Default-Extensions: DeriveDataTypeable, ScopedTypeVariables, DeriveFunctor, DeriveFoldable, DeriveTraversable, FlexibleContexts Default-Language: Haskell2010 ghc-options: diff --git a/tests/parse-pretty/add.js b/tests/parse-pretty/add.js new file mode 100644 index 0000000..3436956 --- /dev/null +++ b/tests/parse-pretty/add.js @@ -0,0 +1 @@ +1+2;;;; diff --git a/tests/parse-pretty/do-while.js b/tests/parse-pretty/do-while.js new file mode 100644 index 0000000..3963840 --- /dev/null +++ b/tests/parse-pretty/do-while.js @@ -0,0 +1,3 @@ +do { + x = y; +} while (element); diff --git a/tests/parse-pretty/flapjax-fxinternal-22dec2008.js b/tests/parse-pretty/flapjax-fxinternal-22dec2008.js new file mode 100644 index 0000000..272499a --- /dev/null +++ b/tests/parse-pretty/flapjax-fxinternal-22dec2008.js @@ -0,0 +1,2552 @@ +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; +} diff --git a/tests/parse-pretty/neg.js b/tests/parse-pretty/neg.js new file mode 100644 index 0000000..be41614 --- /dev/null +++ b/tests/parse-pretty/neg.js @@ -0,0 +1,2 @@ +var foo = -x; +var bar = +y; diff --git a/tests/parse-pretty/numbers.js b/tests/parse-pretty/numbers.js new file mode 100644 index 0000000..b29b7be --- /dev/null +++ b/tests/parse-pretty/numbers.js @@ -0,0 +1,2 @@ +var x = 23; +var y = 23.0; diff --git a/tests/parse-pretty/object-lit-1.js b/tests/parse-pretty/object-lit-1.js new file mode 100644 index 0000000..9a3c5eb --- /dev/null +++ b/tests/parse-pretty/object-lit-1.js @@ -0,0 +1,4 @@ +var x = { // avoid ambiguity with BlockStmt + foo : 23, + bar : 50 +} diff --git a/tests/parse-pretty/object-lit-2.js b/tests/parse-pretty/object-lit-2.js new file mode 100644 index 0000000..24337ef --- /dev/null +++ b/tests/parse-pretty/object-lit-2.js @@ -0,0 +1,4 @@ +var x = { // avoid ambiguity with BlockStmt + foo : 23, + bar : 50, +} diff --git a/tests/parse-pretty/prefix-chain.js b/tests/parse-pretty/prefix-chain.js new file mode 100644 index 0000000..9ff2ec3 --- /dev/null +++ b/tests/parse-pretty/prefix-chain.js @@ -0,0 +1,4 @@ +var w = typeof typeof 34; +var x = ! -- x; // derived from jQuery +var y = ! !x; // derived from Prototype +var z = !! -- x; diff --git a/tests/parse-pretty/strings.js b/tests/parse-pretty/strings.js new file mode 100644 index 0000000..2fa431d --- /dev/null +++ b/tests/parse-pretty/strings.js @@ -0,0 +1,6 @@ +var x = '\b'; +var z = { + '\b': '\\b' // lhs is a PropString, not a StringLit, but must be printed + // as a StringLit +}; +var w = "\\s"; |