├── .gitignore ├── images ├── ajax.gif ├── bacon.png └── registration-form-bacon.png ├── mocks.js ├── lib ├── Bacon.UI.js ├── jquery.mockjax.js └── Bacon.js ├── index.html ├── README.md └── css └── register.css /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /images/ajax.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raimohanska/bacon-devday-code/HEAD/images/ajax.gif -------------------------------------------------------------------------------- /images/bacon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raimohanska/bacon-devday-code/HEAD/images/bacon.png -------------------------------------------------------------------------------- /images/registration-form-bacon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/raimohanska/bacon-devday-code/HEAD/images/registration-form-bacon.png -------------------------------------------------------------------------------- /mocks.js: -------------------------------------------------------------------------------- 1 | $.mockjax({ 2 | url: '/usernameavailable/*', 3 | dataType: 'json', 4 | response: function(settings) { 5 | var username = settings.url.match(/.+\/(\w+$)/)[1] 6 | var available = username.length % 2 == 0 7 | this.responseText = available; 8 | } 9 | }); 10 | 11 | $.mockjax({ 12 | url: "/register", 13 | dataType: "json", 14 | type: "post", 15 | response: function(settings) { 16 | this.responseText = "true" 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /lib/Bacon.UI.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var isChrome = navigator.userAgent.toLowerCase().indexOf('chrome') > -1; 3 | function nonEmpty(x) { return x && x.length > 0 } 4 | 5 | Bacon.UI = {} 6 | Bacon.UI.textFieldValue = function(textfield, initValue) { 7 | function getValue() { return textfield.val() } 8 | function autofillPoller() { 9 | if (textfield.attr("type") == "password") 10 | return Bacon.interval(100) 11 | else if (isChrome) 12 | return Bacon.interval(100).take(20).map(getValue).filter(nonEmpty).take(1) 13 | else 14 | return Bacon.never() 15 | } 16 | if (initValue !== null) { 17 | textfield.val(initValue) 18 | } 19 | return textfield.asEventStream("keyup input"). 20 | merge(textfield.asEventStream("cut paste").delay(1)). 21 | merge(autofillPoller()). 22 | map(getValue).toProperty(getValue()).skipDuplicates() 23 | } 24 | Bacon.UI.optionValue = function(option) { 25 | function getValue() { return option.val() } 26 | return option.asEventStream("change").map(getValue).toProperty(getValue()) 27 | } 28 | Bacon.UI.checkBoxGroupValue = function(checkboxes, initValue) { 29 | function selectedValues() { 30 | return checkboxes.filter(":checked").map(function(i, elem) { return $(elem).val()}).toArray() 31 | } 32 | if (initValue) { 33 | checkboxes.each(function(i, elem) { 34 | $(elem).attr("checked", initValue.indexOf($(elem).val()) >= 0) 35 | }) 36 | } 37 | return checkboxes.asEventStream("click").map(selectedValues).toProperty(selectedValues()) 38 | } 39 | Bacon.Observable.prototype.pending = function(src) { 40 | return src.map(true).merge(this.map(false)).toProperty(false) 41 | } 42 | Bacon.EventStream.prototype.ajax = function() { 43 | return this["switch"](function(params) { return Bacon.fromPromise($.ajax(params)) }) 44 | } 45 | Bacon.UI.radioGroupValue = function(options) { 46 | var initialValue = options.filter(':checked').val() 47 | return options.asEventStream("change").map('.target.value').toProperty(initialValue) 48 | } 49 | })(); 50 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 39 | 40 | 41 |
42 |

Bacon Registration Form

43 |
44 | 45 | 46 | Username is unavailable 47 |
48 |
49 | 50 |
51 |
52 | 53 | 54 | 55 |
56 |
57 | 58 | 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is the Bacon.JS coding excercise we did at ReaktorDevDay 2012. 2 | Below are the instructions in case you wanna try it yourself. You may also 3 | have a look at the [Full Solution](https://github.com/raimohanska/bacon-devday-code/tree/full-solution) which 4 | is in another branch. 5 | 6 | ## Preparations 7 | 8 | 1. Clone this repo 9 | 10 | ~~~ 11 | git clone https://github.com/raimohanska/bacon-devday-code.git 12 | cd bacon-devday-code 13 | ~~~ 14 | 15 | 2. Open the index.html file in your browser 16 | 17 | ~~~ 18 | open index.html 19 | ~~~ 20 | 21 | 3. Make sure you have developer tools in your browser and that you can 22 | use them. Google Chrome will do. In Chrome (Mac OSX), Go to View -> 23 | Developer -> Developer Tools. You should be able to run Javascript 24 | expressions on the Console tab. 25 | 26 | 4. Try some expression in the Developer Console, like 27 | 28 | ~~~ 29 | $("#username input").asEventStream("keyup") 30 | ~~~ 31 | 32 | 5. Have a look at Bacon.js 33 | [readme](https://github.com/raimohanska/bacon.js/blob/master/README.md) 34 | 35 | ## Map 36 | 37 | Here's how I modeled the problem for Bacon.js reactive code. 38 | 39 | ![diagram](https://raw.github.com/raimohanska/bacon-devday-code/master/images/registration-form-bacon.png) 40 | 41 | Side-effects are not depicted. 42 | 43 | ## Steps to success 44 | 45 | 1. Disable button if username is missing 46 | * define usernameEntered property 47 | * assign side-effect: setDisabled for registerButton 48 | 49 | 2. Disable also if full name is missing 50 | * define fullname and fullnameEntered properties 51 | * use .and() to change the condition for enabling the button 52 | 53 | 3. Disable also if username unavailable 54 | * include usernameAvailable to the condition for enabling the button 55 | 56 | 4. Show AJAX indicator when AJAX pending 57 | * define usernameRequestPending property as usernameResponse.pending(usernameRequest) 58 | * assign side effect to show usernameAjaxIndicator 59 | 60 | 5. Disable button when AJAX is pending 61 | 62 | 6. Implement registerClick stream 63 | * tip: do(".preventDefault") 64 | 65 | 7. Implement registrationRequest 66 | * combine username and password into a new property that would be the data given to JQuery.ajax 67 | * can use username.combine(..) or Bacon.combineTemplate 68 | * type: "POST" 69 | 70 | 8. Make this a stream of registration requests, send when the button is clicked 71 | * .sampledBy(registerClick) 72 | 73 | 9. Create registrationResponse stream 74 | * as in usernameResponse stream 75 | 76 | 10. Show feedback 77 | 78 | 11. Disable button after registration sent 79 | 80 | 12. Show ajax indicator for registration POST 81 | -------------------------------------------------------------------------------- /css/register.css: -------------------------------------------------------------------------------- 1 | body, input, button, h1 { 2 | font-family: 'Comic Sans MS', sans-serif; 3 | } 4 | 5 | .ajax { 6 | background: url('../images/ajax.gif') no-repeat; 7 | display: inline-block; 8 | width: 54; 9 | height: 54; 10 | margin-top: -2px; 11 | } 12 | 13 | #username-unavailable { 14 | display: none; 15 | } 16 | 17 | #username { 18 | position: relative; 19 | } 20 | 21 | #username .ajax { 22 | position: absolute; 23 | top: 10px; 24 | right: 0px; 25 | } 26 | 27 | .tooltip { 28 | font-size:1.7em; 29 | color: #fff; 30 | display:inline; 31 | padding:15px; 32 | position: absolute; 33 | left: 100%; 34 | top: 0%; 35 | white-space:nowrap; 36 | background-color: #FF0000; 37 | -webkit-border-radius: 10px 10px 10px 10px; 38 | -moz-border-radius: 10px 10px 10px 10px; 39 | border-radius: 10px 10px 10px 10px; 40 | border:2px solid #FF0000; 41 | -webkit-box-shadow: #B3B3B3 4px 4px 4px; 42 | -moz-box-shadow: #B3B3B3 4px 4px 4px; 43 | box-shadow: #B3B3B3 4px 4px 4px; 44 | z-index:97 45 | } 46 | 47 | .tooltip:before { 48 | border:solid; 49 | border-color:#FF0000 transparent; 50 | border-width:20px 20px 0 20px; 51 | top: 15px; 52 | left: -20px; 53 | content:""; 54 | display:block; 55 | position:absolute; 56 | z-index:98 57 | } 58 | 59 | #login-container { 60 | position: relative; 61 | width:400px; 62 | height:300px; 63 | margin:50px; 64 | padding: 0px 120px 40px 40px; 65 | background-color: #323B55; 66 | background-image: -webkit-linear-gradient(bottom, #323B55 0%, #424F71 100%); 67 | background-image: -moz-linear-gradient(bottom, #323B55 0%, #424F71 100%); 68 | background-image: -ms-linear-gradient(bottom, #323B55 0%, #424F71 100%); 69 | background-image: linear-gradient(bottom, #323B55 0%, #424F71 100%); 70 | -webkit-border-radius: 30px 40px 50px 90px; 71 | -moz-border-radius: 30px 40px 50px 90px; 72 | border-radius: 30px 40px 50px 90px; 73 | border:10px solid #F2F2F2; 74 | } 75 | 76 | #login-container:after { 77 | position:absolute; 78 | top:-50px; 79 | right:-100px; 80 | content: url('../images/bacon.png'); 81 | width: 200px; 82 | height: 176px; 83 | display: block; 84 | } 85 | 86 | #username, #fullname { 87 | position: relative; 88 | } 89 | 90 | #login-container h1 { 91 | color: #fff; 92 | } 93 | 94 | #login-container button { 95 | background-color: #FFFF00; 96 | border-color: #dddd00; 97 | color: #c00; 98 | } 99 | 100 | #login-container button:disabled { 101 | background-color: #888; 102 | color: #444; 103 | border-color: #666; 104 | } 105 | 106 | #login-container input { 107 | color: #800; 108 | } 109 | 110 | #login-container input::-webkit-input-placeholder { color: LightGray; } 111 | #login-container input:-moz-placeholder { color: LightGray; } 112 | #login-container input:-ms-input-placeholder { color: LightGray; } 113 | 114 | 115 | #login-container input, 116 | #login-container button { 117 | padding: 5px; 118 | font-size: 2.0em; 119 | margin-top: 10px; 120 | -webkit-border-radius: 15px 15px 15px 15px; 121 | -moz-border-radius: 15px 15px 15px 15px; 122 | border-radius: 15px 15px 15px 15px; 123 | border-width:3px; 124 | } 125 | #login-container input { 126 | width: 85%; 127 | } 128 | #login-container button { 129 | position: absolute; 130 | left: auto; 131 | right: -95px; 132 | } 133 | 134 | #register { 135 | position: relative; 136 | } 137 | 138 | #register .ajax { 139 | position: absolute; 140 | right: 81px; 141 | top: 12px; 142 | } 143 | 144 | #register #result { 145 | display: inline-block; 146 | margin-top: 10px; 147 | font-size: 2em; 148 | color: #ff0; 149 | text-shadow: black 4px 4px 4px; 150 | } 151 | -------------------------------------------------------------------------------- /lib/jquery.mockjax.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * MockJax - jQuery Plugin to Mock Ajax requests 3 | * 4 | * Version: 1.5.1 5 | * Released: 6 | * Home: http://github.com/appendto/jquery-mockjax 7 | * Author: Jonathan Sharp (http://jdsharp.com) 8 | * License: MIT,GPL 9 | * 10 | * Copyright (c) 2011 appendTo LLC. 11 | * Dual licensed under the MIT or GPL licenses. 12 | * http://appendto.com/open-source-licenses 13 | */ 14 | (function($) { 15 | var _ajax = $.ajax, 16 | mockHandlers = [], 17 | CALLBACK_REGEX = /=\?(&|$)/, 18 | jsc = (new Date()).getTime(); 19 | 20 | 21 | // Parse the given XML string. 22 | function parseXML(xml) { 23 | if ( window['DOMParser'] == undefined && window.ActiveXObject ) { 24 | DOMParser = function() { }; 25 | DOMParser.prototype.parseFromString = function( xmlString ) { 26 | var doc = new ActiveXObject('Microsoft.XMLDOM'); 27 | doc.async = 'false'; 28 | doc.loadXML( xmlString ); 29 | return doc; 30 | }; 31 | } 32 | 33 | try { 34 | var xmlDoc = ( new DOMParser() ).parseFromString( xml, 'text/xml' ); 35 | if ( $.isXMLDoc( xmlDoc ) ) { 36 | var err = $('parsererror', xmlDoc); 37 | if ( err.length == 1 ) { 38 | throw('Error: ' + $(xmlDoc).text() ); 39 | } 40 | } else { 41 | throw('Unable to parse XML'); 42 | } 43 | } catch( e ) { 44 | var msg = ( e.name == undefined ? e : e.name + ': ' + e.message ); 45 | $(document).trigger('xmlParseError', [ msg ]); 46 | return undefined; 47 | } 48 | return xmlDoc; 49 | } 50 | 51 | // Trigger a jQuery event 52 | function trigger(s, type, args) { 53 | (s.context ? jQuery(s.context) : jQuery.event).trigger(type, args); 54 | } 55 | 56 | // Check if the data field on the mock handler and the request match. This 57 | // can be used to restrict a mock handler to being used only when a certain 58 | // set of data is passed to it. 59 | function isMockDataEqual( mock, live ) { 60 | var identical = false; 61 | // Test for situations where the data is a querystring (not an object) 62 | if (typeof live === 'string') { 63 | // Querystring may be a regex 64 | return $.isFunction( mock.test ) ? mock.test(live) : mock == live; 65 | } 66 | $.each(mock, function(k, v) { 67 | if ( live[k] === undefined ) { 68 | identical = false; 69 | return identical; 70 | } else { 71 | identical = true; 72 | if ( typeof live[k] == 'object' ) { 73 | return isMockDataEqual(mock[k], live[k]); 74 | } else { 75 | if ( $.isFunction( mock[k].test ) ) { 76 | identical = mock[k].test(live[k]); 77 | } else { 78 | identical = ( mock[k] == live[k] ); 79 | } 80 | return identical; 81 | } 82 | } 83 | }); 84 | 85 | return identical; 86 | } 87 | 88 | // Check the given handler should mock the given request 89 | function getMockForRequest( handler, requestSettings ) { 90 | // If the mock was registered with a function, let the function decide if we 91 | // want to mock this request 92 | if ( $.isFunction(handler) ) { 93 | return handler( requestSettings ); 94 | } 95 | 96 | // Inspect the URL of the request and check if the mock handler's url 97 | // matches the url for this ajax request 98 | if ( $.isFunction(handler.url.test) ) { 99 | // The user provided a regex for the url, test it 100 | if ( !handler.url.test( requestSettings.url ) ) { 101 | return null; 102 | } 103 | } else { 104 | // Look for a simple wildcard '*' or a direct URL match 105 | var star = handler.url.indexOf('*'); 106 | if (handler.url !== requestSettings.url && star === -1 || 107 | !new RegExp(handler.url.replace(/[-[\]{}()+?.,\\^$|#\s]/g, "\\$&").replace('*', '.+')).test(requestSettings.url)) { 108 | return null; 109 | } 110 | } 111 | 112 | // Inspect the data submitted in the request (either POST body or GET query string) 113 | if ( handler.data && requestSettings.data ) { 114 | if ( !isMockDataEqual(handler.data, requestSettings.data) ) { 115 | // They're not identical, do not mock this request 116 | return null; 117 | } 118 | } 119 | // Inspect the request type 120 | if ( handler && handler.type && 121 | handler.type.toLowerCase() != requestSettings.type.toLowerCase() ) { 122 | // The request type doesn't match (GET vs. POST) 123 | return null; 124 | } 125 | 126 | return handler; 127 | } 128 | 129 | // If logging is enabled, log the mock to the console 130 | function logMock( mockHandler, requestSettings ) { 131 | var c = $.extend({}, $.mockjaxSettings, mockHandler); 132 | if ( c.log && $.isFunction(c.log) ) { 133 | c.log('MOCK ' + requestSettings.type.toUpperCase() + ': ' + requestSettings.url, $.extend({}, requestSettings)); 134 | } 135 | } 136 | 137 | // Process the xhr objects send operation 138 | function _xhrSend(mockHandler, requestSettings, origSettings) { 139 | 140 | // This is a substitute for < 1.4 which lacks $.proxy 141 | var process = (function(that) { 142 | return function() { 143 | return (function() { 144 | // The request has returned 145 | this.status = mockHandler.status; 146 | this.statusText = mockHandler.statusText; 147 | this.readyState = 4; 148 | 149 | // We have an executable function, call it to give 150 | // the mock handler a chance to update it's data 151 | if ( $.isFunction(mockHandler.response) ) { 152 | mockHandler.response(origSettings); 153 | } 154 | // Copy over our mock to our xhr object before passing control back to 155 | // jQuery's onreadystatechange callback 156 | if ( requestSettings.dataType == 'json' && ( typeof mockHandler.responseText == 'object' ) ) { 157 | this.responseText = JSON.stringify(mockHandler.responseText); 158 | } else if ( requestSettings.dataType == 'xml' ) { 159 | if ( typeof mockHandler.responseXML == 'string' ) { 160 | this.responseXML = parseXML(mockHandler.responseXML); 161 | } else { 162 | this.responseXML = mockHandler.responseXML; 163 | } 164 | } else { 165 | this.responseText = mockHandler.responseText; 166 | } 167 | if( typeof mockHandler.status == 'number' || typeof mockHandler.status == 'string' ) { 168 | this.status = mockHandler.status; 169 | } 170 | if( typeof mockHandler.statusText === "string") { 171 | this.statusText = mockHandler.statusText; 172 | } 173 | // jQuery < 1.4 doesn't have onreadystate change for xhr 174 | if ( $.isFunction(this.onreadystatechange) ) { 175 | if( mockHandler.isTimeout) { 176 | this.status = -1; 177 | } 178 | this.onreadystatechange( mockHandler.isTimeout ? 'timeout' : undefined ); 179 | } else if ( mockHandler.isTimeout ) { 180 | // Fix for 1.3.2 timeout to keep success from firing. 181 | this.status = -1; 182 | } 183 | }).apply(that); 184 | }; 185 | })(this); 186 | 187 | if ( mockHandler.proxy ) { 188 | // We're proxying this request and loading in an external file instead 189 | _ajax({ 190 | global: false, 191 | url: mockHandler.proxy, 192 | type: mockHandler.proxyType, 193 | data: mockHandler.data, 194 | dataType: requestSettings.dataType === "script" ? "text/plain" : requestSettings.dataType, 195 | complete: function(xhr, txt) { 196 | mockHandler.responseXML = xhr.responseXML; 197 | mockHandler.responseText = xhr.responseText; 198 | mockHandler.status = xhr.status; 199 | mockHandler.statusText = xhr.statusText; 200 | this.responseTimer = setTimeout(process, mockHandler.responseTime || 0); 201 | } 202 | }); 203 | } else { 204 | // type == 'POST' || 'GET' || 'DELETE' 205 | if ( requestSettings.async === false ) { 206 | // TODO: Blocking delay 207 | process(); 208 | } else { 209 | this.responseTimer = setTimeout(process, mockHandler.responseTime || 50); 210 | } 211 | } 212 | } 213 | 214 | // Construct a mocked XHR Object 215 | function xhr(mockHandler, requestSettings, origSettings, origHandler) { 216 | // Extend with our default mockjax settings 217 | mockHandler = $.extend({}, $.mockjaxSettings, mockHandler); 218 | 219 | if (typeof mockHandler.headers === 'undefined') { 220 | mockHandler.headers = {}; 221 | } 222 | if ( mockHandler.contentType ) { 223 | mockHandler.headers['content-type'] = mockHandler.contentType; 224 | } 225 | 226 | return { 227 | status: mockHandler.status, 228 | statusText: mockHandler.statusText, 229 | readyState: 1, 230 | open: function() { }, 231 | send: function() { 232 | origHandler.fired = true; 233 | _xhrSend.call(this, mockHandler, requestSettings, origSettings); 234 | }, 235 | abort: function() { 236 | clearTimeout(this.responseTimer); 237 | }, 238 | setRequestHeader: function(header, value) { 239 | mockHandler.headers[header] = value; 240 | }, 241 | getResponseHeader: function(header) { 242 | // 'Last-modified', 'Etag', 'content-type' are all checked by jQuery 243 | if ( mockHandler.headers && mockHandler.headers[header] ) { 244 | // Return arbitrary headers 245 | return mockHandler.headers[header]; 246 | } else if ( header.toLowerCase() == 'last-modified' ) { 247 | return mockHandler.lastModified || (new Date()).toString(); 248 | } else if ( header.toLowerCase() == 'etag' ) { 249 | return mockHandler.etag || ''; 250 | } else if ( header.toLowerCase() == 'content-type' ) { 251 | return mockHandler.contentType || 'text/plain'; 252 | } 253 | }, 254 | getAllResponseHeaders: function() { 255 | var headers = ''; 256 | $.each(mockHandler.headers, function(k, v) { 257 | headers += k + ': ' + v + "\n"; 258 | }); 259 | return headers; 260 | } 261 | }; 262 | } 263 | 264 | // Process a JSONP mock request. 265 | function processJsonpMock( requestSettings, mockHandler, origSettings ) { 266 | // Handle JSONP Parameter Callbacks, we need to replicate some of the jQuery core here 267 | // because there isn't an easy hook for the cross domain script tag of jsonp 268 | 269 | processJsonpUrl( requestSettings ); 270 | 271 | requestSettings.dataType = "json"; 272 | if(requestSettings.data && CALLBACK_REGEX.test(requestSettings.data) || CALLBACK_REGEX.test(requestSettings.url)) { 273 | createJsonpCallback(requestSettings, mockHandler); 274 | 275 | // We need to make sure 276 | // that a JSONP style response is executed properly 277 | 278 | var rurl = /^(\w+:)?\/\/([^\/?#]+)/, 279 | parts = rurl.exec( requestSettings.url ), 280 | remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host); 281 | 282 | requestSettings.dataType = "script"; 283 | if(requestSettings.type.toUpperCase() === "GET" && remote ) { 284 | var newMockReturn = processJsonpRequest( requestSettings, mockHandler, origSettings ); 285 | 286 | // Check if we are supposed to return a Deferred back to the mock call, or just 287 | // signal success 288 | if(newMockReturn) { 289 | return newMockReturn; 290 | } else { 291 | return true; 292 | } 293 | } 294 | } 295 | return null; 296 | } 297 | 298 | // Append the required callback parameter to the end of the request URL, for a JSONP request 299 | function processJsonpUrl( requestSettings ) { 300 | if ( requestSettings.type.toUpperCase() === "GET" ) { 301 | if ( !CALLBACK_REGEX.test( requestSettings.url ) ) { 302 | requestSettings.url += (/\?/.test( requestSettings.url ) ? "&" : "?") + 303 | (requestSettings.jsonp || "callback") + "=?"; 304 | } 305 | } else if ( !requestSettings.data || !CALLBACK_REGEX.test(requestSettings.data) ) { 306 | requestSettings.data = (requestSettings.data ? requestSettings.data + "&" : "") + (requestSettings.jsonp || "callback") + "=?"; 307 | } 308 | } 309 | 310 | // Process a JSONP request by evaluating the mocked response text 311 | function processJsonpRequest( requestSettings, mockHandler, origSettings ) { 312 | // Synthesize the mock request for adding a script tag 313 | var callbackContext = origSettings && origSettings.context || requestSettings, 314 | newMock = null; 315 | 316 | 317 | // If the response handler on the moock is a function, call it 318 | if ( mockHandler.response && $.isFunction(mockHandler.response) ) { 319 | mockHandler.response(origSettings); 320 | } else { 321 | 322 | // Evaluate the responseText javascript in a global context 323 | if( typeof mockHandler.responseText === 'object' ) { 324 | $.globalEval( '(' + JSON.stringify( mockHandler.responseText ) + ')'); 325 | } else { 326 | $.globalEval( '(' + mockHandler.responseText + ')'); 327 | } 328 | } 329 | 330 | // Successful response 331 | jsonpSuccess( requestSettings, mockHandler ); 332 | jsonpComplete( requestSettings, mockHandler ); 333 | 334 | // If we are running under jQuery 1.5+, return a deferred object 335 | if(jQuery.Deferred){ 336 | newMock = new jQuery.Deferred(); 337 | if(typeof mockHandler.responseText == "object"){ 338 | newMock.resolveWith( callbackContext, [mockHandler.responseText] ); 339 | } 340 | else{ 341 | newMock.resolveWith( callbackContext, [jQuery.parseJSON( mockHandler.responseText )] ); 342 | } 343 | } 344 | return newMock; 345 | } 346 | 347 | 348 | // Create the required JSONP callback function for the request 349 | function createJsonpCallback( requestSettings, mockHandler ) { 350 | jsonp = requestSettings.jsonpCallback || ("jsonp" + jsc++); 351 | 352 | // Replace the =? sequence both in the query string and the data 353 | if ( requestSettings.data ) { 354 | requestSettings.data = (requestSettings.data + "").replace(CALLBACK_REGEX, "=" + jsonp + "$1"); 355 | } 356 | 357 | requestSettings.url = requestSettings.url.replace(CALLBACK_REGEX, "=" + jsonp + "$1"); 358 | 359 | 360 | // Handle JSONP-style loading 361 | window[ jsonp ] = window[ jsonp ] || function( tmp ) { 362 | data = tmp; 363 | jsonpSuccess( requestSettings, mockHandler ); 364 | jsonpComplete( requestSettings, mockHandler ); 365 | // Garbage collect 366 | window[ jsonp ] = undefined; 367 | 368 | try { 369 | delete window[ jsonp ]; 370 | } catch(e) {} 371 | 372 | if ( head ) { 373 | head.removeChild( script ); 374 | } 375 | }; 376 | } 377 | 378 | // The JSONP request was successful 379 | function jsonpSuccess(requestSettings, mockHandler) { 380 | // If a local callback was specified, fire it and pass it the data 381 | if ( requestSettings.success ) { 382 | requestSettings.success.call( callbackContext, ( mockHandler.response ? mockHandler.response.toString() : mockHandler.responseText || ''), status, {} ); 383 | } 384 | 385 | // Fire the global callback 386 | if ( requestSettings.global ) { 387 | trigger(requestSettings, "ajaxSuccess", [{}, requestSettings] ); 388 | } 389 | } 390 | 391 | // The JSONP request was completed 392 | function jsonpComplete(requestSettings, mockHandler) { 393 | // Process result 394 | if ( requestSettings.complete ) { 395 | requestSettings.complete.call( callbackContext, {} , status ); 396 | } 397 | 398 | // The request was completed 399 | if ( requestSettings.global ) { 400 | trigger( "ajaxComplete", [{}, requestSettings] ); 401 | } 402 | 403 | // Handle the global AJAX counter 404 | if ( requestSettings.global && ! --jQuery.active ) { 405 | jQuery.event.trigger( "ajaxStop" ); 406 | } 407 | } 408 | 409 | 410 | // The core $.ajax replacement. 411 | function handleAjax( url, origSettings ) { 412 | var mockRequest, requestSettings, mockHandler; 413 | 414 | // If url is an object, simulate pre-1.5 signature 415 | if ( typeof url === "object" ) { 416 | origSettings = url; 417 | url = undefined; 418 | } else { 419 | // work around to support 1.5 signature 420 | origSettings.url = url; 421 | } 422 | 423 | // Extend the original settings for the request 424 | requestSettings = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings); 425 | 426 | // Iterate over our mock handlers (in registration order) until we find 427 | // one that is willing to intercept the request 428 | for(var k = 0; k < mockHandlers.length; k++) { 429 | if ( !mockHandlers[k] ) { 430 | continue; 431 | } 432 | 433 | mockHandler = getMockForRequest( mockHandlers[k], requestSettings ); 434 | if(!mockHandler) { 435 | // No valid mock found for this request 436 | continue; 437 | } 438 | 439 | // Handle console logging 440 | logMock( mockHandler, requestSettings ); 441 | 442 | 443 | if ( requestSettings.dataType === "jsonp" ) { 444 | if ((mockRequest = processJsonpMock( requestSettings, mockHandler, origSettings ))) { 445 | // This mock will handle the JSONP request 446 | return mockRequest; 447 | } 448 | } 449 | 450 | 451 | // Removed to fix #54 - keep the mocking data object intact 452 | //mockHandler.data = requestSettings.data; 453 | 454 | mockHandler.cache = requestSettings.cache; 455 | mockHandler.timeout = requestSettings.timeout; 456 | mockHandler.global = requestSettings.global; 457 | 458 | (function(mockHandler, requestSettings, origSettings, origHandler) { 459 | mockRequest = _ajax.call($, $.extend(true, {}, origSettings, { 460 | // Mock the XHR object 461 | xhr: function() { return xhr( mockHandler, requestSettings, origSettings, origHandler ) } 462 | })); 463 | })(mockHandler, requestSettings, origSettings, mockHandlers[k]); 464 | 465 | return mockRequest; 466 | } 467 | 468 | // We don't have a mock request, trigger a normal request 469 | return _ajax.apply($, [origSettings]); 470 | } 471 | 472 | 473 | // Public 474 | 475 | $.extend({ 476 | ajax: handleAjax 477 | }); 478 | 479 | $.mockjaxSettings = { 480 | //url: null, 481 | //type: 'GET', 482 | log: function(msg) { 483 | window['console'] && window.console.log && window.console.log(msg); 484 | }, 485 | status: 200, 486 | statusText: "OK", 487 | responseTime: 500, 488 | isTimeout: false, 489 | contentType: 'text/plain', 490 | response: '', 491 | responseText: '', 492 | responseXML: '', 493 | proxy: '', 494 | proxyType: 'GET', 495 | 496 | lastModified: null, 497 | etag: '', 498 | headers: { 499 | etag: 'IJF@H#@923uf8023hFO@I#H#', 500 | 'content-type' : 'text/plain' 501 | } 502 | }; 503 | 504 | $.mockjax = function(settings) { 505 | var i = mockHandlers.length; 506 | mockHandlers[i] = settings; 507 | return i; 508 | }; 509 | $.mockjaxClear = function(i) { 510 | if ( arguments.length == 1 ) { 511 | mockHandlers[i] = null; 512 | } else { 513 | mockHandlers = []; 514 | } 515 | }; 516 | $.mockjax.handler = function(i) { 517 | if ( arguments.length == 1 ) { 518 | return mockHandlers[i]; 519 | } 520 | }; 521 | })(jQuery); 522 | 523 | -------------------------------------------------------------------------------- /lib/Bacon.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var Bacon, Bus, Dispatcher, End, Error, Event, EventStream, Initial, Next, None, Observable, Property, PropertyDispatcher, Some, assert, assertArray, assertEvent, assertFunction, assertString, cloneArray, cloneObject, end, former, initial, isEvent, isFieldKey, isFunction, latter, makeFunction, methodCall, next, nop, partiallyApplied, propertyThenStream, remove, sendWrapped, toCombinator, toEvent, toFieldExtractor, toFieldKey, toOption, toSimpleExtractor, _, _ref, 3 | __slice = Array.prototype.slice, 4 | __hasProp = Object.prototype.hasOwnProperty, 5 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }, 6 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 7 | 8 | if ((_ref = this.jQuery || this.Zepto) != null) { 9 | _ref.fn.asEventStream = function(eventName, selector, eventTransformer) { 10 | var element; 11 | if (eventTransformer == null) eventTransformer = _.id; 12 | if (isFunction(selector)) { 13 | eventTransformer = selector; 14 | selector = null; 15 | } 16 | element = this; 17 | return new EventStream(function(sink) { 18 | var handler, unbind; 19 | handler = function() { 20 | var args, reply; 21 | args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 22 | reply = sink(next(eventTransformer.apply(null, args))); 23 | if (reply === Bacon.noMore) return unbind(); 24 | }; 25 | unbind = function() { 26 | return element.off(eventName, selector, handler); 27 | }; 28 | element.on(eventName, selector, handler); 29 | return unbind; 30 | }); 31 | }; 32 | } 33 | 34 | Bacon = this.Bacon = {}; 35 | 36 | Bacon.fromPromise = function(promise) { 37 | return new Bacon.EventStream(function(sink) { 38 | var onError, onSuccess; 39 | onSuccess = function(value) { 40 | sink(new Next(value)); 41 | return sink(new End); 42 | }; 43 | onError = function(e) { 44 | sink(new Error(e)); 45 | return sink(new End); 46 | }; 47 | promise.then(onSuccess, onError); 48 | return nop; 49 | }); 50 | }; 51 | 52 | Bacon.noMore = "veggies"; 53 | 54 | Bacon.more = "moar bacon!"; 55 | 56 | Bacon.later = function(delay, value) { 57 | return Bacon.sequentially(delay, [value]); 58 | }; 59 | 60 | Bacon.sequentially = function(delay, values) { 61 | var index, poll; 62 | index = -1; 63 | poll = function() { 64 | index++; 65 | if (index < values.length) { 66 | return toEvent(values[index]); 67 | } else { 68 | return end(); 69 | } 70 | }; 71 | return Bacon.fromPoll(delay, poll); 72 | }; 73 | 74 | Bacon.repeatedly = function(delay, values) { 75 | var index, poll; 76 | index = -1; 77 | poll = function() { 78 | index++; 79 | return toEvent(values[index % values.length]); 80 | }; 81 | return Bacon.fromPoll(delay, poll); 82 | }; 83 | 84 | Bacon.fromCallback = function() { 85 | var args, f; 86 | f = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 87 | f = makeFunction(f, args); 88 | return new EventStream(function(sink) { 89 | var handler; 90 | handler = function(value) { 91 | sink(next(value)); 92 | return sink(end()); 93 | }; 94 | f(handler); 95 | return nop; 96 | }); 97 | }; 98 | 99 | Bacon.fromPoll = function(delay, poll) { 100 | return new EventStream(function(sink) { 101 | var handler, id, unbind; 102 | id = void 0; 103 | handler = function() { 104 | var reply, value; 105 | value = poll(); 106 | reply = sink(value); 107 | if (reply === Bacon.noMore || value.isEnd()) return unbind(); 108 | }; 109 | unbind = function() { 110 | return clearInterval(id); 111 | }; 112 | id = setInterval(handler, delay); 113 | return unbind; 114 | }); 115 | }; 116 | 117 | Bacon.fromEventTarget = function(target, eventName) { 118 | return new EventStream(function(sink) { 119 | var handler, unbind; 120 | handler = function(event) { 121 | var reply; 122 | reply = sink(next(event)); 123 | if (reply === Bacon.noMore) return unbind(); 124 | }; 125 | if (target.addEventListener) { 126 | unbind = function() { 127 | return target.removeEventListener(eventName, handler, false); 128 | }; 129 | target.addEventListener(eventName, handler, false); 130 | } else { 131 | unbind = function() { 132 | return target.removeListener(eventName, handler); 133 | }; 134 | target.addListener(eventName, handler); 135 | } 136 | return unbind; 137 | }); 138 | }; 139 | 140 | Bacon.interval = function(delay, value) { 141 | var poll; 142 | if (value == null) value = {}; 143 | poll = function() { 144 | return next(value); 145 | }; 146 | return Bacon.fromPoll(delay, poll); 147 | }; 148 | 149 | Bacon.constant = function(value) { 150 | return new Property(sendWrapped([value], initial)); 151 | }; 152 | 153 | Bacon.never = function() { 154 | return Bacon.fromArray([]); 155 | }; 156 | 157 | Bacon.once = function(value) { 158 | return Bacon.fromArray([value]); 159 | }; 160 | 161 | Bacon.fromArray = function(values) { 162 | return new EventStream(sendWrapped(values, next)); 163 | }; 164 | 165 | sendWrapped = function(values, wrapper) { 166 | return function(sink) { 167 | var value, _i, _len; 168 | for (_i = 0, _len = values.length; _i < _len; _i++) { 169 | value = values[_i]; 170 | sink(wrapper(value)); 171 | } 172 | sink(end()); 173 | return nop; 174 | }; 175 | }; 176 | 177 | Bacon.combineAll = function(streams, f) { 178 | var next, stream, _i, _len, _ref2; 179 | assertArray(streams); 180 | stream = _.head(streams); 181 | _ref2 = _.tail(streams); 182 | for (_i = 0, _len = _ref2.length; _i < _len; _i++) { 183 | next = _ref2[_i]; 184 | stream = f(stream, next); 185 | } 186 | return stream; 187 | }; 188 | 189 | Bacon.mergeAll = function(streams) { 190 | return Bacon.combineAll(streams, function(s1, s2) { 191 | return s1.merge(s2); 192 | }); 193 | }; 194 | 195 | Bacon.combineAsArray = function() { 196 | var more, next, stream, streams, _i, _len, _ref2; 197 | streams = arguments[0], more = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 198 | if (!(streams instanceof Array)) streams = [streams].concat(more); 199 | if (streams.length) { 200 | stream = (_.head(streams)).toProperty().map(function(x) { 201 | return [x]; 202 | }); 203 | _ref2 = _.tail(streams); 204 | for (_i = 0, _len = _ref2.length; _i < _len; _i++) { 205 | next = _ref2[_i]; 206 | stream = stream.combine(next, function(xs, x) { 207 | return xs.concat([x]); 208 | }); 209 | } 210 | return stream; 211 | } else { 212 | return Bacon.constant([]); 213 | } 214 | }; 215 | 216 | Bacon.combineWith = function(streams, f) { 217 | return Bacon.combineAll(streams, function(s1, s2) { 218 | return s1.toProperty().combine(s2, f); 219 | }); 220 | }; 221 | 222 | Bacon.combineTemplate = function(template) { 223 | var applyStreamValue, combinator, compileTemplate, constantValue, current, funcs, setValue, streams; 224 | funcs = []; 225 | streams = []; 226 | current = function(ctxStack) { 227 | return ctxStack[ctxStack.length - 1]; 228 | }; 229 | setValue = function(ctxStack, key, value) { 230 | return current(ctxStack)[key] = value; 231 | }; 232 | applyStreamValue = function(key, index) { 233 | return function(ctxStack, values) { 234 | return setValue(ctxStack, key, values[index]); 235 | }; 236 | }; 237 | constantValue = function(key, value) { 238 | return function(ctxStack, values) { 239 | return setValue(ctxStack, key, value); 240 | }; 241 | }; 242 | compileTemplate = function(template) { 243 | var key, popContext, pushContext, value, _results; 244 | _results = []; 245 | for (key in template) { 246 | value = template[key]; 247 | if (value instanceof Observable) { 248 | streams.push(value); 249 | _results.push(funcs.push(applyStreamValue(key, streams.length - 1))); 250 | } else if (typeof value === "object") { 251 | pushContext = function(key) { 252 | return function(ctxStack, values) { 253 | var newContext; 254 | newContext = {}; 255 | setValue(ctxStack, key, newContext); 256 | return ctxStack.push(newContext); 257 | }; 258 | }; 259 | popContext = function(ctxStack, values) { 260 | return ctxStack.pop(); 261 | }; 262 | funcs.push(pushContext(key)); 263 | compileTemplate(value); 264 | _results.push(funcs.push(popContext)); 265 | } else { 266 | _results.push(funcs.push(constantValue(key, value))); 267 | } 268 | } 269 | return _results; 270 | }; 271 | compileTemplate(template); 272 | combinator = function(values) { 273 | var ctxStack, f, rootContext, _i, _len; 274 | rootContext = {}; 275 | ctxStack = [rootContext]; 276 | for (_i = 0, _len = funcs.length; _i < _len; _i++) { 277 | f = funcs[_i]; 278 | f(ctxStack, values); 279 | } 280 | return rootContext; 281 | }; 282 | return Bacon.combineAsArray(streams).map(combinator); 283 | }; 284 | 285 | Bacon.latestValue = function(src) { 286 | var latest, 287 | _this = this; 288 | latest = void 0; 289 | src.subscribe(function(event) { 290 | if (event.hasValue()) return latest = event.value; 291 | }); 292 | return function() { 293 | return latest; 294 | }; 295 | }; 296 | 297 | Event = (function() { 298 | 299 | function Event() {} 300 | 301 | Event.prototype.isEvent = function() { 302 | return true; 303 | }; 304 | 305 | Event.prototype.isEnd = function() { 306 | return false; 307 | }; 308 | 309 | Event.prototype.isInitial = function() { 310 | return false; 311 | }; 312 | 313 | Event.prototype.isNext = function() { 314 | return false; 315 | }; 316 | 317 | Event.prototype.isError = function() { 318 | return false; 319 | }; 320 | 321 | Event.prototype.hasValue = function() { 322 | return false; 323 | }; 324 | 325 | Event.prototype.filter = function(f) { 326 | return true; 327 | }; 328 | 329 | Event.prototype.getOriginalEvent = function() { 330 | if (this.sourceEvent != null) { 331 | return this.sourceEvent.getOriginalEvent(); 332 | } else { 333 | return this; 334 | } 335 | }; 336 | 337 | Event.prototype.onDone = function(listener) { 338 | return listener(); 339 | }; 340 | 341 | return Event; 342 | 343 | })(); 344 | 345 | Next = (function(_super) { 346 | 347 | __extends(Next, _super); 348 | 349 | function Next(value, sourceEvent) { 350 | this.value = value; 351 | } 352 | 353 | Next.prototype.isNext = function() { 354 | return true; 355 | }; 356 | 357 | Next.prototype.hasValue = function() { 358 | return true; 359 | }; 360 | 361 | Next.prototype.fmap = function(f) { 362 | return this.apply(f(this.value)); 363 | }; 364 | 365 | Next.prototype.apply = function(value) { 366 | return next(value, this.getOriginalEvent()); 367 | }; 368 | 369 | Next.prototype.filter = function(f) { 370 | return f(this.value); 371 | }; 372 | 373 | Next.prototype.describe = function() { 374 | return this.value; 375 | }; 376 | 377 | return Next; 378 | 379 | })(Event); 380 | 381 | Initial = (function(_super) { 382 | 383 | __extends(Initial, _super); 384 | 385 | function Initial() { 386 | Initial.__super__.constructor.apply(this, arguments); 387 | } 388 | 389 | Initial.prototype.isInitial = function() { 390 | return true; 391 | }; 392 | 393 | Initial.prototype.isNext = function() { 394 | return false; 395 | }; 396 | 397 | Initial.prototype.apply = function(value) { 398 | return initial(value, this.getOriginalEvent()); 399 | }; 400 | 401 | return Initial; 402 | 403 | })(Next); 404 | 405 | End = (function(_super) { 406 | 407 | __extends(End, _super); 408 | 409 | function End() { 410 | End.__super__.constructor.apply(this, arguments); 411 | } 412 | 413 | End.prototype.isEnd = function() { 414 | return true; 415 | }; 416 | 417 | End.prototype.fmap = function() { 418 | return this; 419 | }; 420 | 421 | End.prototype.apply = function() { 422 | return this; 423 | }; 424 | 425 | End.prototype.describe = function() { 426 | return ""; 427 | }; 428 | 429 | return End; 430 | 431 | })(Event); 432 | 433 | Error = (function(_super) { 434 | 435 | __extends(Error, _super); 436 | 437 | function Error(error) { 438 | this.error = error; 439 | } 440 | 441 | Error.prototype.isError = function() { 442 | return true; 443 | }; 444 | 445 | Error.prototype.fmap = function() { 446 | return this; 447 | }; 448 | 449 | Error.prototype.apply = function() { 450 | return this; 451 | }; 452 | 453 | Error.prototype.describe = function() { 454 | return " " + this.error; 455 | }; 456 | 457 | return Error; 458 | 459 | })(Event); 460 | 461 | Observable = (function() { 462 | 463 | function Observable() { 464 | this.flatMapLatest = __bind(this.flatMapLatest, this); 465 | this["switch"] = __bind(this["switch"], this); 466 | this.takeUntil = __bind(this.takeUntil, this); this.assign = this.onValue; 467 | } 468 | 469 | Observable.prototype.onValue = function() { 470 | var args, f; 471 | f = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 472 | f = makeFunction(f, args); 473 | return this.subscribe(function(event) { 474 | if (event.hasValue()) return f(event.value); 475 | }); 476 | }; 477 | 478 | Observable.prototype.onValues = function(f) { 479 | return this.onValue(function(args) { 480 | return f.apply(null, args); 481 | }); 482 | }; 483 | 484 | Observable.prototype.onError = function() { 485 | var args, f; 486 | f = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 487 | f = makeFunction(f, args); 488 | return this.subscribe(function(event) { 489 | if (event.isError()) return f(event.error); 490 | }); 491 | }; 492 | 493 | Observable.prototype.onEnd = function() { 494 | var args, f; 495 | f = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 496 | f = makeFunction(f, args); 497 | return this.subscribe(function(event) { 498 | if (event.isEnd()) return f(); 499 | }); 500 | }; 501 | 502 | Observable.prototype.errors = function() { 503 | return this.filter(function() { 504 | return false; 505 | }); 506 | }; 507 | 508 | Observable.prototype.filter = function() { 509 | var args, f; 510 | f = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 511 | f = makeFunction(f, args); 512 | return this.withHandler(function(event) { 513 | if (event.filter(f)) { 514 | return this.push(event); 515 | } else { 516 | return Bacon.more; 517 | } 518 | }); 519 | }; 520 | 521 | Observable.prototype.takeWhile = function() { 522 | var args, f; 523 | f = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 524 | f = makeFunction(f, args); 525 | return this.withHandler(function(event) { 526 | if (event.filter(f)) { 527 | return this.push(event); 528 | } else { 529 | this.push(end()); 530 | return Bacon.noMore; 531 | } 532 | }); 533 | }; 534 | 535 | Observable.prototype.endOnError = function() { 536 | return this.withHandler(function(event) { 537 | if (event.isError()) { 538 | this.push(event); 539 | return this.push(end()); 540 | } else { 541 | return this.push(event); 542 | } 543 | }); 544 | }; 545 | 546 | Observable.prototype.take = function(count) { 547 | assert("take: count must >= 1", count >= 1); 548 | return this.withHandler(function(event) { 549 | if (!event.hasValue()) { 550 | return this.push(event); 551 | } else if (count === 1) { 552 | this.push(event); 553 | this.push(end()); 554 | return Bacon.noMore; 555 | } else { 556 | count--; 557 | return this.push(event); 558 | } 559 | }); 560 | }; 561 | 562 | Observable.prototype.map = function() { 563 | var args, f; 564 | f = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 565 | f = makeFunction(f, args); 566 | return this.withHandler(function(event) { 567 | return this.push(event.fmap(f)); 568 | }); 569 | }; 570 | 571 | Observable.prototype.mapError = function() { 572 | var args, f; 573 | f = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 574 | f = makeFunction(f, args); 575 | return this.withHandler(function(event) { 576 | if (event.isError()) { 577 | return this.push(next(f(event.error))); 578 | } else { 579 | return this.push(event); 580 | } 581 | }); 582 | }; 583 | 584 | Observable.prototype["do"] = function() { 585 | var args; 586 | args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 587 | return this.doAction.apply(this, args); 588 | }; 589 | 590 | Observable.prototype.doAction = function() { 591 | var args, f; 592 | f = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 593 | f = makeFunction(f, args); 594 | return this.withHandler(function(event) { 595 | if (event.hasValue()) f(event.value); 596 | return this.push(event); 597 | }); 598 | }; 599 | 600 | Observable.prototype.takeUntil = function(stopper) { 601 | var src; 602 | src = this; 603 | return this.withSubscribe(function(sink) { 604 | var srcSink, stopperSink, unsubBoth, unsubSrc, unsubStopper, unsubscribed; 605 | unsubscribed = false; 606 | unsubSrc = nop; 607 | unsubStopper = nop; 608 | unsubBoth = function() { 609 | unsubSrc(); 610 | unsubStopper(); 611 | return unsubscribed = true; 612 | }; 613 | srcSink = function(event) { 614 | if (event.isEnd()) { 615 | unsubStopper(); 616 | sink(event); 617 | return Bacon.noMore; 618 | } else { 619 | event.getOriginalEvent().onDone(function() { 620 | var reply; 621 | if (!unsubscribed) { 622 | reply = sink(event); 623 | if (reply === Bacon.noMore) return unsubBoth(); 624 | } 625 | }); 626 | return Bacon.more; 627 | } 628 | }; 629 | stopperSink = function(event) { 630 | if (event.isError()) { 631 | return Bacon.more; 632 | } else if (event.isEnd()) { 633 | return Bacon.noMore; 634 | } else { 635 | unsubSrc(); 636 | sink(end()); 637 | return Bacon.noMore; 638 | } 639 | }; 640 | unsubSrc = src.subscribe(srcSink); 641 | if (!unsubscribed) unsubStopper = stopper.subscribe(stopperSink); 642 | return unsubBoth; 643 | }); 644 | }; 645 | 646 | Observable.prototype.skip = function(count) { 647 | assert("skip: count must >= 0", count >= 0); 648 | return this.withHandler(function(event) { 649 | if (!event.hasValue()) { 650 | return this.push(event); 651 | } else if (count > 0) { 652 | count--; 653 | return Bacon.more; 654 | } else { 655 | return this.push(event); 656 | } 657 | }); 658 | }; 659 | 660 | Observable.prototype.distinctUntilChanged = function() { 661 | return this.skipDuplicates(); 662 | }; 663 | 664 | Observable.prototype.skipDuplicates = function() { 665 | return this.withStateMachine(void 0, function(prev, event) { 666 | if (!event.hasValue()) { 667 | return [prev, [event]]; 668 | } else if (prev !== event.value) { 669 | return [event.value, [event]]; 670 | } else { 671 | return [prev, []]; 672 | } 673 | }); 674 | }; 675 | 676 | Observable.prototype.withStateMachine = function(initState, f) { 677 | var state; 678 | state = initState; 679 | return this.withHandler(function(event) { 680 | var fromF, newState, output, outputs, reply, _i, _len; 681 | fromF = f(state, event); 682 | assertArray(fromF); 683 | newState = fromF[0], outputs = fromF[1]; 684 | assertArray(outputs); 685 | state = newState; 686 | reply = Bacon.more; 687 | for (_i = 0, _len = outputs.length; _i < _len; _i++) { 688 | output = outputs[_i]; 689 | reply = this.push(output); 690 | if (reply === Bacon.noMore) return reply; 691 | } 692 | return reply; 693 | }); 694 | }; 695 | 696 | Observable.prototype.flatMap = function(f) { 697 | var root; 698 | root = this; 699 | return new EventStream(function(sink) { 700 | var checkEnd, children, rootEnd, spawner, unbind, unsubRoot; 701 | children = []; 702 | rootEnd = false; 703 | unsubRoot = function() {}; 704 | unbind = function() { 705 | var unsubChild, _i, _len; 706 | unsubRoot(); 707 | for (_i = 0, _len = children.length; _i < _len; _i++) { 708 | unsubChild = children[_i]; 709 | unsubChild(); 710 | } 711 | return children = []; 712 | }; 713 | checkEnd = function() { 714 | if (rootEnd && (children.length === 0)) return sink(end()); 715 | }; 716 | spawner = function(event) { 717 | var child, childEnded, handler, removeChild, unsubChild; 718 | if (event.isEnd()) { 719 | rootEnd = true; 720 | return checkEnd(); 721 | } else if (event.isError()) { 722 | return sink(event); 723 | } else { 724 | child = f(event.value); 725 | unsubChild = void 0; 726 | childEnded = false; 727 | removeChild = function() { 728 | if (unsubChild != null) remove(unsubChild, children); 729 | return checkEnd(); 730 | }; 731 | handler = function(event) { 732 | var reply; 733 | if (event.isEnd()) { 734 | removeChild(); 735 | childEnded = true; 736 | return Bacon.noMore; 737 | } else { 738 | reply = sink(event); 739 | if (reply === Bacon.noMore) unbind(); 740 | return reply; 741 | } 742 | }; 743 | unsubChild = child.subscribe(handler); 744 | if (!childEnded) return children.push(unsubChild); 745 | } 746 | }; 747 | unsubRoot = root.subscribe(spawner); 748 | return unbind; 749 | }); 750 | }; 751 | 752 | Observable.prototype["switch"] = function() { 753 | var args; 754 | args = 1 <= arguments.length ? __slice.call(arguments, 0) : []; 755 | return this.flatMapLatest.apply(this, args); 756 | }; 757 | 758 | Observable.prototype.flatMapLatest = function(f) { 759 | var stream, 760 | _this = this; 761 | stream = this.toEventStream(); 762 | return stream.flatMap(function(value) { 763 | return f(value).takeUntil(stream); 764 | }); 765 | }; 766 | 767 | Observable.prototype.not = function() { 768 | return this.map(function(x) { 769 | return !x; 770 | }); 771 | }; 772 | 773 | Observable.prototype.log = function() { 774 | this.subscribe(function(event) { 775 | return console.log(event.describe()); 776 | }); 777 | return this; 778 | }; 779 | 780 | return Observable; 781 | 782 | })(); 783 | 784 | EventStream = (function(_super) { 785 | 786 | __extends(EventStream, _super); 787 | 788 | function EventStream(subscribe) { 789 | var dispatcher; 790 | EventStream.__super__.constructor.call(this); 791 | assertFunction(subscribe); 792 | dispatcher = new Dispatcher(subscribe); 793 | this.subscribe = dispatcher.subscribe; 794 | this.hasSubscribers = dispatcher.hasSubscribers; 795 | } 796 | 797 | EventStream.prototype.map = function() { 798 | var args, p; 799 | p = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 800 | if (p instanceof Property) { 801 | return p.sampledBy(this, former); 802 | } else { 803 | return EventStream.__super__.map.apply(this, [p].concat(__slice.call(args))); 804 | } 805 | }; 806 | 807 | EventStream.prototype.filter = function() { 808 | var args, p; 809 | p = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 810 | if (p instanceof Property) { 811 | return p.sampledBy(this, function(p, s) { 812 | return [p, s]; 813 | }).filter(function(_arg) { 814 | var p, s; 815 | p = _arg[0], s = _arg[1]; 816 | return p; 817 | }).map(function(_arg) { 818 | var p, s; 819 | p = _arg[0], s = _arg[1]; 820 | return s; 821 | }); 822 | } else { 823 | return EventStream.__super__.filter.apply(this, [p].concat(__slice.call(args))); 824 | } 825 | }; 826 | 827 | EventStream.prototype.delay = function(delay) { 828 | return this.flatMap(function(value) { 829 | return Bacon.later(delay, value); 830 | }); 831 | }; 832 | 833 | EventStream.prototype.throttle = function(delay) { 834 | return this.flatMapLatest(function(value) { 835 | return Bacon.later(delay, value); 836 | }); 837 | }; 838 | 839 | EventStream.prototype.bufferWithTime = function(delay) { 840 | var buffer, flush, storeAndMaybeTrigger, values; 841 | values = []; 842 | storeAndMaybeTrigger = function(value) { 843 | values.push(value); 844 | return values.length === 1; 845 | }; 846 | flush = function() { 847 | var output; 848 | output = values; 849 | values = []; 850 | return output; 851 | }; 852 | buffer = function() { 853 | return Bacon.later(delay).map(flush); 854 | }; 855 | return this.filter(storeAndMaybeTrigger).flatMap(buffer); 856 | }; 857 | 858 | EventStream.prototype.bufferWithCount = function(count) { 859 | var values; 860 | values = []; 861 | return this.withHandler(function(event) { 862 | var flush, 863 | _this = this; 864 | flush = function() { 865 | _this.push(next(values, event)); 866 | return values = []; 867 | }; 868 | if (event.isError()) { 869 | return this.push(event); 870 | } else if (event.isEnd()) { 871 | flush(); 872 | return this.push(event); 873 | } else { 874 | values.push(event.value); 875 | if (values.length === count) return flush(); 876 | } 877 | }); 878 | }; 879 | 880 | EventStream.prototype.merge = function(right) { 881 | var left; 882 | left = this; 883 | return new EventStream(function(sink) { 884 | var ends, smartSink, unsubBoth, unsubLeft, unsubRight, unsubscribed; 885 | unsubLeft = nop; 886 | unsubRight = nop; 887 | unsubscribed = false; 888 | unsubBoth = function() { 889 | unsubLeft(); 890 | unsubRight(); 891 | return unsubscribed = true; 892 | }; 893 | ends = 0; 894 | smartSink = function(event) { 895 | var reply; 896 | if (event.isEnd()) { 897 | ends++; 898 | if (ends === 2) { 899 | return sink(end()); 900 | } else { 901 | return Bacon.more; 902 | } 903 | } else { 904 | reply = sink(event); 905 | if (reply === Bacon.noMore) unsubBoth(); 906 | return reply; 907 | } 908 | }; 909 | unsubLeft = left.subscribe(smartSink); 910 | if (!unsubscribed) unsubRight = right.subscribe(smartSink); 911 | return unsubBoth; 912 | }); 913 | }; 914 | 915 | EventStream.prototype.toProperty = function(initValue) { 916 | if (arguments.length === 0) initValue = None; 917 | return this.scan(initValue, latter); 918 | }; 919 | 920 | EventStream.prototype.toEventStream = function() { 921 | return this; 922 | }; 923 | 924 | EventStream.prototype.scan = function(seed, f) { 925 | var acc, d, handleEvent, subscribe; 926 | acc = toOption(seed); 927 | f = toCombinator(f); 928 | handleEvent = function(event) { 929 | if (event.hasValue()) { 930 | acc = new Some(f(acc.getOrElse(void 0), event.value)); 931 | } 932 | return this.push(event.apply(acc.getOrElse(void 0))); 933 | }; 934 | d = new Dispatcher(this.subscribe, handleEvent); 935 | subscribe = function(sink) { 936 | var reply; 937 | reply = acc.map(function(val) { 938 | return sink(initial(val)); 939 | }).getOrElse(Bacon.more); 940 | if (reply !== Bacon.noMore) { 941 | return d.subscribe(sink); 942 | } else { 943 | return nop; 944 | } 945 | }; 946 | return new Property(subscribe); 947 | }; 948 | 949 | EventStream.prototype.concat = function(right) { 950 | var left; 951 | left = this; 952 | return new EventStream(function(sink) { 953 | var unsub; 954 | unsub = left.subscribe(function(e) { 955 | if (e.isEnd()) { 956 | return unsub = right.subscribe(sink); 957 | } else { 958 | return sink(e); 959 | } 960 | }); 961 | return function() { 962 | return unsub(); 963 | }; 964 | }); 965 | }; 966 | 967 | EventStream.prototype.startWith = function(seed) { 968 | return Bacon.once(seed).concat(this); 969 | }; 970 | 971 | EventStream.prototype.decorateWith = function(label, property) { 972 | return property.sampledBy(this, function(propertyValue, streamValue) { 973 | var result; 974 | result = cloneObject(streamValue); 975 | result[label] = propertyValue; 976 | return result; 977 | }); 978 | }; 979 | 980 | EventStream.prototype.mapEnd = function() { 981 | var args, f; 982 | f = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 983 | f = makeFunction(f, args); 984 | return this.withHandler(function(event) { 985 | if (event.isEnd()) { 986 | this.push(next(f(event))); 987 | this.push(end()); 988 | return Bacon.noMore; 989 | } else { 990 | return this.push(event); 991 | } 992 | }); 993 | }; 994 | 995 | EventStream.prototype.withHandler = function(handler) { 996 | var dispatcher; 997 | dispatcher = new Dispatcher(this.subscribe, handler); 998 | return new EventStream(dispatcher.subscribe); 999 | }; 1000 | 1001 | EventStream.prototype.withSubscribe = function(subscribe) { 1002 | return new EventStream(subscribe); 1003 | }; 1004 | 1005 | return EventStream; 1006 | 1007 | })(Observable); 1008 | 1009 | Property = (function(_super) { 1010 | 1011 | __extends(Property, _super); 1012 | 1013 | function Property(subscribe) { 1014 | var combine, 1015 | _this = this; 1016 | this.subscribe = subscribe; 1017 | this.toEventStream = __bind(this.toEventStream, this); 1018 | this.toProperty = __bind(this.toProperty, this); 1019 | this.changes = __bind(this.changes, this); 1020 | this.sample = __bind(this.sample, this); 1021 | Property.__super__.constructor.call(this); 1022 | combine = function(other, leftSink, rightSink) { 1023 | var myVal, otherVal; 1024 | myVal = None; 1025 | otherVal = None; 1026 | return new Property(function(sink) { 1027 | var checkEnd, combiningSink, initialSent, myEnd, mySink, otherEnd, otherSink, unsubBoth, unsubMe, unsubOther, unsubscribed; 1028 | unsubscribed = false; 1029 | unsubMe = nop; 1030 | unsubOther = nop; 1031 | unsubBoth = function() { 1032 | unsubMe(); 1033 | unsubOther(); 1034 | return unsubscribed = true; 1035 | }; 1036 | myEnd = false; 1037 | otherEnd = false; 1038 | checkEnd = function() { 1039 | var reply; 1040 | if (myEnd && otherEnd) { 1041 | reply = sink(end()); 1042 | if (reply === Bacon.noMore) unsubBoth(); 1043 | return reply; 1044 | } 1045 | }; 1046 | initialSent = false; 1047 | combiningSink = function(markEnd, setValue, thisSink) { 1048 | return function(event) { 1049 | var reply; 1050 | if (event.isEnd()) { 1051 | markEnd(); 1052 | checkEnd(); 1053 | return Bacon.noMore; 1054 | } else if (event.isError()) { 1055 | reply = sink(event); 1056 | if (reply === Bacon.noMore) unsubBoth(); 1057 | return reply; 1058 | } else { 1059 | setValue(new Some(event.value)); 1060 | if (myVal.isDefined && otherVal.isDefined) { 1061 | if (initialSent && event.isInitial()) { 1062 | return Bacon.more; 1063 | } else { 1064 | initialSent = true; 1065 | reply = thisSink(sink, event, myVal.value, otherVal.value); 1066 | if (reply === Bacon.noMore) unsubBoth(); 1067 | return reply; 1068 | } 1069 | } else { 1070 | return Bacon.more; 1071 | } 1072 | } 1073 | }; 1074 | }; 1075 | mySink = combiningSink((function() { 1076 | return myEnd = true; 1077 | }), (function(value) { 1078 | return myVal = value; 1079 | }), leftSink); 1080 | otherSink = combiningSink((function() { 1081 | return otherEnd = true; 1082 | }), (function(value) { 1083 | return otherVal = value; 1084 | }), rightSink); 1085 | unsubMe = _this.subscribe(mySink); 1086 | if (!unsubscribed) unsubOther = other.subscribe(otherSink); 1087 | return unsubBoth; 1088 | }); 1089 | }; 1090 | this.combine = function(other, f) { 1091 | var combinator, combineAndPush; 1092 | combinator = toCombinator(f); 1093 | combineAndPush = function(sink, event, myVal, otherVal) { 1094 | return sink(event.apply(combinator(myVal, otherVal))); 1095 | }; 1096 | return combine(other, combineAndPush, combineAndPush); 1097 | }; 1098 | this.sampledBy = function(sampler, combinator) { 1099 | var pushPropertyValue; 1100 | if (combinator == null) combinator = former; 1101 | combinator = toCombinator(combinator); 1102 | pushPropertyValue = function(sink, event, propertyVal, streamVal) { 1103 | return sink(event.apply(combinator(propertyVal, streamVal))); 1104 | }; 1105 | return combine(sampler, nop, pushPropertyValue).changes().takeUntil(sampler.filter(false).mapEnd()); 1106 | }; 1107 | } 1108 | 1109 | Property.prototype.sample = function(interval) { 1110 | return this.sampledBy(Bacon.interval(interval, {})); 1111 | }; 1112 | 1113 | Property.prototype.changes = function() { 1114 | var _this = this; 1115 | return new EventStream(function(sink) { 1116 | return _this.subscribe(function(event) { 1117 | if (!event.isInitial()) return sink(event); 1118 | }); 1119 | }); 1120 | }; 1121 | 1122 | Property.prototype.withHandler = function(handler) { 1123 | return new Property(new PropertyDispatcher(this.subscribe, handler).subscribe); 1124 | }; 1125 | 1126 | Property.prototype.withSubscribe = function(subscribe) { 1127 | return new Property(new PropertyDispatcher(subscribe).subscribe); 1128 | }; 1129 | 1130 | Property.prototype.toProperty = function() { 1131 | return this; 1132 | }; 1133 | 1134 | Property.prototype.toEventStream = function() { 1135 | var _this = this; 1136 | return new EventStream(function(sink) { 1137 | return _this.subscribe(function(event) { 1138 | if (event.isInitial()) event = next(event.value); 1139 | return sink(event); 1140 | }); 1141 | }); 1142 | }; 1143 | 1144 | Property.prototype.and = function(other) { 1145 | return this.combine(other, function(x, y) { 1146 | return x && y; 1147 | }); 1148 | }; 1149 | 1150 | Property.prototype.or = function(other) { 1151 | return this.combine(other, function(x, y) { 1152 | return x || y; 1153 | }); 1154 | }; 1155 | 1156 | Property.prototype.delay = function(delay) { 1157 | return propertyThenStream(this, this.changes().delay(delay)); 1158 | }; 1159 | 1160 | Property.prototype.throttle = function(delay) { 1161 | return propertyThenStream(this, this.changes().throttle(delay)); 1162 | }; 1163 | 1164 | return Property; 1165 | 1166 | })(Observable); 1167 | 1168 | propertyThenStream = function(property, stream) { 1169 | var getInitValue; 1170 | getInitValue = function(property) { 1171 | var value; 1172 | value = None; 1173 | property.subscribe(function(event) { 1174 | if (event.isInitial()) value = new Some(event.value); 1175 | return Bacon.noMore; 1176 | }); 1177 | return value; 1178 | }; 1179 | return stream.toProperty.apply(stream, getInitValue(property).toArray()); 1180 | }; 1181 | 1182 | Dispatcher = (function() { 1183 | 1184 | function Dispatcher(subscribe, handleEvent) { 1185 | var ended, removeSink, sinks, unsubscribeFromSource, 1186 | _this = this; 1187 | if (subscribe == null) { 1188 | subscribe = function() { 1189 | return nop; 1190 | }; 1191 | } 1192 | sinks = []; 1193 | ended = false; 1194 | this.hasSubscribers = function() { 1195 | return sinks.length > 0; 1196 | }; 1197 | unsubscribeFromSource = nop; 1198 | removeSink = function(sink) { 1199 | return remove(sink, sinks); 1200 | }; 1201 | this.push = function(event) { 1202 | var done, reply, sink, waiters, _i, _len, _ref2; 1203 | waiters = void 0; 1204 | done = function() { 1205 | var w, ws, _i, _len; 1206 | if (waiters != null) { 1207 | ws = waiters; 1208 | waiters = void 0; 1209 | for (_i = 0, _len = ws.length; _i < _len; _i++) { 1210 | w = ws[_i]; 1211 | w(); 1212 | } 1213 | } 1214 | return event.onDone = Event.prototype.onDone; 1215 | }; 1216 | event.onDone = function(listener) { 1217 | if ((waiters != null) && !_.contains(waiters, listener)) { 1218 | return waiters.push(listener); 1219 | } else { 1220 | return waiters = [listener]; 1221 | } 1222 | }; 1223 | assertEvent(event); 1224 | _ref2 = cloneArray(sinks); 1225 | for (_i = 0, _len = _ref2.length; _i < _len; _i++) { 1226 | sink = _ref2[_i]; 1227 | reply = sink(event); 1228 | if (reply === Bacon.noMore || event.isEnd()) removeSink(sink); 1229 | } 1230 | done(); 1231 | if (_this.hasSubscribers()) { 1232 | return Bacon.more; 1233 | } else { 1234 | return Bacon.noMore; 1235 | } 1236 | }; 1237 | if (handleEvent == null) { 1238 | handleEvent = function(event) { 1239 | return this.push(event); 1240 | }; 1241 | } 1242 | this.handleEvent = function(event) { 1243 | assertEvent(event); 1244 | if (event.isEnd()) ended = true; 1245 | return handleEvent.apply(_this, [event]); 1246 | }; 1247 | this.subscribe = function(sink) { 1248 | if (ended) { 1249 | sink(end()); 1250 | return nop; 1251 | } else { 1252 | assertFunction(sink); 1253 | sinks.push(sink); 1254 | if (sinks.length === 1) { 1255 | unsubscribeFromSource = subscribe(_this.handleEvent); 1256 | } 1257 | assertFunction(unsubscribeFromSource); 1258 | return function() { 1259 | removeSink(sink); 1260 | if (!_this.hasSubscribers()) return unsubscribeFromSource(); 1261 | }; 1262 | } 1263 | }; 1264 | } 1265 | 1266 | return Dispatcher; 1267 | 1268 | })(); 1269 | 1270 | PropertyDispatcher = (function(_super) { 1271 | 1272 | __extends(PropertyDispatcher, _super); 1273 | 1274 | function PropertyDispatcher(subscribe, handleEvent) { 1275 | var current, ended, push, 1276 | _this = this; 1277 | PropertyDispatcher.__super__.constructor.call(this, subscribe, handleEvent); 1278 | current = None; 1279 | push = this.push; 1280 | subscribe = this.subscribe; 1281 | ended = false; 1282 | this.push = function(event) { 1283 | if (event.isEnd()) ended = true; 1284 | if (event.hasValue()) current = new Some(event.value); 1285 | return push.apply(_this, [event]); 1286 | }; 1287 | this.subscribe = function(sink) { 1288 | var reply, shouldBounceInitialValue; 1289 | shouldBounceInitialValue = function() { 1290 | return _this.hasSubscribers() || ended; 1291 | }; 1292 | reply = current.filter(shouldBounceInitialValue).map(function(val) { 1293 | return sink(initial(val)); 1294 | }); 1295 | if (reply.getOrElse(Bacon.more) === Bacon.noMore) { 1296 | return nop; 1297 | } else if (ended) { 1298 | sink(end()); 1299 | return nop; 1300 | } else { 1301 | return subscribe.apply(_this, [sink]); 1302 | } 1303 | }; 1304 | } 1305 | 1306 | return PropertyDispatcher; 1307 | 1308 | })(Dispatcher); 1309 | 1310 | Bus = (function(_super) { 1311 | 1312 | __extends(Bus, _super); 1313 | 1314 | function Bus() { 1315 | var dispatcher, ended, guardedSink, inputs, sink, subscribeAll, subscribeThis, unsubAll, unsubFuncs, 1316 | _this = this; 1317 | sink = void 0; 1318 | unsubFuncs = []; 1319 | inputs = []; 1320 | ended = false; 1321 | guardedSink = function(input) { 1322 | return function(event) { 1323 | if (event.isEnd()) { 1324 | remove(input, inputs); 1325 | return Bacon.noMore; 1326 | } else { 1327 | return sink(event); 1328 | } 1329 | }; 1330 | }; 1331 | unsubAll = function() { 1332 | var f, _i, _len; 1333 | for (_i = 0, _len = unsubFuncs.length; _i < _len; _i++) { 1334 | f = unsubFuncs[_i]; 1335 | f(); 1336 | } 1337 | return unsubFuncs = []; 1338 | }; 1339 | subscribeAll = function(newSink) { 1340 | var input, _i, _len, _ref2; 1341 | sink = newSink; 1342 | unsubFuncs = []; 1343 | _ref2 = cloneArray(inputs); 1344 | for (_i = 0, _len = _ref2.length; _i < _len; _i++) { 1345 | input = _ref2[_i]; 1346 | unsubFuncs.push(input.subscribe(guardedSink(input))); 1347 | } 1348 | return unsubAll; 1349 | }; 1350 | dispatcher = new Dispatcher(subscribeAll); 1351 | subscribeThis = function(sink) { 1352 | return dispatcher.subscribe(sink); 1353 | }; 1354 | Bus.__super__.constructor.call(this, subscribeThis); 1355 | this.plug = function(inputStream) { 1356 | if (ended) return; 1357 | inputs.push(inputStream); 1358 | if ((sink != null)) { 1359 | return unsubFuncs.push(inputStream.subscribe(guardedSink(inputStream))); 1360 | } 1361 | }; 1362 | this.push = function(value) { 1363 | if (sink != null) return sink(next(value)); 1364 | }; 1365 | this.error = function(error) { 1366 | if (sink != null) return sink(new Error(error)); 1367 | }; 1368 | this.end = function() { 1369 | ended = true; 1370 | unsubAll(); 1371 | if (sink != null) return sink(end()); 1372 | }; 1373 | } 1374 | 1375 | return Bus; 1376 | 1377 | })(EventStream); 1378 | 1379 | Some = (function() { 1380 | 1381 | function Some(value) { 1382 | this.value = value; 1383 | } 1384 | 1385 | Some.prototype.getOrElse = function() { 1386 | return this.value; 1387 | }; 1388 | 1389 | Some.prototype.filter = function(f) { 1390 | if (f(this.value)) { 1391 | return new Some(this.value); 1392 | } else { 1393 | return None; 1394 | } 1395 | }; 1396 | 1397 | Some.prototype.map = function(f) { 1398 | return new Some(f(this.value)); 1399 | }; 1400 | 1401 | Some.prototype.isDefined = true; 1402 | 1403 | Some.prototype.toArray = function() { 1404 | return [this.value]; 1405 | }; 1406 | 1407 | return Some; 1408 | 1409 | })(); 1410 | 1411 | None = { 1412 | getOrElse: function(value) { 1413 | return value; 1414 | }, 1415 | filter: function() { 1416 | return None; 1417 | }, 1418 | map: function() { 1419 | return None; 1420 | }, 1421 | isDefined: false, 1422 | toArray: function() { 1423 | return []; 1424 | } 1425 | }; 1426 | 1427 | Bacon.EventStream = EventStream; 1428 | 1429 | Bacon.Property = Property; 1430 | 1431 | Bacon.Observable = Observable; 1432 | 1433 | Bacon.Bus = Bus; 1434 | 1435 | Bacon.Initial = Initial; 1436 | 1437 | Bacon.Next = Next; 1438 | 1439 | Bacon.End = End; 1440 | 1441 | Bacon.Error = Error; 1442 | 1443 | nop = function() {}; 1444 | 1445 | latter = function(_, x) { 1446 | return x; 1447 | }; 1448 | 1449 | former = function(x, _) { 1450 | return x; 1451 | }; 1452 | 1453 | initial = function(value) { 1454 | return new Initial(value); 1455 | }; 1456 | 1457 | next = function(value) { 1458 | return new Next(value); 1459 | }; 1460 | 1461 | end = function() { 1462 | return new End(); 1463 | }; 1464 | 1465 | isEvent = function(x) { 1466 | return (x != null) && (x.isEvent != null) && x.isEvent(); 1467 | }; 1468 | 1469 | toEvent = function(x) { 1470 | if (isEvent(x)) { 1471 | return x; 1472 | } else { 1473 | return next(x); 1474 | } 1475 | }; 1476 | 1477 | cloneArray = function(xs) { 1478 | return xs.slice(0); 1479 | }; 1480 | 1481 | cloneObject = function(src) { 1482 | var clone, key, value; 1483 | clone = {}; 1484 | for (key in src) { 1485 | value = src[key]; 1486 | clone[key] = value; 1487 | } 1488 | return clone; 1489 | }; 1490 | 1491 | remove = function(x, xs) { 1492 | var i; 1493 | i = xs.indexOf(x); 1494 | if (i >= 0) return xs.splice(i, 1); 1495 | }; 1496 | 1497 | assert = function(message, condition) { 1498 | if (!condition) throw message; 1499 | }; 1500 | 1501 | assertEvent = function(event) { 1502 | assert("not an event : " + event, event.isEvent != null); 1503 | return assert("not event", event.isEvent()); 1504 | }; 1505 | 1506 | assertFunction = function(f) { 1507 | return assert("not a function : " + f, isFunction(f)); 1508 | }; 1509 | 1510 | isFunction = function(f) { 1511 | return typeof f === "function"; 1512 | }; 1513 | 1514 | assertArray = function(xs) { 1515 | return assert("not an array : " + xs, xs instanceof Array); 1516 | }; 1517 | 1518 | assertString = function(x) { 1519 | return assert("not a string : " + x, typeof x === "string"); 1520 | }; 1521 | 1522 | methodCall = function(obj, method, args) { 1523 | assertString(method); 1524 | if (args === void 0) args = []; 1525 | return function(value) { 1526 | return obj[method].apply(obj, args.concat([value])); 1527 | }; 1528 | }; 1529 | 1530 | partiallyApplied = function(f, args) { 1531 | return function(value) { 1532 | return f.apply(null, args.concat([value])); 1533 | }; 1534 | }; 1535 | 1536 | makeFunction = function(f, args) { 1537 | if (isFunction(f)) { 1538 | if (args.length) { 1539 | return partiallyApplied(f, args); 1540 | } else { 1541 | return f; 1542 | } 1543 | } else if (isFieldKey(f)) { 1544 | return toFieldExtractor(f, args); 1545 | } else if (typeof f === "object" && args.length) { 1546 | return methodCall(f, _.head(args), _.tail(args)); 1547 | } else { 1548 | return _.always(f); 1549 | } 1550 | }; 1551 | 1552 | isFieldKey = function(f) { 1553 | return (typeof f === "string") && f.length > 1 && f[0] === "."; 1554 | }; 1555 | 1556 | toFieldExtractor = function(f, args) { 1557 | var partFuncs, parts; 1558 | parts = f.slice(1).split("."); 1559 | partFuncs = _.map(toSimpleExtractor(args), parts); 1560 | return function(value) { 1561 | var f, _i, _len; 1562 | for (_i = 0, _len = partFuncs.length; _i < _len; _i++) { 1563 | f = partFuncs[_i]; 1564 | value = f(value); 1565 | } 1566 | return value; 1567 | }; 1568 | }; 1569 | 1570 | toSimpleExtractor = function(args) { 1571 | return function(key) { 1572 | return function(value) { 1573 | var fieldValue; 1574 | fieldValue = value[key]; 1575 | if (isFunction(fieldValue)) { 1576 | return fieldValue.apply(null, args); 1577 | } else { 1578 | return fieldValue; 1579 | } 1580 | }; 1581 | }; 1582 | }; 1583 | 1584 | toFieldKey = function(f) { 1585 | return f.slice(1); 1586 | }; 1587 | 1588 | toCombinator = function(f) { 1589 | var key; 1590 | if (isFunction(f)) { 1591 | return f; 1592 | } else if (isFieldKey(f)) { 1593 | key = toFieldKey(f); 1594 | return function(left, right) { 1595 | return left[key](right); 1596 | }; 1597 | } else { 1598 | return assert("not a function or a field key: " + f, false); 1599 | } 1600 | }; 1601 | 1602 | toOption = function(v) { 1603 | if (v instanceof Some || v === None) { 1604 | return v; 1605 | } else { 1606 | return new Some(v); 1607 | } 1608 | }; 1609 | 1610 | if ((typeof define !== "undefined" && define !== null) && (define.amd != null)) { 1611 | if (typeof define === "function") { 1612 | define(function() { 1613 | return Bacon; 1614 | }); 1615 | } 1616 | } 1617 | 1618 | _ = { 1619 | head: function(xs) { 1620 | return xs[0]; 1621 | }, 1622 | always: function(x) { 1623 | return function() { 1624 | return x; 1625 | }; 1626 | }, 1627 | empty: function(xs) { 1628 | return xs.length === 0; 1629 | }, 1630 | tail: function(xs) { 1631 | return xs.slice(1, xs.length); 1632 | }, 1633 | filter: function(f, xs) { 1634 | var filtered, x, _i, _len; 1635 | filtered = []; 1636 | for (_i = 0, _len = xs.length; _i < _len; _i++) { 1637 | x = xs[_i]; 1638 | if (f(x)) filtered.push(x); 1639 | } 1640 | return filtered; 1641 | }, 1642 | map: function(f, xs) { 1643 | var x, _i, _len, _results; 1644 | _results = []; 1645 | for (_i = 0, _len = xs.length; _i < _len; _i++) { 1646 | x = xs[_i]; 1647 | _results.push(f(x)); 1648 | } 1649 | return _results; 1650 | }, 1651 | contains: function(xs, x) { 1652 | return xs.indexOf(x) >= 0; 1653 | }, 1654 | id: function(x) { 1655 | return x; 1656 | }, 1657 | last: function(xs) { 1658 | return xs[xs.length - 1]; 1659 | } 1660 | }; 1661 | 1662 | Bacon._ = _; 1663 | 1664 | }).call(this); 1665 | 1666 | --------------------------------------------------------------------------------