From fc52fffa138beab7d1e80d42e82b6ca75ca80ee1 Mon Sep 17 00:00:00 2001 From: Ifeora Okechukwu Date: Wed, 3 Aug 2016 05:16:37 -0700 Subject: [PATCH] removed jQuery dependency and refactored --- README.md | 115 ++- dist/Phlorx.min.js | 17 +- src/Phlorx.js | 1450 ++++++++++++++++++++++++++++++++++--- tests/specRunner.html | 1 - tests/specs/streamSpec.js | 143 +++- 5 files changed, 1561 insertions(+), 165 deletions(-) diff --git a/README.md b/README.md index 3bb0ee2..6ae791d 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,115 @@ # Phlorx -Phlorx is a light-weight, task efficient, fault tolerant JavaScript library for functional reactive programming inspired by Bacon.js & RxJS. It requires jQuery to work + +Phlorx is a light-weight, task efficient, fault tolerant JavaScript library for functional reactive programming inspired by Bacon.js & RxJS. It does not require jQuery to work. But you could use jQuery with Phlorx if you like. ## Browser Support -+ IE 7.0+ -+ Firefox 3.5+ -+ Opera 9.0+ ++ IE 6.0+ ++ Firefox 3.0+ ++ Opera 7.0+ + Chrome 2.0+ + Safari 3.0+ ## Getting Started -You can start using Phlorx now with your project(s) in experimentation. It is not advised to use this -library in production level. It is still in *Alpha* stage of release. Only 3 simple step needed. +You can start using Phlorx now with your project(s) in development. It is now okay to use this +library in production level. However, it is still in *Beta* stage of release. Only 2 simple steps +are needed to get started. **Step 1** -Load jQuery into your web project like so... +Optionally load _jQuery_ into your web project and then load _Phlorx_ afterwards like so ``` - - + + + + + + Phlorx App + + + + + +
+ + +
+ + ``` -**Step2** -Load Phlorx afterwards like so... +**Step 2** + Code to heart's content! ``` - + ``` @@ -56,5 +121,5 @@ I am open to start recieving PRs on code enhancements and changes. You can also ## Extras -> Code is 18KB in size (not minified) - Code is 7KB in size (minified) +> Code is 58.2KB in size (not minified) + Code is 22.5KB in size (minified) diff --git a/dist/Phlorx.min.js b/dist/Phlorx.min.js index 59cf6a2..e2fe1a3 100644 --- a/dist/Phlorx.min.js +++ b/dist/Phlorx.min.js @@ -1,16 +1,25 @@ -/*! +/*! * @projectname: Phlorx v0.0.2 * @repo: https://www.github.com/isocroft/phlorx * @author(s): Okechukwu Ifeora (@isocroft) - * @contributor(s): ----------- + * @contributor(s): nil * @copyright: Copyright (c) 2016 @cdv * @license: MIT * @releasedate: 12/01/2016 + * @modifieddate : 28/07/2016 * * Phlorx is a light-weight, task efficient, JavaScript library * for functional reactive programming inspired by Bacon.js & * RxJS * - * It requires jQuery to work properly + * It does not requires jQuery to work properly */ -window.Phlorx=function(n,t,i){var u="function";if(!t)throw new Error("Phlorx library cannot load -> jQuery dependeny missing");var e={}.hasOwnProperty,o=[].slice,r={},f=i(e,o,n);return t.fn.getStream=function(n){return new f(n,this)},t.fn.setAction=function(n,i){return $self=t(this),i.subscribe(function(t,i,r){$self.each(function(){n in $self&&$self[n](r)})}),!0},r.createUIStream=function(n,i){return t(n).getStream(i)},r.createWorkStream=function(n){return new f(n,{})},r.createBasicStream=function(n){return new f(n,{},!0)},r.interval=function(n,t){var i=this.createWorkStream(null),r;return r=setTimeout(function u(){i.fireAtCore(t),r=setTimeout(u,n)},n),i.filter(function(n){return Array.isArray(n)?n.length>0:!!n})},r.sequentially=function(n,t){var i=this.interval(n,t);return i.map(function(n){return n.shift()})},r.setAction=function(n,i,r){return t(n).setAction(typeof i==u?i(r):i,r)},r.fromPromise=function(n){var t=this.createWorkStream(null);if(typeof n.promise==u&&typeof n.promise().then==u)typeof n.then==u&&n.then(function(n){t.fireAtCore(n)},function(n){t.fireAtCore(n)});else throw new TypeError("first argument must be a standard promise object");return t},r}(this,this.jQuery,function(n,t,i){var u=null,e=!0,o="object",s="first argument must be a function",f="function",c="number",l="undefined",h="string";function y(n){var i=this;if(typeof n!=h)throw new TypeError("first argument nust be a string");var u=n,e=[],r=a(i),o=function(n,i,r){r&&typeof r!=l||i&&typeof i==c&&(r=i);var u=now=(new Date).getMilliseconds(),f;return function(){now=(new Date).getMillisecond(),f=now-u;var i=this;f>=r&&n.apply(i,t.call(arguments)),u=now}};return i.toString=function(){return"[object Stream]"},i.getEvent=function(){return u},i.getDelayFn=function(){return o},i.subscribe=function(n){return r.on(this.getEvent(),n,this)},i.unsubscribe=function(){return r.off(this.getEvent())},i.hasCoreEvent=function(){return r.has(this.getEvent())},i.fireAtCore=function(n){return r.emit(this.getEvent(),n)},i.queryQueue=function(n,t){if(typeof n!=f)throw new Error(s);return n.call(t,e)},i.linkStream=function(n,t){n.subscribe.toString()==this.subscribe.toString()&&this.subscribe(function(i){var r=typeof t==f;n.fireAtCore(r?t(i):i)})},i}function r(n,t,r){var f,u,e;if(typeof t!=o||!("constructor"in t))throw new TypeError("second argument must be an object");return this.getProto=function(){return t},f=t.constructor,u=y.apply(this,[v(26)]),f===i.jQuery?t.each(function(){var i=f(this);i.on(""+n,function(n){u.fireAtCore(n)})}):r&&(e=u.subscribe||function(){},u.subscribe=function(t){e.call(this,t),this.fireAtCore(n)}),this.getStream=function(){return u},u}Object.keyExists=function(t,i){return!!t[i]&&n.call(t,i)},Array.filter=Array.filter||function(n,t,i){if(n instanceof Array||typeof t==f){var r,u=n,o=[];for(d=0;d",0)>0?n.split("->",2):[]};return{emit:function(f){var o,h=-1,c=[],a,v={},p,y=t.call(arguments,1),s=!1;if(o=i(f),o.length?(f=o[0],o[0]=String(s),s=n[f]):(o.push([]),s=n[f]),s)while(h-1?t.split(":",2):t;if(typeof t==h&&typeof i==f)return t=typeof u==h?u:u[1],Object.keyExists(n,t)||(n[t]=[]),e=Array.isArray(u)?u[0]:""+n[t].length,n[t].push({name:e,cxt:r,fn:i,timestamp:+new Date}),n[t].sort(function(n,t){n.timestamp-t.timestamp}),o},once:function(){},has:function(t){return t?n.hasOwnProperty(t):!!t},poof:function(){n={}},emitList:function(n,t,r){var o={},e,u,f;if(Array.isArray(n))for(f=0;f=200&&i<400||!notFound)return u={err:r,xhr:n.statusText||n.responseText};if(u={err:i,xhr:n.responseText||n.response},i===0)throw new Error("Server Offline Or Unavailable");return u},et=function(){var i=["Msxml2.DOMDocument.3.0","Msxml2.DOMDocument","Msxml2.DOMDocument.6.0","Msxml2.DOMDocument.5.0","Msxml2.DOMDocument.4.0","MSXML2.DOMDocument","MSXML.DOMDocument"],t;if(n.ActiveXObject){for(t=0;t=0&&n.state<=1&&t[i][2].add.apply(n,w.call(arguments)),n}:function(){if(n.state>=0&&n.state<=1&&(n.state=a[t[i][1]]),t[i][2].fireWith(n===this?n:this,[].slice.call(arguments)),f){t[r[0]][2].disable(),t[r[1]][2].disable();switch(i){case"reject":case"resolve":n.state=a[t[i][1]]}}return o}},c=0,v=f.slice(),u,i={};for(u in t)p.call(t,u)&&(f.splice(c++,1),n[u]=e(u,f),n[u+"With"]=e(u,[]),i[t[u][0]]=e(u,[],o),f=v.slice());return i.state=a.STARTED,i.always=function(){return this.done.apply(n,arguments).fail.apply(n,arguments)},i.promise=function(n){if(n&&typeof n==b&&!n.length){for(var t in i)p.call(i,t)&&(n[t]=i[t]);return n}return i},i.then=function(){var i,r=[].slice.call(arguments);return r.forEach(function(r,u){r=typeof r==h&&r,n[t[f[u]][0]](function(){var n;try{n=r&&r.apply(this,arguments)}catch(t){n=this.reject(t)}finally{n&&typeof n.promise==h&&(i=n.promise())}})}),n.promise(i)},i.isResolved=function(){return!t.reject[2].hasList()},i.isRejected=function(){return!t.resolve[2].hasList()},i.pipe=i.then,i.promise(n),l.STARTED=a.STARTED,l.AWAITING=a.AWAIT,l.RESOLVED=a.RESOLVED,l.REJECTED=a.REJECTED,e=v=u=c=r,n instanceof l?n:new l},u={},tt={binds:{},promises:{}},st=function(){return++i.uuid+""},ft=i(p,w,n,tt),ht=!(it in t)&&function(){function u(){this.c={}}function pt(n){return vt.g(n)||vt.s(n,"(^|\\s+)"+n+"(\\s+|$)",1)}function tt(n,t){for(var i=0,r=n.length;i~+]/,si=/^\s+|\s*([,\s\+\~>]|$)\s*/g,ht=/[\s\>\+\~]/,ct=/(?![\s\w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^'"]*\]|[\s\w\+\-]*\))/,hi=/([.*+?\^=!:${}()|\[\]\/\\])/g,ci=/^(\*|[a-z0-9]+)?(?:([\.\#]+[\w\-\.#]+)?)/,li=/\[([\w\-]+)(?:([\|\^\$\*\~]?\=)['"]?([ \w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^]+)["']?)?\]/,ai=/:([\w\-]+)(\(['"]?([^()]+)['"]?\))?/,vi=new RegExp(ot.source+"|"+ei.source+"|"+fi.source),lt=new RegExp("("+ht.source+")"+ct.source,"g"),at=new RegExp(ht.source+ct.source),yi=new RegExp(ci.source+"("+li.source+")?("+ai.source+")?"),pi={" ":function(n){return n&&n!==c&&n.parentNode},">":function(n,t){return n&&n.parentNode==t.parentNode&&n.parentNode},"~":function(n){return n&&n.previousSibling},"+":function(n,t,i,r){return n?(i=bt(n))&&(r=bt(t))&&i==r&&i:f}};u.prototype={g:function(n){return this.c[n]||undefined},s:function(n,t,i){return t=i?new RegExp(t):t,this.c[n]=t}};var vt=new u,yt=new u,i=new u,l=new u;var s="compareDocumentPosition"in c?function(n,t){return(t.compareDocumentPosition(n)&16)==16}:"contains"in c?function(t,i){return i=i[n]===9||i==window?c:i,i!==t&&i.contains(t)}:function(n,t){while(n=n.parentNode)if(n===t)return 1;return 0},gi=function(){var n=h.createElement("p");return(n.innerHTML='x<\/a>')&&n.firstChild.getAttribute(y)!="#x"?function(n,t){return t===k?n.className:t===y||t==="src"?n.getAttribute(t,2):n.getAttribute(t)}:function(n,t){return n.getAttribute(t)}}(),tr=function(t,i){var r=[],f,u;try{return i[n]===9||!st.test(t)?a(i[et](t)):(tt(f=t.split(","),gt(i,function(n,t){u=n[et](t),u.length==1?r[r.length]=u.item(0):u.length&&(r=r.concat(a(u)))})),f.length>1&&r.length>1?ft(r):r)}catch(e){}return ni(t,i)},ni=function(t,i){var u=[],e,h,r,o,f,c;if(t=t.replace(si,"$1"),h=t.match(oi)){for(f=pt(h[2]),e=i[w](h[1]||"*"),r=0,o=e.length;r1&&u.length>1?ft(u):u},ti=function(){nt=ni};return ti(),r.configure=ti,r.uniq=ft,r.is=ki,r.pseudos={},r}()||t.querySelectorAll.bind(t); +/*!@submodule + * Document Object Model Helper + */ +return c={observe:function(i,u,e){var s="execScript";function l(){try{t.documentElement.doScroll("left"),a()}catch(i){n.setTimeout(l,0)}}function a(){e.call(r),DomReady=o}var h=f,v=i[0]===t,k=t.body,y="complete",d=f,c=s in window&&!p.call(window,s),w=c?"attachEvent":"addEventListener",b=function(){return function(n){(u!=="onreadystatechange"||t.readyState===y)&&(!~n||(n=window.event,n.stopPropagation=n.stopPropagation?n.stopPropagation:function(){n.cancelBubble=o},n.preventDefault=n.preventDefault?n.preventDefault:function(){n.returnValue=f},n.target=n.srcElement||(t.documentMode&&t.documentMode>=8?HTMLDocument:{}),n.root=n.target.ownerDocument||t,n.relatedTarget=n.type.indexOf("mouse")>-1&&n.fromElement===n.target?n.toElement:n.fromElement,n.currentTarget=n.currentTarget?this.parentNode:n.srcElement,n.timestamp=+new Date,n.metaKey=n.type.indexOf("key")>-1&&n.ctrlKey?n.ctrlKey:n.shiftKey,n.which=n.type=="click"?n.button:n.type.indexOf("key")>-1&&n.charCode!==r?n.charCode:n.keyCode,n.pageX=n.clientX+(t.body.scrollLeft||t.documentElement.scrollLeft||0)-(t.body.clientLeft||t.documentElement.clientLeft||0),n.pageY=n.clientY+(t.body.scrollTop||t.documentElement.scrollLeft||0)-(t.body.clientTop||t.documentElement.clientTop||0)),e.call(this,n))}};c&&(u=="DOMContentLoaded"&&(u="readystatechange"),u="on"+u);try{h=n.frameElement!==r}catch(g){}!h&&/loaded|readystate/i.test(u)&&v?l():(a=r,i.forEach(function(n,t){n[w](u,b(t))}))},unobserve:function(){},select:function(n){return w.call(ht(n))},get_current_pixel_style:function(n,t){var i=n.currentStyle[t]||0,r=n.style.left,u=n.runtimeStyle.left;return n.runtimeStyle.left=n.currentStyle.left,n.style.left=t==="fontSize"?"1em":i,i=n.style.pixelLeft+"px",n.style.left=r,n.runtimeStyle.left=u,i},get_css_property:function(i,u,f){var s,h,e,o;f=f||"px";switch(u){case"float":u=t.all?"styleFloat":"cssFloat";break;case"margin":case"background":case"padding":case"border":case"borderTop":case"borderBottom":case"borderLeft":case"borderRight":u=r}if(u){switch({}.toString.call(i).substring(8,11)){case"Str":for(h=0;h1)while(n!==r){if(n.nodeType==1)break;n.nodeType==3&&(n=n.nextSibling),n.nodeType==9&&(n=t)}}),this.updateCollection(n)},parent:function(){var n=[],t,i=this.getCurrentCollection();return e.fn.each(i,function(i){t=i.parentNode,n.push(t)}),this.updateCollection(n)},each:function(n){return e.fn.each(this.getCurrentCollection(),n),this},attr:function(n,t){var r,i=this.getCurrentCollection();return t?(e.fn.each(i,function(i){c.set_attrib(i,n,t)}),this):c.get_attrib(i[0],n)},removeAtrr:function(n){if(typeof n!==rt)return this;var t=this.getCurrentCollection();return e.fn.each(t,function(t){t.removeAttribute(n)}),this},html:function(n){var t=this.getCurrentCollection();return n?(e.fn.each(t,function(t){t.innerHTML=n}),this):t[0].innerHTML}},u.workStream=function(n){return new ft(n)},u.basicStream=function(n){return new ft(n,o)},u.UI={},u.UI.DOM=function(n){return new e.fn.init(n)},u.viaBinder=function(n){var t=this.basicStream(r),i=new l;return tt.binds[t.getEvent()]=i,typeof n==h&&i.then(function(t){n(t[0])}).then(function(n){t.whenUnsubscribe(n)}),t},u.viaDOM=function(n,i){return i=i||t,this.viaBinder(function(t){return c.observe(c.select(i),n,t),function(){c.unobserve(c.select(i),n,t)}})},u.interval=function(n,t){return this.viaBinder(function(i){var r=setInterval(function(){i(typeof t==h?t():t)},n);return function(){clearInterval(r)}})},u.viaPoll=function(n,t){return this.interval(n,t)},u.viaCallback=function(n){this.viaBinder(n)},u.viaNodeCallback=u.viaCallback,u.ajax=function(t,i){var u,e,s;t=t||{},i=i||f,u=r,e=new l;try{u=et(),u===r&&(u=new XMLHttpRequest),t.crossdomain&&"XDomainRequest"in n&&(u=new XDomainRequest)}catch(h){}t.headers||(t.headers={});for(s in t.headers)headers.hasOwnProperty(s)&&u.setRequestHeader(s,headers[s]);!i&&t.method&&t.url&&(u instanceof n.XDomainRequest?(u.open(t.method,t.url),u.onload=function(){e.resolve(v(u,{fakeStatus:200}).xhr)},u.onerror=function(){e.reject(v(u,{fakeStatus:400}).xhr)},u.ontimeout=function(){e.reject(v(u,{fakeStatus:0}).xhr)},u.onprogress=function(){},u.timeout=0):(u.open(t.method,t.url,!i),u.onreadystatechange=function(){u.readyState===4&&(u.status>=400?e.reject(v(u,{status:u.status,error:o}).xhr):e.resolve(v(u,{status:u.status,error:f}).xhr))}));try{u.send(t.data)}catch(h){u=r}return e.promise()},u.sequentially=function(n,t){return t=t||[],n=n||0,this.viaPoll(n,function(){return t.shift()})},u.viaArray=function(n){return this.sequentially(r,n)},u.later=function(n,t){return this.sequentially(n,[t])},u.viaPromise=function(n){var t=this.workStream(r);if(tt.promises[st()]=n,n&&typeof n.promise==h&&typeof n.promise().then==h)typeof n.then==h&&n.then(function(){t.fireAtCore.apply(t,w.call(arguments))},function(n){t.getErrorHandle()(n)});else throw new TypeError("first argument must be a standard promise object");return t},u.retry=function(n){n=n||{};var t=function(i){if(typeof n.source==h){if(typeof n.retryCount=="number"?++n.retryCount:n.retryCount=1,n.retryCount!==n.retries){var u=n.source();u.onValue(function(n){i.fireAtCore(n),i=r});u.onError(function(){setTimeout(function(){i.offValue(),t(i)},typeof n.delay==h&&n.delay()||0)})}}else i=r},i=this.workStream();return t(i),i},u}(this,this.document,function(n,t,i,r){var h="first argument must be a function",o=!0,e="function",f=null,s="number",c="undefined",l="string";function y(n){var i=this;if(typeof n!=l)throw new TypeError("first argument nust be a string");var r,v=n,y=[],u=a(i),p=function(n,i,r){r&&typeof r!=c||i&&typeof i==s&&(r=i);var u=now=(new Date).getMilliseconds(),f;return function(){now=(new Date).getMillisecond(),f=now-u;var i=this;f>=r&&n.apply(i,t.call(arguments)),u=now}};return i.onError=function(n){r===f&&(r=n)},i.offError=function(){r&&(r=f)},i.getErrorHandle=function(){return function(n){typeof r==e&&r(n)}},i.toString=function(){return"[object Stream]"},i.log=function(){u.canLog=o},i.getEvent=function(){return v},i.getDelayFn=function(){return p},i.onValue=function(n){return u.on(this.getEvent(),n,this)},i.offValue=function(){return u.off(this.getEvent())},i.whenUnsubscribe=function(n){typeof n==e&&u.whenOff(n)},i.hasCoreEvent=function(){return u.has(this.getEvent())},i.fireAtCore=function(n){try{return u.emit(this.getEvent(),n)}catch(t){this.getErrorHandle()(t)}},i.queryQueue=function(n,t){if(typeof n!=e)throw new Error(h);return n.call(t,y)},i.linkStream=function(n,t){if(n===f)throw new Error("[Stream] not okay");if(typeof n.onValue==typeof this.onValue)this.onValue(function(i){var r=typeof t==e;n.fireAtCore(r?t(i):i)})},i}function u(n,i){var u=y.apply(this,[v(26)]),e=this,f;return i&&(f=u.onValue||function(){},u.onValue=function(i){var u=this,e=u;f.call(u,i),console.log("lala",u.getEvent(),r.binds),u.getEvent()in r.binds?r.binds[u.getEvent()].resolve(function(n){if(stream=e.getStream(),n===void 0){stream.offValue();return}if(n instanceof Error){stream.getErrorHandle()(n);return}stream.fireAtCore.apply(stream,t.call(arguments))}):u.fireAtCore(n)}),this.getStream=function(){return u},u}Object.keyExists=function(t,i){return!!t[i]&&n.call(t,i)},Array.filter=Array.filter||function(n,t,i){if(n instanceof Array||typeof t==e){var r,u=n,f=[];for(d=0;d",0)>0?n.split("->",2):[]};return{acumulator:function(){},canLog:!1,emit:function(r){var e,l=-1,a=[],v,y={},w,p=t.call(arguments,1),h=!1;if(e=u(r),e.length?(r=e[0],e[0]=String(h),h=n[r]):(e.push([]),h=n[r]),this.canLog&&i.console!==void 0&&i.console.log("Phlorx Event Log: ",p),h)while(l-1?t.split(":",2):t;if(typeof t==l&&typeof i==e)return t=typeof u==l?u:u[1],Object.keyExists(n,t)||(n[t]=[]),f=Array.isArray(u)?u[0]:""+n[t].length,n[t].push({name:f,cxt:r,fn:i,timestamp:+new Date}),n[t].sort(function(n,t){n.timestamp-t.timestamp}),o},once:function(){},whenOff:function(n){r.push(n)},has:function(t){return t?n.hasOwnProperty(t):!!t},poof:function(){n={}},emitList:function(n,t,i){var o={},e,r,f;if(Array.isArray(n))for(f=0;f jQuery dependeny missing"); - } +window.Phlorx = (function(w, d, factory){ + + factory.uuid = 0; + + /*! + Helpers + */ + var st = "string", - var $h = ({}).hasOwnProperty; + $h = ({}).hasOwnProperty, - var $s = ([]).slice; + $s = ([]).slice, + + getAttributeByName = function(obj, attrName){ + + var attributes = obj.attributes; + + try { + + return attributes.getNamedItem(attrName); + + } + catch (ex) { + var i; + + for (i = 0; i < attributes.length; i++) { + var attr = attributes[i] + if (attr.nodeName == attrName && attr.specified) { + return attr; + } + } + return null; + } + + }, + + requestComplete = function (xhr, control) { + var requestCompleteResult = {xhr:null}, + notFoundOk, + httpStatus; + + // + // XDomainRequest doesn't give us a way to get the status, + // so allow passing in a forged one + // + if (typeof xhr.status === "undefined") { + httpStatus = control.fakeStatus; + } + else { + // + // older versions of IE don't properly handle 204 status codes + // so correct when receiving a 1223 to be 204 locally + // http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request + // + httpStatus = (xhr.status === 1223) ? 204 : xhr.status; + } + + if (!control.finished) { + // may be in sync or async mode, using XMLHttpRequest or IE XDomainRequest, onreadystatechange or + // onload or both might fire depending upon browser, just covering all bases with event hooks and + // using 'finished' flag to avoid triggering events multiple times + control.finished = true; + + notFound = (httpStatus === 404); + if ((httpStatus >= 200 && httpStatus < 400) || !notFound) { + + requestCompleteResult = { + err: null, + xhr:(xhr.statusText || xhr.responseText) + }; + + return requestCompleteResult; + + } + else { + requestCompleteResult = { + err: httpStatus, + xhr:(xhr.responseText || xhr.response) + }; + + if (httpStatus === 0) { + throw new Error('Server Offline Or Unavailable'); + } + + return requestCompleteResult; + } + } + else { + return requestCompleteResult; + } + }, + + CreateMSXMLDocument = function(){ + + var progIDs = [ + 'Msxml2.DOMDocument.3.0', + 'Msxml2.DOMDocument', + "Msxml2.DOMDocument.6.0", + "Msxml2.DOMDocument.5.0", + "Msxml2.DOMDocument.4.0", + "MSXML2.DOMDocument", + "MSXML.DOMDocument" + ]; + + if(w.ActiveXObject){ + for(var i=0;i= 0 && self.state <=1){ + self.state = futuresStates[defTracks[dx][1]]; + } + defTracks[dx][2].fireWith(self === this? self : this, [].slice.call(arguments)); + if(drop){ + defTracks[arr[0]][2].disable(); + defTracks[arr[1]][2].disable(); + switch(dx){ + case "reject": + case "resolve": + self.state = futuresStates[defTracks[dx][1]]; + break; + } + } + return true; + } : function(){ + if(self.state >= 0 && self.state <=1){ + defTracks[dx][2].add.apply(self, $s.call(arguments)); + } + return self; + } ; + }, + i = 0, + ax = keys.slice(), + d, + promise = {}; + + + // using a closure to define a function on the fly... + for(d in defTracks){ + if($h.call(defTracks, d)){ + keys.splice(i++, 1); + self[d] = setter(d, keys); + self[d+"With"] = setter(d, []); + promise[defTracks[d][0]] = setter(d, [], true); + keys = ax.slice(); + } + } + + + promise.state = futuresStates.STARTED; + + promise.always = function(){ + return this.done.apply(self, arguments).fail.apply(self, arguments); + }; + + promise.promise = function(obj){ + if(obj && typeof obj == "object" && !obj.length){ + for(var i in promise){ + if($h.call(promise, i)){ + obj[i] = promise[i]; + } + } + return obj; + } + return promise; + }; + + promise.then = function(/* fnDone, fnFail, fnProgress */){ + var ret, args = [].slice.call(arguments); + args.forEach(function(item, i){ + item = (typeof item == "function") && item; + self[defTracks[keys[i]][0]](function(){ + var rt; + try{ + // Promises/A+ specifies that errors should be conatined and + // returned as value of rejected promise + rt = item && item.apply(this, arguments); + }catch(e){ + rt = this.reject(e); + }finally{ + if(rt && typeof rt.promise == "function") + ret = rt.promise(); + } + }); + }); + return self.promise(ret); + }; + + promise.isResolved = function(){ + return !defTracks['reject'][2].hasList(); + }; + promise.isRejected = function(){ + return !defTracks['resolve'][2].hasList(); + }; + promise.pipe = promise.then; + + promise.promise(self); + + Futures.STARTED = futuresStates.STARTED; + Futures.AWAITING = futuresStates.AWAIT; + Futures.RESOLVED = futuresStates.RESOLVED; + Futures.REJECTED = futuresStates.REJECTED; + + + setter = ax = d = i = null; // avoid unecessarily leaking memory with each call to Futures constructor!! + + // enforce new! + return (self instanceof Futures)? self : new Futures(); + }, + + Phlorx = {}, + + PhlorxStreamsMap = { + 'binds':{}, + 'promises':{} + }, - var Phlorx = {}; + uuid = function(){ + return (++factory.uuid)+''; + }, + + /*!@submodule + * Stream Constructor + */ - var DataStream = factory($h, $s, w); - - $.fn.getStream = function(event){ - return new DataStream(event, this); // [this] is a reference to [jQuery.prototype] - }; + DataStream = factory($h, $s, w, PhlorxStreamsMap), - $.fn.setAction = function(action, stream){ - $self = $(this); - stream.subscribe(function(e, event, data){ - $self.each(function(){ - if(action in $self){ - $self[action](data); - } - }); - }); - return true; - }; + /*!@submodule + * Copyright © 2012 + * Qwery - Mini CSS Selector Engine + * http://github.com/ded/qwery + * Dustin Diaz + * + * @license MIT + * @ + */ + + Qwery = (!('querySelectorAll' in d) && function () { + var doc = d + , html = doc.documentElement + , byClass = 'getElementsByClassName' + , byTag = 'getElementsByTagName' + , qSA = 'querySelectorAll' + , useNativeQSA = 'useNativeQSA' + , tagName = 'tagName' + , nodeType = 'nodeType' + , select // main select() method, assign later + , id = /#([\w\-]+)/ + , clas = /\.[\w\-]+/g + , idOnly = /^#([\w\-]+)$/ + , classOnly = /^\.([\w\-]+)$/ + , tagOnly = /^([\w\-]+)$/ + , tagAndOrClass = /^([\w]+)?\.([\w\-]+)$/ + , splittable = /(^|,)\s*[>~+]/ + , normalizr = /^\s+|\s*([,\s\+\~>]|$)\s*/g + , splitters = /[\s\>\+\~]/ + , splittersMore = /(?![\s\w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^'"]*\]|[\s\w\+\-]*\))/ + , specialChars = /([.*+?\^=!:${}()|\[\]\/\\])/g + , simple = /^(\*|[a-z0-9]+)?(?:([\.\#]+[\w\-\.#]+)?)/ + , attr = /\[([\w\-]+)(?:([\|\^\$\*\~]?\=)['"]?([ \w\-\/\?\&\=\:\.\(\)\!,@#%<>\{\}\$\*\^]+)["']?)?\]/ //[dgg='&5%nns'] + , pseudo = /:([\w\-]+)(\(['"]?([^()]+)['"]?\))?/ //:----('---') or :not('button') + , easy = new RegExp(idOnly.source + '|' + tagOnly.source + '|' + classOnly.source) + , dividers = new RegExp('(' + splitters.source + ')' + splittersMore.source, 'g') + , tokenizr = new RegExp(splitters.source + splittersMore.source) + , chunker = new RegExp(simple.source + '(' + attr.source + ')?' + '(' + pseudo.source + ')?'); + + var walker = { + ' ': function (node) { + return node && node !== html && node.parentNode + } + , '>': function (node, contestant) { + return node && node.parentNode == contestant.parentNode && node.parentNode + } + , '~': function (node) { + return node && node.previousSibling + } + , '+': function (node, contestant, p1, p2) { + if (!node) return false + return (p1 = previous(node)) && (p2 = previous(contestant)) && p1 == p2 && p1 + } + } + + function cache() { + this.c = {} + } + cache.prototype = { + g: function (k) { + return this.c[k] || undefined + }, + s: function (k, v, r) { + v = r ? new RegExp(v) : v + return (this.c[k] = v) + } + } + + var classCache = new cache() + , cleanCache = new cache() + , attrCache = new cache() + , tokenCache = new cache(); + + function classRegex(c) { + return classCache.g(c) || classCache.s(c, '(^|\\s+)' + c + '(\\s+|$)', 1) + } + + // not quite as fast as inline loops in older browsers so don't use liberally + function each(a, fn) { + var i = 0, l = a.length + for (; i < l; i++) fn(a[i]) + } + + function flatten(ar) { + for (var r = [], i = 0, l = ar.length; i < l; ++i) arrayLike(ar[i]) ? (r = r.concat(ar[i])) : (r[r.length] = ar[i]) + return r + } + + function arrayify(ar) { + var i = 0, l = ar.length, r = [] + for (; i < l; i++) r[i] = ar[i] + return r; + } + + function previous(n) { + while (n = n.previousSibling) if (n[nodeType] == 1) break; + return n + } + + function q(query) { + return query.match(chunker) + } + + // called using `this` as element and arguments from regex group results. + // given => div.hello[title="world"]:foo('bar') + // div.hello[title="world"]:foo('bar'), div, .hello, [title="world"], title, =, world, :foo('bar'), foo, ('bar'), bar] + function interpret(whole, tag, idsAndClasses, wholeAttribute, attribute, qualifier, value, wholePseudo, pseudo, wholePseudoVal, pseudoVal) { + var i, m, k, o, classes + if (this[nodeType] !== 1) return false + if (tag && tag !== '*' && this[tagName] && this[tagName].toLowerCase() !== tag) return false + if (idsAndClasses && (m = idsAndClasses.match(id)) && m[1] !== this.id) return false + if (idsAndClasses && (classes = idsAndClasses.match(clas))) { + for (i = classes.length; i--; ) if (!classRegex(classes[i].slice(1)).test(this.className)) return false + } + if (pseudo && qwery.pseudos[pseudo] && !qwery.pseudos[pseudo](this, pseudoVal)) return false + if (wholeAttribute && !value) { // select is just for existance of attrib + o = this.attributes + for (k in o) { + if (hOwn.call(o, k) && (o[k].name || k) == attribute) { + return this + } + } + } + if (wholeAttribute && !checkAttr(qualifier,(getAttr(this, attribute) || ''), value)) { + // select is for attrib equality + return false; + } + return this + } + + function clean(s) { + return cleanCache.g(s) || cleanCache.s(s, s.replace(specialChars, '\\$1')) + } + + function checkAttr(qualify, actual, val) { + switch (qualify) { + case '=': + return actual == val + case '^=': + return actual.match(attrCache.g('^=' + val) || attrCache.s('^=' + val, '^' + clean(val), 1)) + case '$=': + return actual.match(attrCache.g('$=' + val) || attrCache.s('$=' + val, clean(val) + '$', 1)) + case '*=': + return actual.match(attrCache.g(val) || attrCache.s(val, clean(val), 1)) + case '~=': + return actual.match(attrCache.g('~=' + val) || attrCache.s('~=' + val, '(?:^|\\s+)' + clean(val) + '(?:\\s+|$)', 1)) + case '|=': + return actual.match(attrCache.g('|=' + val) || attrCache.s('|=' + val, '^' + clean(val) + '(-|$)', 1)) + } + return 0 + } + + // given a selector, first check for simple cases then collect all base candidate matches and filter + function _qwery(selector, _root) { + var r = [], ret = [], i, l, m, token, tag, els, intr, item, root = _root + , tokens = tokenCache.g(selector) || tokenCache.s(selector, selector.split(tokenizr)) + , dividedTokens = selector.match(dividers) + + if (!tokens.length) return r + + token = (tokens = tokens.slice(0)).pop() // copy cached tokens, take the last one + if (tokens.length && (m = tokens[tokens.length - 1].match(idOnly))) root = byId(_root, m[1]) + if (!root) return r + + intr = q(token) + // collect base candidates to filter + els = root !== _root && root[nodeType] !== 9 && dividedTokens && /^[+~]$/.test(dividedTokens[dividedTokens.length - 1]) ? + function (r) { + while (root = root.nextSibling) { + root[nodeType] == 1 && (intr[1] ? intr[1] == root[tagName].toLowerCase() : 1) && (r[r.length] = root) + } + return r + } ([]) : + root[byTag](intr[1] || '*') + // filter elements according to the right-most part of the selector + for (i = 0, l = els.length; i < l; i++) { + if (item = interpret.apply(els[i], intr)) r[r.length] = item + } + if (!tokens.length) return r + + // filter further according to the rest of the selector (the left side) + each(r, function (e) { if (ancestorMatch(e, tokens, dividedTokens)) ret[ret.length] = e }) + return ret + } + + // compare element to a selector + function is(el, selector, root) { + if (isNode(selector)) return el == selector + if (arrayLike(selector)) return !! ~flatten(selector).indexOf(el) // if selector is an array, is el a member? + + var selectors = selector.split(','), tokens, dividedTokens + while (selector = selectors.pop()) { + tokens = tokenCache.g(selector) || tokenCache.s(selector, selector.split(tokenizr)) + dividedTokens = selector.match(dividers) + tokens = tokens.slice(0) // copy array + if (interpret.apply(el, q(tokens.pop())) && (!tokens.length || ancestorMatch(el, tokens, dividedTokens, root))) { + return true + } + } + return false + } + + // given elements matching the right-most part of a selector, filter out any that don't match the rest + function ancestorMatch(el, tokens, dividedTokens, root) { + var cand + // recursively work backwards through the tokens and up the dom, covering all options + function crawl(e, i, p) { + while (p = walker[dividedTokens[i]](p, e)) { + if (isNode(p) && (interpret.apply(p, q(tokens[i])))) { + if (i) { + if (cand = crawl(p, i - 1, p)) return cand + } else return p + } + } + } + return (cand = crawl(el, tokens.length - 1, el)) && (!root || isAncestor(cand, root)) + } + + function isNode(el, t) { + return el && typeof el === 'object' && (t = el[nodeType]) && (t == 1 || t == 9) + } + + function uniq(ar) { + var a = [], i, j; + o: + for (i = 0; i < ar.length; ++i) { + for (j = 0; j < a.length; ++j) if (a[j] == ar[i]) continue o + a[a.length] = ar[i] + } + return a + } + + function arrayLike(o) { + return (typeof o === 'object' && isFinite(o.length)) + } + + function normalizeRoot(root) { + if (!root) return doc + if (typeof root == 'string') return qwery(root)[0] + if (!root[nodeType] && arrayLike(root)) return root[0] + return root + } + + function byId(root, id, el) { + // if doc, query on it, else query the parent doc or if a detached fragment rewrite the query and run on the fragment + return root[nodeType] === 9 ? root.getElementById(id) : + root.ownerDocument && + (((el = root.ownerDocument.getElementById(id)) && isAncestor(el, root) && el) || + (!isAncestor(root, root.ownerDocument) && select('[id="' + id + '"]', root)[0])) + } + + function qwery(selector) { + var m, el, root = normalizeRoot(doc) + + // easy, fast cases that we can dispatch with simple DOM calls + if (!root || !selector) return [] + if (selector === window || isNode(selector)) { + return !_root || (selector !== window && isNode(root) && isAncestor(selector, root)) ? [selector] : [] + } + if (selector && arrayLike(selector)) return flatten(selector) + if (m = selector.match(easy)) { + if (m[1]) return (el = byId(root, m[1])) ? [el] : [] + if (m[2]) return arrayify(root[byTag](m[2])) + if (hasByClass && m[3]) return arrayify(root[byClass](m[3])) + } + + return select(selector, root) + } + + // where the root is not document and a relationship selector is first we have to + // do some awkward adjustments to get it to work, even with qSA + function collectSelector(root, collector) { + return function (s) { + var oid, nid + if (splittable.test(s)) { + if (root[nodeType] !== 9) { + // make sure the el has an id, rewrite the query, set root to doc and run it + if (!(nid = oid = root.getAttribute('id'))) root.setAttribute('id', nid = '__qwerymeupscotty') + s = '[id="' + nid + '"]' + s // avoid byId and allow us to match context element + collector(root.parentNode || root, s, true) + oid || root.removeAttribute('id') + } + return; + } + s.length && collector(root, s, false) + } + } + + var isAncestor = 'compareDocumentPosition' in html ? + function (element, container) { + return (container.compareDocumentPosition(element) & 16) == 16 + } : 'contains' in html ? + function (element, container) { + container = container[nodeType] === 9 || container == window ? html : container + return container !== element && container.contains(element) + } : + function (element, container) { + while (element = element.parentNode) if (element === container) return 1 + return 0 + } + , getAttr = function () { + // detect buggy IE src/href getAttribute() call + var e = doc.createElement('p') + return ((e.innerHTML = 'x') && e.firstChild.getAttribute('href') != '#x') ? + function (e, a) { + return a === 'class' ? e.className : (a === 'href' || a === 'src') ? + e.getAttribute(a, 2) : e.getAttribute(a) + } : + function (e, a) { return e.getAttribute(a) } + } () + , selectQSA = function (selector, root) { + var result = [], ss, e + try { + if (root[nodeType] === 9 || !splittable.test(selector)) { + // most work is done right here, defer to qSA + return arrayify(root[qSA](selector)) + } + // special case where we need the services of `collectSelector()` + each(ss = selector.split(','), collectSelector(root, function (ctx, s) { + e = ctx[qSA](s) + if (e.length == 1) result[result.length] = e.item(0) + else if (e.length) result = result.concat(arrayify(e)) + })) + return ss.length > 1 && result.length > 1 ? uniq(result) : result + } catch (ex) { } + return selectNonNative(selector, root) + } + // no native selector support + , selectNonNative = function (selector, root) { + var result = [], items, m, i, l, r, ss + selector = selector.replace(normalizr, '$1') + if (m = selector.match(tagAndOrClass)) { + r = classRegex(m[2]) + items = root[byTag](m[1] || '*') + for (i = 0, l = items.length; i < l; i++) { + if (r.test(items[i].className)) result[result.length] = items[i] + } + return result + } + // more complex selector, get `_qwery()` to do the work for us + each(ss = selector.split(','), collectSelector(root, function (ctx, s, rewrite) { + r = _qwery(s, ctx) + for (i = 0, l = r.length; i < l; i++) { + if (ctx[nodeType] === 9 || rewrite || isAncestor(r[i], root)) result[result.length] = r[i] + } + })) + return ss.length > 1 && result.length > 1 ? uniq(result) : result + } + , configure = function () { + // configNativeQSA: use fully-internal selector or native qSA where present + select = selectNonNative; + + } + + configure(); + + qwery.configure = configure + qwery.uniq = uniq + qwery.is = is + qwery.pseudos = {} + + return qwery; + }()) || (d.querySelectorAll.bind(d)); + + /*!@submodule + * Document Object Model Helper + */ + + var DOM = { + observe:function(elem, etype, ehandle){ + + // based on Diego Perini's solution: [http://javascript.nwbox.com/IEContentLoaded/] + + var iFrame = false, + isDoc = elem[0] === d, + bod = d.body, + COMPL = "complete", + capture = false, + IE_case = (('execScript' in window) && !($h.call(window, 'execScript'))), + eventHandle = IE_case ? 'attachEvent' : 'addEventListener', + eventObserver = function(target){ + + return function(e){ + + //NOTE: (if [bod] is undefined) - IE's flag for "DOM not ready" + if(etype === "onreadystatechange" && d.readyState !== COMPL){ + return; + } + + if(!!~e){ + e = window.event; // update the event object... + e.stopPropagation = (e.stopPropagation) ? e.stopPropagation : function () { e.cancelBubble = true; } + e.preventDefault = (e.preventDefault) ? e.preventDefault : function () { e.returnValue = false; } + e.target = e.srcElement || ((d.documentMode && d.documentMode >= 8)? HTMLDocument : {}); + e.root = e.target.ownerDocument || d; + e.relatedTarget = (e.type.indexOf("mouse") > -1 && e.fromElement === e.target) ? e.toElement : e.fromElement; + e.currentTarget = (!e.currentTarget) ? e.srcElement : this.parentNode; + e.timestamp = (new Date).getTime(); + e.metaKey = (e.type.indexOf("key") > -1 && e.ctrlKey) ? e.ctrlKey : e.shiftKey; + e.which = (e.type == "click") ? e.button : (e.type.indexOf("key") > -1 && e.charCode !== null) ? e.charCode : e.keyCode; + e.pageX = e.clientX + (d.body.scrollLeft || d.documentElement.scrollLeft || 0) - (d.body.clientLeft || d.documentElement.clientLeft || 0); + e.pageY = e.clientY + (d.body.scrollTop || d.documentElement.scrollLeft || 0) - (d.body.clientTop || d.documentElement.clientTop || 0); + } + + ehandle.call(this, e); // [ehandle] is fired only by a call function... + }; + }; + + if(IE_case){ + if(etype == "DOMContentLoaded"){ + etype = "readystatechange"; + } + etype = "on" + etype; + } + + function doReady(){ + try { + d.documentElement.doScroll("left"); + ready(); + } catch(e){ + w.setTimeout(doReady, 0); + } + } + + try{ + iFrame = (w.frameElement !== null); + }catch(er){} + + function ready() { + ehandle.call(null); + DomReady = true; + } + + if(!iFrame && /loaded|readystate/i.test(etype) && isDoc){ + doReady(); + }else{ + ready = null; + elem.forEach(function(elm, index){ + elm[eventHandle](etype, eventObserver(index)); + }); + } + + /* @TODO: detect safari Apple browser version 2 + if(Safari2){ // Deal with (Safari) browsers here + if (DomReady) return; + var set; + if (!(/loaded|complete/).test(state)) { + set = setTimeout(arguments.callee, 0); // if [state] is 'interactive', call this function all over again!! + return; + } + clearInterval(set); + ready(); + }*/ + + + + }, + unobserve:function(elem, etype, ehandle){ + + }, + select: function(selector){ + return $s.call(Qwery(selector)); + }, + get_current_pixel_style:function(elem, prop) { + var value = elem.currentStyle[prop] || 0 + + // we use 'left' property as a place holder so backup values + var leftCopy = elem.style.left + + var runtimeLeftCopy = elem.runtimeStyle.left + // assign to runtimeStyle and get pixel value + elem.runtimeStyle.left = elem.currentStyle.left + elem.style.left = (prop === "fontSize") ? "1em" : value + value = elem.style.pixelLeft + "px"; + // restore values for left + elem.style.left = leftCopy + elem.runtimeStyle.left = runtimeLeftCopy + return value; + + }, + get_css_property:function(sel, prp, unt){ + var res; + unt = unt || "px"; + switch(prp){ + case "float": + prp = (d.all)? "styleFloat" : "cssFloat"; + break; + case "margin": + case "background": + case "padding": + case "border": + case "borderTop": + case "borderBottom": + case "borderLeft": + case "borderRight": + prp = null; + break; + }; + if(!prp) return; + switch(({}).toString.call(sel).substring(8, 11)){ + case "Str": + for(var j = 0; j < d.styleSheets.length; j++){ + var cssRls = d.styleSheets[j].cssRules; + if(!cssRls) cssRls = d.styleSheets[j].rules; + for(var i = 0; i < cssRls.length; i++){ + if(cssRls[i].selectorText == sel){ + try{ + res = cssRls[i].style.getPropertyValue(prp); + }catch(e){ + res = cssRls[i].style.getAttribute(this.utils.decamelize(prp, '-')); + } + } + } + } + break; + case "HTM": + case "Obj": + if(sel.style[prp]){ + res = sel.style[prp]; + }else{ + + res = (w.getComputedStyle) ? w.getComputedStyle(sel, null)[prp] : ((sel.runtimeStyle)? (unt=="px"? this.get_current_pixel_style(sel, prp) : sel.runtimeStyle[prp]) : + d.defaultView.getComputedStyle(sel,null).getPropertyValue(this.utils.decamelizr(prp, '-'))); + } + break; + default: + throw "invalid object "+sel+" found!"; + break; + } + + return res; + }, + is_node:function(o){ + return (o !== void 0 && o.nodeType); + }, + is_node_disconnected:function(obj){ + return (obj !== void 0 && this.is_node(obj) && obj.offsetParent === null); + }, + get_attrib:function(ob, a, isXML){ + + if(isXML){ + + var attr = getAttributeByName(ob, a); + + if (attr != null) { + return attr.nodeValue; + } else { + return obj[a]; + } + } + + return (a === 'class') ? ob.className : (a === 'href' || a === 'src') ? ob.getAttribute(a, 2) : (a === "style") ? ob.style.cssText.toLowerCase() : (a === "for")? ob.htmlFor : ob.getAttribute(a); + }, + set_attrib:function(ob, a, vl, isXML){ + if(isXML){ + var attr = getAttributeByName(ob, a); + + if (attr !== null) { + attr.nodeValue = vl; + } else { + attr = d.createAttribute(a); + attr.value = vl; + ob.setAttributeNode(attr); + } + return; + } - Phlorx.createUIStream = function (view, event){ - return $(view).getStream(event); + return (a === 'class')? ob.className = vl : (a === 'href')? ob.href = vl : a === "style" ? ob.style.cssText += ''+vl : ob.setAttribute(a, vl); + + }, + dom_name:function(o){ + return this.is_node(o) && o.nodeName.toLowerCase(); + }, + utils:{ + decamelize:function(str, delim){ + return str.replace(/([A-Z])/g, delim+"$1").toLowerCase(); + }, + camelize:function(str, delim){ + var rx = new RegExp(delim+"(.)","g"); + return s.replace(rx, function (m, m1){ + return m1.toUpperCase(); + }); + } + } + }; + + function b(){ + return this; + } + + b.fn = { + type:function(){ + + } + }; + + b.fn.each = Object.each; + + b["prototype"] = b.fn; + + b.fn.init = function(selector){ + + var collections = [DOM.select(selector)]; + + this.updateCollection = function(x){ + collections.unshift(x); + return this; + }; + + this.getCurrentCollection = function(){ + return collections[0]; + }; + + return this; } - Phlorx.createWorkStream = function(set){ - return new DataStream(set, {}); + + b.fn.init["prototype"] = { + css:function(opts){ var x = this.getCurrentCollection(); if(typeof opts == st) return DOM.get_css_property(x[0], opts); b.fn.each(x, function(item){ for(var t in opts){ item.style.cssText += DOM.utils.decamelize(t,'-')+":"+opts[t]+";" } }); return this; }, + offCss:function(prop){ if(prop===null) return; var g, rx, x = this.getCurrentCollection(); b.fn.each(x, function(item){ if(!d.all){ item.removeProperty(prop); }else{ rx=new RegExp(prop+"\\:([#!*%\\w]+);"), g=DOM.get_attrib(item, "style"); g=g.replace(rx, ""); item.cssText+=g; } }); return this; }, + next:function(){ var r, n, x =this.getCurrentCollection(); b.fn.each(x, function(item){ r = item.parentNode; n = item.nextSibling; if(n && r.childNodes.length > 1){ while (n !== null) { if (n.nodeType == 1) break; if (n.nodeType == 3) n = n.nextSibling; if (n.nodeType == 9) n = d; }} }); return this.updateCollection(n); }, + parent:function(){ var mk = [], cl, x = this.getCurrentCollection(); b.fn.each(x, function(item){ cl = item.parentNode; mk.push(cl); }); return this.updateCollection(mk); }, + each:function(fh){ b.fn.each(this.getCurrentCollection(), fh); return this; }, + attr:function(nm, val){ var g, x = this.getCurrentCollection(); if(!val){ return DOM.get_attrib(x[0], nm); } b.fn.each(x, function(item){ DOM.set_attrib(item, nm, val) }); return this; }, + removeAtrr:function(s){ if(typeof s !== st){ return this;} var x = this.getCurrentCollection(); b.fn.each(x, function(item){ item.removeAttribute(s); }); return this; }, + html:function(txt){ var x = this.getCurrentCollection(); if(!txt){ return x[0].innerHTML; } b.fn.each(x, function(item){ item.innerHTML = txt; }); return this; } + }; + + Phlorx.workStream = function(data){ + return new DataStream(data); }; - Phlorx.createBasicStream = function(data){ - return new DataStream(data, {}, true); + Phlorx.basicStream = function(data){ + return new DataStream(data, true); }; + Phlorx.UI = {}; + /* + Phlorx.UI.textFieldValue = function(textField){ + function value(event){ return event ? event.target.value : "" ; }; + return Phlorx.viaDOM("keyup", textField).map(value).toProp(value()); + }; + */ + Phlorx.UI.DOM = function(selector){ + return new b.fn.init(selector); + } + + Phlorx.viaBinder = function(Binder){ + var stream = this.basicStream(null), d = new Futures(); + PhlorxStreamsMap['binds'][stream.getEvent()] = d; + if(typeof Binder === "function"){ + d.then(function(sink){ + Binder(sink[0]); + }) + .then(function(stopper){ + stream.whenUnsubscribe(stopper); + }); + } + return stream; + }; + + Phlorx.viaDOM = function(DOMEvent, selector){ + selector = selector || d; + return this.viaBinder(function(sink){ + DOM.observe(DOM.select(selector), DOMEvent, sink); + + return function(){ + DOM.unobserve(DOM.select(selector), DOMEvent, sink); + } + }); + }; + Phlorx.interval = function(interval, data){ - var $interv = this.createWorkStream(null), $tmr; - $tmr = setTimeout(function repeat(){ - $interv.fireAtCore(data); - $tmr = setTimeout(repeat, interval); - }, interval); - return $interv.filter(function(data){ return Array.isArray(data)? data.length > 0 : !!data; }); + return this.viaBinder(function(sink){ + var tid = setInterval(function(){ + sink(typeof data === "function" ? data() : data); + }, interval); + + return function(){ + clearInterval(tid); + } + }); + }; + + Phlorx.viaPoll = function(delay, callback){ + return this.interval(delay, callback); }; - Phlorx.sequentially = function(interval, array){ - var $sequent = this.interval(interval, array); - return $sequent.map(function(arr){ return arr.shift(); }); + Phlorx.viaCallback = function(fn){ + this.viaBinder(fn); }; + + Phlorx.viaNodeCallback = Phlorx.viaCallback; + + Phlorx.ajax = function(options, sync){ + + options = options || {} + sync = sync || false; + + var xhr = null, deferred = new Futures(); + + try{ + xhr = CreateMSXMLDocument(); + if(xhr === null){ + xhr = new XMLHttpRequest(); + } + if(options.crossdomain && 'XDomainRequest' in w){ + xhr = new XDomainRequest(); + } + }catch(ex){} + + + if(!options.headers){ + options.headers = {}; + } + + for (var prop in options.headers){ + if (headers.hasOwnProperty(prop)) { + xhr.setRequestHeader(prop, headers[prop]); + } + } + + if (!sync && options.method && options.url){ + if(xhr instanceof w.XDomainRequest){ + xhr.open(options.method, options.url); + xhr.onload = function(){ + deferred.resolve(requestComplete(xhr, {fakeStatus:200}).xhr); + } + xhr.onerror = function(){ + deferred.reject(requestComplete(xhr, {fakeStatus:400}).xhr); + } + xhr.ontimeout = function(){ + deferred.reject(requestComplete(xhr, {fakeStatus:0}).xhr); + } + + xhr.onprogress = function () {}; + xhr.timeout = 0; + }else{ + xhr.open(options.method, options.url, !sync); + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + if(xhr.status >= 400){ + deferred.reject(requestComplete(xhr, {status:xhr.status, error:true}).xhr); + }else{ + deferred.resolve(requestComplete(xhr, {status:xhr.status, error:false}).xhr); + } + } + }; + } + } + + // + // research indicates that IE is known to just throw exceptions + // on .send and it seems everyone pretty much just ignores them + // including jQuery (https://github.com/jquery/jquery/blob/1.10.2/src/ajax.js#L549 + // https://github.com/jquery/jquery/blob/1.10.2/src/ajax/xhr.js#L97) + // + try { + xhr.send(options.data); + } + catch (ex) { + xhr = null; + } - Phlorx.setAction = function(view, action, stream){ - return $(view).setAction((typeof action == "function"? action(stream) : action), stream); + return deferred.promise(); + + }; + + Phlorx.sequentially = function(delay, array){ + array = array || []; + delay = delay || 0; + return this.viaPoll(delay, function(){ return array.shift(); }); + }; + + Phlorx.viaArray = function(array){ + return this.sequentially(null, array); + }; + + Phlorx.later = function(delay, value){ + return this.sequentially(delay, [value]); }; - Phlorx.fromPromise = function(promise){ - var $p_stream = this.createWorkStream(null); - if(typeof promise.promise == "function" && (typeof promise.promise().then === "function")){ // according to the Promise/A+ spec, it should be a "thenable" + Phlorx.viaPromise = function(promise){ + var $p_stream = this.workStream(null); + PhlorxStreamsMap['promises'][uuid()] = promise; + if(promise && typeof promise.promise == "function" && (typeof promise.promise().then === "function")){ // according to the Promise/A+ spec, it should be a "thenable" if(typeof promise.then == "function"){ promise.then(function(data){ - $p_stream.fireAtCore(data); // success + $p_stream.fireAtCore.apply($p_stream, $s.call(arguments)); }, function(err){ - $p_stream.fireAtCore(err); // fail + ($p_stream.getErrorHandle())(err); }); // the notify handler won't be necessary... this is FRP! } @@ -90,10 +1190,43 @@ window.Phlorx = (function(w, $, factory){ } return $p_stream; }; + + Phlorx.retry = function(option){ + option = option || {}; + var callup = function(stream){ + if(typeof option.source === "function"){ + if(typeof option.retryCount === "number"){ + ++option.retryCount; + }else{ + option.retryCount = 1; + } + + if(option.retryCount !== option.retries){ + var pream = option.source(); + pream.onValue(function(data){ + stream.fireAtCore(data); + stream = null; + }); + pream.onError(function(err){ + // stream.log(err); + setTimeout(function(){ + stream.offValue(); + callup(stream); + }, ((typeof option.delay === "function" && option.delay()) || 0)); + }); + } + }else{ + stream = null; + } + }, + stream = this.workStream(); + callup(stream); + return stream; + }; return Phlorx; -}(this, this.jQuery, function(hOwn, slice, w){ +}(this, this.document, function(hOwn, slice, w, maps){ // Helpers @@ -119,6 +1252,24 @@ window.Phlorx = (function(w, $, factory){ return arr && (arr instanceof Array); }; + if(!Function.prototype.bind){ + Function.prototype.bind = function(obj) { + + var args = slice.call(arguments, 1), + self = this, + nop = function () {}, + bound = function () { + return self.apply(this instanceof nop ? this : (obj || {}), args.concat(slice.call(arguments))); + }; + + nop.prototype = self.prototype; + + bound.prototype = new nop(); + + return bound; + }; +} + Object.each = function (obj, iterator, context) { var key, length, temp; @@ -161,7 +1312,9 @@ window.Phlorx = (function(w, $, factory){ }; // a fairly complex Pub/Sub interface using the observer pattern var ObserverCore = function(){ + var handlers = {}, + offEvents = [], reparate = function(array){ var obj = {}; @@ -178,7 +1331,11 @@ window.Phlorx = (function(w, $, factory){ }; return { - + + acumulator:function(){ + + }, + canLog:false, emit:function(evt){ var scope, f=-1, res=[], nx, base = {}, set, data = slice.call(arguments, 1), ah = false; @@ -194,6 +1351,10 @@ window.Phlorx = (function(w, $, factory){ ah = handlers[evt]; } + if(this.canLog && w.console !== void 0){ + w.console.log('Phlorx Event Log: ', data); + } + if(ah){ while(f < ah.length - 1){ f=f+1; @@ -205,6 +1366,7 @@ window.Phlorx = (function(w, $, factory){ continue; } res.unshift({result:ah[f]["fn"].apply(ah[f]["cxt"], data), name:nx}); + if(res[0].result === null || typeof res[0].result === "undefined"){ res.shift(); } @@ -212,9 +1374,7 @@ window.Phlorx = (function(w, $, factory){ break; } } - }else{ - // throw new Error(); ...hold the thought, this is FRP! - } + } f=0; base[evt] = (res.length)? reparate(res) : null; @@ -251,11 +1411,14 @@ window.Phlorx = (function(w, $, factory){ once:function(evt, callback, context){ }, + whenOff:function(callback){ + offEvents.push(callback); + }, has:function(evt){ return (!evt)? !!evt : handlers.hasOwnProperty(evt); }, poof:function(){ - handlers = {}; + handlers = {}; }, emitList:function(events, data, context){ var result = {}, scope, ev; @@ -283,8 +1446,13 @@ window.Phlorx = (function(w, $, factory){ this.splice(v, 1); }, handlers[evt]); } - + Object.each(offEvents, function(v){ + if(typeof v === "function"){ + setTimeout(v, 1); + } + }); } + return self; // chaining } }; @@ -295,13 +1463,13 @@ window.Phlorx = (function(w, $, factory){ }; /* i am making use of the prototypes [e.g Stream] to make inheritance easier */ - - // a fairly complex [Stream] observable using the adapter pattern is employed here to make things easy + /* a fairly complex [Stream] observable using the adapter pattern */ function Stream(event){ // Stream [Adapter Class] if(typeof event != "string"){ throw new TypeError("first argument nust be a string"); } + var errorHandle; var $event = event; var $queue = []; var $emitCore = ObserverCore(this); // bind a reference... @@ -324,12 +1492,34 @@ window.Phlorx = (function(w, $, factory){ return setter; }; + + this.onError = function(cb){ + if(errorHandle === null){ + errorHandle = cb; + } + }; + + this.offError = function(){ + if(errorHandle){ + errorHandle = null; + } + }; + + this.getErrorHandle = function(){ + return function(err){ + typeof errorHandle === "function" && errorHandle(err); + } + }; this.toString = function(){ - return "[object Stream]"; + return "[object Stream]"; + }; + + this.log = function(){ + $emitCore.canLog = true; }; - this.getEvent = function(){ + this.getEvent = function(){ return $event; }; @@ -340,17 +1530,23 @@ window.Phlorx = (function(w, $, factory){ }; - this.subscribe = function(callback){ // adapter interface + this.onValue = function(callback){ // adapter interface return $emitCore.on(this.getEvent(), callback, this); }; - this.unsubscribe = function(){ // adapter interface + this.offValue = function(){ // adapter interface return $emitCore.off(this.getEvent()); }; + + this.whenUnsubscribe = function(callback){ // adapter interface + if(typeof callback == "function"){ + $emitCore.whenOff(callback); + } + }; this.hasCoreEvent = function(){ // adapter interface @@ -359,8 +1555,11 @@ window.Phlorx = (function(w, $, factory){ }; this.fireAtCore = function(c){ // adapter interface to observer - + try{ return $emitCore.emit(this.getEvent(), c); + }catch(e){ + this.getErrorHandle()(e); + } }; @@ -376,47 +1575,51 @@ window.Phlorx = (function(w, $, factory){ }; this.linkStream = function(stream, callback){ - /*if(stream !== null || typeof stream.subscribe != "function"){ - throw new Error("-----------------") - }*/ - if(stream.subscribe.toString() == this.subscribe.toString()){ - this.subscribe(function(data){ - var bool = typeof callback == "function"; - stream.fireAtCore((bool? callback(data) : data)); - }); + if(stream === null){ + throw new Error("[Stream] not okay"); + } + if(typeof stream.onValue == typeof this.onValue){ + this.onValue(function(data){ + var bool = typeof callback == "function"; + stream.fireAtCore((bool? callback(data) : data)); + }); } } + return this; } - function DataEventStream(eventOrData, __proto, isBasic){ // DataEventStream [Implementer Class] extends Stream [Adapter Class] - if(typeof __proto != "object" || !("constructor" in __proto)){ - throw new TypeError("second argument must be an object"); - } - this.getProto = function(){ - return __proto; - } - var _$ = __proto.constructor; - var $stream = Stream.apply(this, [generateEventId(26)]); + function DataEventStream(eventOrData, isBasic){ // DataEventStream [Implementer Class] extends Stream [Adapter Class] - if(_$ === w["jQuery"]){ - __proto.each(function(index){ - var $this = _$(this); - $this.on(""+eventOrData, function(e){ - $stream.fireAtCore(e); - }); - }); - }else{ - if(isBasic){ - var formerSubscribe = $stream.subscribe || (function(){}); - $stream.subscribe = function(callback){ - formerSubscribe.call(this, callback); - this.fireAtCore(eventOrData); - }; - } - } + var $stream = Stream.apply(this, [generateEventId(26)]); + var self = this; + if(isBasic){ + var formerSubscribe = $stream.onValue || (function(){}); + $stream.onValue = function(callback){ + var _self = this; + formerSubscribe.call(this, callback); + console.log("lala", this.getEvent(), maps['binds']); + if(this.getEvent() in maps['binds']){ + (maps['binds'][this.getEvent()]).resolve(function(data){ + stream = _self.getStream(); + if(data === void 0){ + stream.offValue(); + return; + } + if(data instanceof Error){ + (stream.getErrorHandle())(data); + return; + } + stream.fireAtCore.apply(stream, slice.call(arguments)); + }); + }else{ + this.fireAtCore(eventOrData); + } + }; + } + this.getStream = function(){ return $stream; @@ -425,27 +1628,52 @@ window.Phlorx = (function(w, $, factory){ return $stream; } - + + /* @TODO: next set of commits + DataEventStream.prototype.concat = function(){ + + }; + + DataEventStream.prototype.zip = function(){ + + }; + */ + DataEventStream.prototype.filter = function(callback){ if(typeof callback != "function"){ throw new TypeError("first argument must be a function"); } var $S = this.getStream(); - var $f_stream = new DataEventStream(null, {}); - $S.subscribe(function(data){ - if(callback(data)){ + var $f_stream = new DataEventStream(null); + $S.onValue(function(data){ + if(callback(data) === true){ $f_stream.fireAtCore(data); } }); return $f_stream; }; + + DataEventStream.prototype.take = function(integer){ + limit.integer = integer || 0; + return this.filter(function limit(data){ + --limit.integer; + return (limit.integer !== 0); + }); + }; + + DataEventStream.prototype.once = function(value){ + var $S = this.getStream(); + var $o_stream = new DataEventStream(value, true); + + return $o_stream.take(1); + }; DataEventStream.prototype.map = function(callback){ if(typeof callback != "function"){ throw new TypeError("first argument must be a function"); } var $S = this.getStream(); - var $_stream = new DataEventStream(null, {}); + var $_stream = new DataEventStream(null); $S.linkStream($_stream, callback); return $_stream; @@ -456,7 +1684,7 @@ window.Phlorx = (function(w, $, factory){ throw new TypeError("first argument must be a [Stream] object"); } var $S = this.getStream(); - var $m_stream = new DataEventStream(null, {}); + var $m_stream = new DataEventStream(null); var $C = function (stream){ var __run = function (data){ this.queryQueue(function(queue){ @@ -466,28 +1694,28 @@ window.Phlorx = (function(w, $, factory){ }; return __run; }; - $S.subscribe($C($m_stream)); - stream.subscribe($C($m_stream)); + $S.onValue($C($m_stream)); + stream.onValue($C($m_stream)); return $m_stream; }; DataEventStream.prototype.flatMap = function(callback){ - var $C = function(stream, callb){ + var $C = function(stream, callb){ var __run = function(e){ callb.apply(this, arguments).linkStream(stream); } return __run; }; - var $S = this.getStream(); - var $fm_stream = new DataEventStream(null, {}); - $S.subscribe($C($fm_stream, callback)); + var $S = this.getStream(); + var $fm_stream = new DataEventStream(null); + $S.onValue($C($fm_stream, callback)); return $fm_stream; }; DataEventStream.prototype.combineLatest = function(stream, callback){ var self = this; var $merged = self.mergeToNew(stream); - $merged.subscribe(function(data){ + $merged.onValue(function(data){ var latest = function(q){ return Boolean(!!q[0]); }; var retrieve = function(q){ return q.shift(); }; if(self.queryQueue(latest) === stream.queryQueue(latest)){ @@ -497,7 +1725,7 @@ window.Phlorx = (function(w, $, factory){ return $merged; }; -DataEventStream.prototype.scan = function(callback, initial){ +DataEventStream.prototype.scan = function(initial, callback){ var $C = function(callb){ var __run = function(data){ data = initial; @@ -514,8 +1742,8 @@ DataEventStream.prototype.throttle = function(millis){ } var $throttle = this.getDelayFn(); var $S = this.getStream(); - var $t_stream = new DataEventStream($S.getEvent(), {}); - $S.subscribe($throttle(function(evtObj){ + var $t_stream = new DataEventStream($S.getEvent()); + $S.onValue($throttle(function(evtObj){ $t_stream.fireAtCore.apply($t_stream, arguments); }, millis)); return $t_stream; diff --git a/tests/specRunner.html b/tests/specRunner.html index b2fc13b..be6c864 100644 --- a/tests/specRunner.html +++ b/tests/specRunner.html @@ -9,7 +9,6 @@ - diff --git a/tests/specs/streamSpec.js b/tests/specs/streamSpec.js index f2b52a0..195716f 100644 --- a/tests/specs/streamSpec.js +++ b/tests/specs/streamSpec.js @@ -1,30 +1,97 @@ /** - * @cdv Unit Testing + * @title: Unit Testing * @project: Phlorx */ -describe("Phlorx", function() { - var xStream; - var yStream; - var zData; +describe("Phlorx: the different features of a Phlorx stream", function() { - if(!window.jQuery || (window.jQuery.fn !== window.jQuery.prototype)){ - alert("Phlorx requires jQuery!"); - return; - } - - describe("the different features of a Phlorx stream", function() { + var xStream; + var yStream; + var zData; + var callb; + var val; + var stream; beforeEach(function() { - xStream = Phlorx.createWorkStream(null); - yStream = Phlorx.createBasicStream("primary"); + xStream = Phlorx.workStream(null); + yStream = Phlorx.basicStream("primary"); zData = false; + stream = Phlorx.later(1000, 8); + callb = function(){ + + }; + + jasmine.addMatchers({ + toBeAPromise:function(util, customEqualityTesters){ + return { + compare:function(actual, expected){ + if(!expected){ + expected = true; + } + + var result = {}; + + result.pass = util.equals((typeof actual.then == "function") // test to see if it's a "thenable"! + && (typeof actual.promise == "function") + && (actual.promise() === actual), expected, customEqualityTesters); + + if(result.pass){ + result.message = "It's a promise"; + }else{ + result.message = "It's not a promise"; + } + + return result; + } + + } + }, + /*toBeInstanceOf:function(util, customEqualityTesters){ + return { + compare:function(actual, expected){ + (actual instanceof expected); + } + + } + },*/ + toBeAFunction:function(util, customEqualityTesters){ + return { + compare:function(actual, expected){ + if(!expected){ + expected = true; + } + + var result = {}; + + result.pass = util.equals((typeof actual == "function"), expected, customEqualityTesters); + + if(result.pass){ + result.message = "It's a function"; + }else{ + result.message = "It's not a function"; + } + + return result; + } + + } + } + }); }); - it("should replicate as a stream, everytime!", function() { + afterEach(function() { + xStream = null; + yStream = null; + zData = null; + callb = null; + val = null; + stream = null; + }); + + it("should replicate as a stream, on any given operator call everytime!", function() { expect(xStream.toString()).toEqual("[object Stream]"); @@ -34,11 +101,11 @@ describe("Phlorx", function() { expect((xStream.map(function(data){ return (data % 2); })).toString()).toEqual("[object Stream]"); - }); + }); - it("should recieve notification when subscribed", function() { + it("should recieve notification when subscribed", function() { - yStream.subscribe(function(data){ + yStream.onValue(function(data){ zData = data; @@ -46,7 +113,7 @@ describe("Phlorx", function() { expect(zData).toEqual("primary"); - xStream.subscribe(function(data){ + xStream.onValue(function(data){ zData = data; @@ -56,19 +123,48 @@ describe("Phlorx", function() { expect(zData).toEqual(3); + }); + + it("should interact with dependencies", function(done){ + + spyOn(Phlorx, "viaBinder"); + + spyOn(Phlorx, "interval"); + + Phlorx.viaCallback(callb); + + expect(Phlorx.viaBinder).toHaveBeenCalled(); + + expect(Phlorx.viaBinder).toHaveBeenCalledWith(callb); + + + stream.onValue(function(d){ + val = d; + done(); + }); + + + // expect(val).toEqual(8); + // expect(Phlorx.interval).toHaveBeenCalled(); + + }); - it("should have standard interfaces and throw errors where necessary", function() { + it("should have/present standard interfaces and throw errors where necessary", function() { + + expect(Phlorx.ajax).toBeAFunction(); + + expect(Phlorx.ajax()).toBeAPromise(); - expect(Phlorx.fromPromise).toBeTruthy(); + expect(Phlorx.viaPromise).toBeAFunction(); expect(function(){ - Phlorx.fromPromise(); + Phlorx.viaPromise(); }).toThrowError("first argument must be a standard promise object"); - expect(xStream.filter).toBeTruthy(); + expect(xStream.filter).toBeAFunction(); expect(function(){ @@ -76,7 +172,7 @@ describe("Phlorx", function() { }).toThrowError("first argument must be a function"); - expect(yStream.mergeToNew).toBeTruthy(); + expect(yStream.mergeToNew).toBeAFunction(); expect(function(){ @@ -85,7 +181,6 @@ describe("Phlorx", function() { }).toThrowError("first argument must be a [Stream] object"); }); - }); });