From 262ddccfb31d4e913f8549a7ec2bfec5a0e1869f Mon Sep 17 00:00:00 2001 From: paul taylor Date: Thu, 18 Jul 2013 13:33:31 -0500 Subject: [PATCH 1/7] Updates Disposable to optionally accept a disposal action. --- package.json | 2 +- src/core/disposables/disposable.js | 2 +- tests/ObservableCreationTest.js | 51 +++++++++++++++++++++++++++--- 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 083d4811a..38415c29d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "rx", "title": "Reactive Extensions for JavaScript (RxJS)", "description": "Library for composing asynchronous and event-based operations in JavaScript", - "version": "2.1.3", + "version": "2.1.4", "homepage": "http://rx.codeplex.com", "author": { "name": "Cloud Programmability Team", diff --git a/src/core/disposables/disposable.js b/src/core/disposables/disposable.js index 3235e4258..f214251e8 100644 --- a/src/core/disposables/disposable.js +++ b/src/core/disposables/disposable.js @@ -6,7 +6,7 @@ */ var Disposable = Rx.Disposable = function (action) { this.isDisposed = false; - this.action = action; + this.action = typeof action == 'function' ? action : noop; }; /** diff --git a/tests/ObservableCreationTest.js b/tests/ObservableCreationTest.js index 8923ef582..46e93e0f1 100644 --- a/tests/ObservableCreationTest.js +++ b/tests/ObservableCreationTest.js @@ -10,9 +10,9 @@ onError = root.ReactiveTest.onError, onCompleted = root.ReactiveTest.onCompleted, subscribe = root.ReactiveTest.subscribe, - created = root.ReactiveTest.created, - subscribed = root.ReactiveTest.subscribed, - disposed = root.ReactiveTest.disposed; + created = root.ReactiveTest.created, + subscribed = root.ReactiveTest.subscribed, + disposed = root.ReactiveTest.disposed; var BooleanDisposable = (function () { function BooleanDisposable() { @@ -31,8 +31,8 @@ return Observable.returnValue(42, scheduler); }); results.messages.assertEqual( - onNext(201, 42), - onCompleted(201)); + onNext(201, 42), + onCompleted(201)); }); test('Return_Disposed', function () { @@ -524,6 +524,47 @@ results.messages.assertEqual(onError(200, ex)); }); + test('Create_Noop_Next', function () { + var results, scheduler; + scheduler = new TestScheduler(); + results = scheduler.startWithCreate(function () { + return Observable.create(function (o) { + o.onNext(1); + o.onNext(2); + }); + }); + results.messages.assertEqual(onNext(200, 1), onNext(200, 2)); + }); + + test('Create_Noop_Completed', function () { + var results, scheduler; + scheduler = new TestScheduler(); + results = scheduler.startWithCreate(function () { + return Observable.create(function (o) { + o.onCompleted(); + o.onNext(100); + o.onError('ex'); + o.onCompleted(); + }); + }); + results.messages.assertEqual(onCompleted(200)); + }); + + test('Create_Noop_Error', function () { + var ex, results, scheduler; + scheduler = new TestScheduler(); + ex = 'ex'; + results = scheduler.startWithCreate(function () { + return Observable.create(function (o) { + o.onError(ex); + o.onNext(100); + o.onError('foo'); + o.onCompleted(); + }); + }); + results.messages.assertEqual(onError(200, ex)); + }); + test('Create_Exception', function () { raises(function () { return Observable.create(function (o) { From 2afa8e5a5046bdd7534abb5d3224ab170a6599ae Mon Sep 17 00:00:00 2001 From: paul taylor Date: Thu, 25 Jul 2013 13:46:23 -0500 Subject: [PATCH 2/7] Prevents BehaviorSubject from dispatching `undefined` to new subscribers if initialized without a value. --- src/core/subjects/behaviorsubject.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/core/subjects/behaviorsubject.js b/src/core/subjects/behaviorsubject.js index caa114ee2..002571229 100644 --- a/src/core/subjects/behaviorsubject.js +++ b/src/core/subjects/behaviorsubject.js @@ -3,12 +3,19 @@ * Observers can subscribe to the subject to receive the last (or initial) value and all subsequent notifications. */ var BehaviorSubject = Rx.BehaviorSubject = (function (_super) { + + var no_value = {}; + function subscribe(observer) { var ex; checkDisposed.call(this); if (!this.isStopped) { this.observers.push(observer); - observer.onNext(this.value); + + if(this.value !== no_value) { + observer.onNext(this.value); + } + return new InnerSubscription(this, observer); } ex = this.exception; @@ -30,7 +37,7 @@ function BehaviorSubject(value) { _super.call(this, subscribe); - this.value = value, + this.value = value === void(0) ? no_value : value, this.observers = [], this.isDisposed = false, this.isStopped = false, From 993a179b690db68ac87bd552a8bce75a5416388c Mon Sep 17 00:00:00 2001 From: paul taylor Date: Thu, 25 Jul 2013 13:49:29 -0500 Subject: [PATCH 3/7] version++ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 38415c29d..185ee0ff9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "rx", "title": "Reactive Extensions for JavaScript (RxJS)", "description": "Library for composing asynchronous and event-based operations in JavaScript", - "version": "2.1.4", + "version": "2.1.6", "homepage": "http://rx.codeplex.com", "author": { "name": "Cloud Programmability Team", From 2a7b79a56422234437b6bff0ddcd16e7af030e7f Mon Sep 17 00:00:00 2001 From: paul taylor Date: Thu, 25 Jul 2013 15:20:56 -0500 Subject: [PATCH 4/7] Adds new BehaviorSubject test to make sure an empty BehaviorSubject doesn't dispatch an undefined value. --- tests/BehaviorSubjectTest.js | 19 +++++++++++++++++++ tests/ReactiveAssert.js | 4 ++++ 2 files changed, 23 insertions(+) diff --git a/tests/BehaviorSubjectTest.js b/tests/BehaviorSubjectTest.js index ab2a6e8c6..203c20d1f 100644 --- a/tests/BehaviorSubjectTest.js +++ b/tests/BehaviorSubjectTest.js @@ -297,6 +297,25 @@ ); }); + test('UndefinedValueNotDispatched', function () { + var scheduler = new TestScheduler(); + + var subject; + + var results = scheduler.createObserver(); + + var subscription; + + scheduler.scheduleAbsolute(100, function () { subject = new BehaviorSubject(); }); + scheduler.scheduleAbsolute(200, function () { subscription = subject.subscribe(results); }); + scheduler.scheduleAbsolute(500, function () { subscription.dispose(); }); + scheduler.scheduleAbsolute(600, function () { subject.dispose(); }); + + scheduler.start(); + + results.messages.assertEmpty(); + }); + // must call `QUnit.start()` if using QUnit < 1.3.0 with Node.js or any // version of QUnit with Narwhal, Rhino, or RingoJS diff --git a/tests/ReactiveAssert.js b/tests/ReactiveAssert.js index 8b5dff512..8f42644fb 100644 --- a/tests/ReactiveAssert.js +++ b/tests/ReactiveAssert.js @@ -27,6 +27,10 @@ } ok(isOk, message || createMessage(expected, actual)); } + + Array.prototype.assertEmpty = function() { + return areElementsEqual(this, [], defaultComparer); + } Array.prototype.assertEqual = function () { var actual = slice.call(arguments); From 94b5a821f361d86d7a8427fc607709730106ba97 Mon Sep 17 00:00:00 2001 From: paul taylor Date: Mon, 29 Jul 2013 14:27:32 -0500 Subject: [PATCH 5/7] Adds the latest RxJS-DOM source to the bridges. --- src/bridges/html/rx.dom.js | 488 ++++++++++++++++++++++++++++++++++++ src/bridges/html/rx.html.js | 229 ----------------- 2 files changed, 488 insertions(+), 229 deletions(-) create mode 100644 src/bridges/html/rx.dom.js delete mode 100644 src/bridges/html/rx.html.js diff --git a/src/bridges/html/rx.dom.js b/src/bridges/html/rx.dom.js new file mode 100644 index 000000000..7d7fc7532 --- /dev/null +++ b/src/bridges/html/rx.dom.js @@ -0,0 +1,488 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +(function (root, factory) { + var freeExports = typeof exports == 'object' && exports, + freeModule = typeof module == 'object' && module && module.exports == freeExports && module, + freeGlobal = typeof global == 'object' && global; + if (freeGlobal.global === freeGlobal) { + window = freeGlobal; + } + + // Because of build optimizers + if (typeof define === 'function' && define.amd) { + define(['rx', 'exports'], function (Rx, exports) { + root.Rx = factory(root, exports, Rx); + return root.Rx; + }); + } else if (typeof module === 'object' && module && module.exports === freeExports) { + module.exports = factory(root, module.exports, require('./rx')); + } else { + root.Rx = factory(root, {}, root.Rx); + } +}(this, function (global, exp, Rx, undefined) { + + var freeExports = typeof exports == 'object' && exports, + freeModule = typeof module == 'object' && module && module.exports == freeExports && module, + freeGlobal = typeof global == 'object' && global; + if (freeGlobal.global === freeGlobal) { + window = freeGlobal; + } + + var Rx = window.Rx, + Observable = Rx.Observable, + observableProto = Observable.prototype, + observableCreate = Observable.create, + observableCreateWithDisposable = Observable.createWithDisposable, + disposableCreate = Rx.Disposable.create, + CompositeDisposable = Rx.CompositeDisposable, + Subject = Rx.Subject, + Scheduler = Rx.Scheduler, + dom = Rx.DOM = {}, + ajax = Rx.DOM.Request = {}; + + /** @private + * Creates an event listener on a single element with compat back to DOM Level 1. + */ + function createListener (element, name, handler) { + // Standards compliant + if (element.addEventListener) { + element.addEventListener(name, handler, false); + return disposableCreate(function () { + element.removeEventListener(name, handler, false); + }); + } else if (element.attachEvent) { + // IE Specific + var innerHandler = function (event) { + event || (event = window.event); + event.target = event.target || event.srcElement; + handler(event); + }; + element.attachEvent('on' + name, innerHandler); + return disposableCreate(function () { + element.detachEvent('on' + name, innerHandler); + }); + } else { + // Level 1 DOM Events + var innerHandler = function (event) { + event || (event = window.event); + event.target = event.target || event.srcElement; + handler(event); + }; + element['on' + name] = innerHandler; + return disposableCreate(function () { + element['on' + name] = null; + }); + } + } + + /** @private + * Creates event listeners on either a single element or NodeList + */ + function createEventListener (el, eventName, handler) { + var disposables = new CompositeDisposable(); + + if ( el && el.nodeName || el === window ) { + disposables.add(createListener(el, eventName, handler)); + } else if ( el && el.length ) { + for (var i = 0, len = el.length; i < len; i++) { + disposables.add(createEventListener(el[i], eventName, handler)); + } + } + + return disposables; + } + + /** + * Creates an observable sequence by adding an event listener to the matching DOMElement or each item in the NodeList. + * + * @example + * source = Rx.DOM.fromEvent(element, 'mouseup'); + * + * @param {Object} element The DOMElement or NodeList to attach a listener. + * @param {String} eventName The event name to attach the observable sequence. + * @returns {Observable} An observable sequence of events from the specified element and the specified event. + */ + dom.fromEvent = function (element, eventName) { + return observableCreateWithDisposable(function (observer) { + return createEventListener(element, eventName, function handler (e) { observer.onNext(e); }); + }); + }; + + /* @private + * Gets the proper XMLHttpRequest for support for older IE + */ + function getXMLHttpRequest() { + if (global.XMLHttpRequest) { + return new global.XMLHttpRequest; + } else { + try { + return new global.ActiveXObject('Microsoft.XMLHTTP'); + } catch (e) { + throw new Error('XMLHttpRequest is not supported by your browser'); + } + } + } + + /** + * Creates a cold observable for an Ajax request with either a settings object with url, headers, etc or a string for a URL. + * + * @example + * source = Rx.DOM.Request.ajaxCold('/products'); + * source = Rx.DOM.Request.ajaxCold( url: 'products', method: 'GET' }); + * + * @param {Object} settings Can be one of the following: + * + * A string of the URL to make the Ajax call. + * An object with the following properties + * - url: URL of the request + * - method: Method of the request, such as GET, POST, PUT, PATCH, DELETE + * - async: Whether the request is async + * - headers: Optional headers + * + * @returns {Observable} An observable sequence containing the XMLHttpRequest. + */ + ajax.ajaxCold = function (settings) { + return observableCreateWithDisposable( function (observer) { + if (typeof settings === 'string') { + settings = { method: 'GET', url: settings, async: true }; + } + if (settings.async === undefined) { + settings.async = true; + } + + var xhr; + try { + xhr = getXMLHttpRequest(); + } catch (err) { + observer.onError(err); + } + + try { + if (settings.user) { + xhr.open(settings.method, settings.url, settings.async, settings.user, settings.password); + } else { + xhr.open(settings.method, settings.url, settings.async); + } + + if (settings.headers) { + var headers = settings.headers; + for (var header in headers) { + if (headers.hasOwnProperty(header)) { + xhr.setRequestHeader(header, headers[header]); + } + } + } + + xhr.onreadystatechange = xhr.onload = function () { + if (xhr.readyState === 4) { + var status = xhr.status; + if ((status >= 200 && status <= 300) || status === 0 || status === '') { + observer.onNext(xhr); + observer.onCompleted(); + } else { + observer.onError(xhr); + } + } + }; + + xhr.onerror = function () { + observer.onError(xhr); + }; + + xhr.send(settings.body || null); + } catch (e) { + observer.onError(e); + } + + return disposableCreate( function () { + if (xhr.readyState !== 4) { + xhr.abort(); + } + }); + }); + }; + + /** @private */ + var ajaxCold = ajax.ajaxCold; + + /** + * Creates a hot observable for an Ajax request with either a settings object with url, headers, etc or a string for a URL. + * + * @example + * source = Rx.DOM.Request.ajax('/products'); + * source = Rx.DOM.Request.ajax( url: 'products', method: 'GET' }); + * + * @param {Object} settings Can be one of the following: + * + * A string of the URL to make the Ajax call. + * An object with the following properties + * - url: URL of the request + * - method: Method of the request, such as GET, POST, PUT, PATCH, DELETE + * - async: Whether the request is async + * - headers: Optional headers + * + * @returns {Observable} An observable sequence containing the XMLHttpRequest. + */ + var observableAjax = ajax.ajax = function (settings) { + return ajaxCold(settings).publishLast().refCount(); + }; + + /** + * Creates an observable sequence from an Ajax POST Request with the body. + * + * @param {String} url The URL to POST + * @param {Object} body The body to POST + * @returns {Observable} The observable sequence which contains the response from the Ajax POST. + */ + ajax.post = function (url, body) { + return observableAjax({ url: url, body: body, method: 'POST', async: true }); + }; + + /** + * Creates an observable sequence from an Ajax GET Request with the body. + * + * @param {String} url The URL to GET + * @returns {Observable} The observable sequence which contains the response from the Ajax GET. + */ + var observableGet = ajax.get = function (url) { + return observableAjax({ url: url, method: 'GET', async: true }); + }; + + if (JSON && typeof JSON.parse === 'function') { + /** + * Creates an observable sequence from JSON from an Ajax request + * + * @param {String} url The URL to GET + * @returns {Observable} The observable sequence which contains the parsed JSON. + */ + ajax.getJSON = function (url) { + return observableGet(url).select(function (xhr) { + return JSON.parse(xhr.responseText); + }); + }; + } + + /** @private + * Destroys the current element + */ + var destroy = (function () { + var trash = document.createElement('div'); + return function (element) { + trash.appendChild(element); + trash.innerHTML = ''; + }; + })(); + + /** + * Creates a cold observable JSONP Request with the specified settings. + * + * @example + * source = Rx.DOM.Request.jsonpRequestCold('http://www.bing.com/?q=foo&JSONPRequest=?'); + * source = Rx.DOM.Request.jsonpRequestCold( url: 'http://bing.com/?q=foo', jsonp: 'JSONPRequest' }); + * + * @param {Object} settings Can be one of the following: + * + * A string of the URL to make the JSONP call with the JSONPCallback=? in the url. + * An object with the following properties + * - url: URL of the request + * - jsonp: The named callback parameter for the JSONP call + * + * @returns {Observable} A cold observable containing the results from the JSONP call. + */ + ajax.jsonpRequestCold = (function () { + var uniqueId = 0; + return function (settings) { + return Observable.createWithDisposable(function (observer) { + + if (typeof settings === 'string') { + settings = { url: settings } + } + if (!settings.jsonp) { + settings.jsonp = 'JSONPCallback'; + } + + var head = document.getElementsByTagName('head')[0] || document.documentElement, + tag = document.createElement('script'), + handler = 'rxjscallback' + uniqueId++; + + settings.url = settings.url.replace('=' + settings.jsonp, '=' + handler); + + window[handler] = function (data) { + observer.onNext(data); + observer.onCompleted(); + }; + + tag.src = settings.url; + tag.async = true; + tag.onload = tag.onreadystatechange = function (_, abort) { + if ( abort || !tag.readyState || /loaded|complete/.test(tag.readyState) ) { + tag.onload = tag.onreadystatechange = null; + if (head && tag.parentNode) { + destroy(tag); + } + tag = undefined; + window[handler] = undefined; + } + + }; + head.insertBefore(tag, head.firstChild); + + return disposableCreate(function () { + if (!tag) { + return; + } + tag.onload = tag.onreadystatechange = null; + if (head && tag.parentNode) { + destroy(tag); + } + tag = undefined; + window[handler] = undefined; + }); + }); + }; + + })(); + + /** @private */ + var getJSONPRequestCold = ajax.jsonpRequestCold; + + /** + * Creates a hot observable JSONP Request with the specified settings. + * + * @example + * source = Rx.DOM.Request.getJSONPRequest('http://www.bing.com/?q=foo&JSONPRequest=?'); + * source = Rx.DOM.Request.getJSONPRequest( url: 'http://bing.com/?q=foo', jsonp: 'JSONPRequest' }); + * + * @param {Object} settings Can be one of the following: + * + * A string of the URL to make the JSONP call with the JSONPCallback=? in the url. + * An object with the following properties + * - url: URL of the request + * - jsonp: The named callback parameter for the JSONP call + * + * @returns {Observable} A hot observable containing the results from the JSONP call. + */ + ajax.jsonpRequest = function (settings) { + return getJSONPRequestCold(settings).publishLast().refCount(); + }; + if (window.WebSocket) { + /** + * Creates a WebSocket Subject with a given URL, protocol and an optional observer for the open event. + * + * @example + * var socket = Rx.DOM.fromWebSocket('http://localhost:8080', 'stock-protocol', function(e) { ... }); + * var socket = Rx.DOM.fromWebSocket('http://localhost:8080', 'stock-protocol', observer); + *s + * @param {String} url The URL of the WebSocket. + * @param {String} protocol The protocol of the WebSocket. + * @param {Function|Observer} [observerOrOnNext] An optional Observer or onNext function to capture the open event. + * @returns {Subject} An observable sequence wrapping a WebSocket. + */ + dom.fromWebSocket = function (url, protocol, observerOrOnNext) { + var socket = new window.WebSocket(url, protocol); + + var observable = observableCreate(function (obs) { + if (observerOrOnNext) { + socket.onopen = function (openEvent) { + if (typeof observerOrOnNext === 'function') { + observerOrOnNext(openEvent); + } else if (observerOrOnNext.onNext) { + observerOrOnNext.onNext(openEvent); + } + }; + } + + socket.onmessage = function (data) { + obs.onNext(data); + }; + + socket.onerror = function (err) { + obs.onError(err); + }; + + socket.onclose = function () { + obs.onCompleted(); + }; + + return function () { + socket.close(); + }; + }); + + var observer = observerCreate(function (data) { + if (socket.readyState === WebSocket.OPEN) { + socket.send(data); + } + }); + + return Subject.create(observer, observable); + }; + } + + + if (window.Worker) { + /** + * Creates a Web Worker with a given URL as a Subject. + * + * @example + * var worker = Rx.DOM.fromWebWorker('worker.js'); + * + * @param {String} url The URL of the Web Worker. + * @returns {Subject} A Subject wrapping the Web Worker. + */ + dom.fromWebWorker = function (url) { + var worker = new window.Worker(url); + + var observable = observableCreateWithDisposable(function (obs) { + worker.onmessage = function (data) { + obs.onNext(data); + }; + + worker.onerror = function (err) { + obs.onError(err); + }; + + return disposableCreate(function () { + worker.close(); + }); + }); + + var observer = observerCreate(function (data) { + worker.postMessage(data); + }); + + return Subject.create(observer, observable); + }; + } + + if (window.MutationObserver) { + + /** + * Creates an observable sequence from a Mutation Observer. + * MutationObserver provides developers a way to react to changes in a DOM. + * @example + * Rx.DOM.fromMutationObserver(document.getElementById('foo'), { attributes: true, childList: true, characterData: true }); + * + * @param {Object} target The Node on which to obserave DOM mutations. + * @param {Object} options A MutationObserverInit object, specifies which DOM mutations should be reported. + * @returns {Observable} An observable sequence which contains mutations on the given DOM target. + */ + dom.fromMutationObserver = function (target, options) { + + return observableCreate(function (observer) { + var mutationObserver = new MutationObserver(function (mutations) { + observer.onNext(mutations); + }); + + mutationObserver.observe(target, options); + + return function () { + mutationObserver.disconnect(); + }; + }); + + }; + + } + return Rx; +})); \ No newline at end of file diff --git a/src/bridges/html/rx.html.js b/src/bridges/html/rx.html.js deleted file mode 100644 index 1e7d4596c..000000000 --- a/src/bridges/html/rx.html.js +++ /dev/null @@ -1,229 +0,0 @@ -/** -* Copyright 2011 Microsoft Corporation -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -(function (root, factory) { - var freeExports = typeof exports == 'object' && exports && - (typeof root == 'object' && root && root == root.global && (window = root), exports); - - // Because of build optimizers - if (typeof define === 'function' && define.amd) { - define(['rx', 'exports'], function (Rx, exports) { - root.Rx = factory(root, exports, Rx); - return root.Rx; - }); - } else if (typeof module == 'object' && module && module.exports == freeExports) { - var rxroot = factory(root, module.exports, require('rx')); - module.exports = rxroot.Rx; - } else { - root.Rx = factory(root, {}, root.Rx); - } -}(this, function (global, undefined) { - var freeExports = typeof exports == 'object' && exports && - (typeof global == 'object' && global && global == global.global && (window = global), exports); - - var root = global.Rx, - Observable = root.Observable, - observableProto = Observable.prototype, - observableCreateWithDisposable = Observable.createWithDisposable, - disposableCreate = root.Disposable.create, - CompositeDisposable = root.CompositeDisposable, - RefCountDisposable = root.RefCountDisposable, - AsyncSubject = root.AsyncSubject; - - var createEventListener = function (el, eventName, handler) { - var disposables = new CompositeDisposable(), - - createListener = function (element, eventName, handler) { - if (element.addEventListener) { - element.addEventListener(eventName, handler, false); - return disposableCreate(function () { - element.removeEventListener(eventName, handler, false); - }); - } else if (element.attachEvent) { - element.attachEvent('on' + eventName, handler); - return disposableCreate(function () { - element.detachEvent('on' + eventName, handler); - }); - } else { - element['on' + eventName] = handler; - return disposableCreate(function () { - element['on' + eventName] = null; - }); - } - }; - - if ( el && el.nodeName || el === global ) { - disposables.add(createListener(el, eventName, handler)); - } else if ( el && el.length ) { - for (var i = 0, len = el.length; i < len; i++) { - disposables.add(createEventListener(el[i], eventName, handler)); - } - } - - return disposables; - }; - - Observable.fromEvent = function (element, eventName) { - return observableCreateWithDisposable(function (observer) { - var handler = function (e) { - observer.onNext(e); - }; - return createEventListener(element, eventName, handler); - }); - }; - - var destroy = (function () { - var trash = document.createElement('div'); - return function (element) { - trash.appendChild(element); - trash.innerHTML = ''; - }; - })(); - - - Observable.getJSONPRequest = (function () { - var uniqueId = 0; - return function (url) { - var subject = new AsyncSubject(), - head = document.getElementsByTagName('head')[0] || document.documentElement, - tag = document.createElement('script'), - handler = 'rxjscallback' + uniqueId++, - url = url.replace('=JSONPCallback', '=' + handler); - - global[handler] = function (data) { - subject.onNext(data); - subject.onCompleted(); - }; - - tag.src = url; - tag.async = true; - tag.onload = tag.onreadystatechange = function (_, abort) { - if ( abort || !tag.readyState || /loaded|complete/.test(tag.readyState) ) { - tag.onload = tag.onreadystatechange = null; - if (head && tag.parentNode) { - destroy(tag); - } - tag = undefined; - delete global[handler]; - } - - }; - head.insertBefore(tag, head.firstChild ); - var refCount = new RefCountDisposable(disposableCreate( function () { - if (!/loaded|complete/.test(tag.readyState)) { - tag.abort(); - tag.onload = tag.onreadystatechange = null; - if (head && tag.parentNode) { - destroy(tag); - } - tag = undefined; - delete global[handler]; - subject.onError(new Error('The script has been aborted')); - } - })); - - return observableCreateWithDisposable( function (subscriber) { - return new CompositeDisposable(subject.subscribe(subscriber), refCount.getDisposable()); - }); - }; - - })(); - - - - function getXMLHttpRequest() { - if (global.XMLHttpRequest) { - return new global.XMLHttpRequest; - } else { - try { - return new global.ActiveXObject('Microsoft.XMLHTTP'); - } catch (e) { - throw new Error('XMLHttpRequest is not supported by your browser'); - } - } - } - - var observableAjax = Observable.ajax = function (settings) { - if (typeof settings === 'string') { - settings = { method: 'GET', url: settings, async: true }; - } - if (settings.async === undefined) { - settings.async = true; - } - var subject = new AsyncSubject(), - xhr = getXMLHttpRequest(); - - if (settings.headers) { - var headers = settings.headers, header; - for (header in headers) { - xhr.setRequestHeader(header, headers[header]); - } - } - try { - if (details.user) { - xhr.open(settings.method, settings.url, settings.async, settings.user, settings.password); - } else { - xhr.open(settings.method, settings.url, settings.async); - } - xhr.onreadystatechange = xhr.onload = function () { - if (xhr.readyState === 4) { - var status = xhr.status; - if ((status >= 200 && status <= 300) || status === 0 || status === '') { - subject.onNext(xhr); - subject.onCompleted(); - } else { - subject.onError(xhr); - } - } - }; - xhr.onerror = xhr.onabort = function () { - subject.onError(xhr); - }; - xhr.send(settings.body || null); - } catch (e) { - subject.onError(e); - } - - var refCount = new RefCountDisposable(disposableCreate( function () { - if (xhr.readyState !== 4) { - xhr.abort(); - subject.onError(xhr); - } - })); - - return observableCreateWithDisposable( function (subscriber) { - return new CompositeDisposable(subject.subscribe(subscriber), refCount.getDisposable()); - }); - }; - - Observable.post = function (url, body) { - return observableAjax({ url: url, body: body, method: 'POST', async: true }); - }; - - var observableGet = Observable.get = function (url) { - return observableAjax({ url: url, method: 'GET', async: true }); - }; - - if (JSON && JSON.parse) { - Observable.getJSON = function (url) { - return observableGet(url).select(function (xhr) { - return JSON.parse(xhr.responseText); - }); - }; - } - - return root; - -})); \ No newline at end of file From f250e59609809c4fed3dd6ea39520951bd17f2c1 Mon Sep 17 00:00:00 2001 From: paul taylor Date: Mon, 29 Jul 2013 14:34:48 -0500 Subject: [PATCH 6/7] versionnum++ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 185ee0ff9..83322d1e4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "rx", "title": "Reactive Extensions for JavaScript (RxJS)", "description": "Library for composing asynchronous and event-based operations in JavaScript", - "version": "2.1.6", + "version": "2.1.7", "homepage": "http://rx.codeplex.com", "author": { "name": "Cloud Programmability Team", From 1b74bfb8d51f6438f7caa3eab5f6b263c57250ef Mon Sep 17 00:00:00 2001 From: paul taylor Date: Mon, 29 Jul 2013 14:36:05 -0500 Subject: [PATCH 7/7] Upgrades html dom bindings without breaking backwards compatability with different naming. --- src/bridges/html/rx.html.js | 488 ++++++++++++++++++++++++++++++++ src/bridges/html/rx.html.old.js | 229 +++++++++++++++ 2 files changed, 717 insertions(+) create mode 100644 src/bridges/html/rx.html.js create mode 100644 src/bridges/html/rx.html.old.js diff --git a/src/bridges/html/rx.html.js b/src/bridges/html/rx.html.js new file mode 100644 index 000000000..7d7fc7532 --- /dev/null +++ b/src/bridges/html/rx.html.js @@ -0,0 +1,488 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + +(function (root, factory) { + var freeExports = typeof exports == 'object' && exports, + freeModule = typeof module == 'object' && module && module.exports == freeExports && module, + freeGlobal = typeof global == 'object' && global; + if (freeGlobal.global === freeGlobal) { + window = freeGlobal; + } + + // Because of build optimizers + if (typeof define === 'function' && define.amd) { + define(['rx', 'exports'], function (Rx, exports) { + root.Rx = factory(root, exports, Rx); + return root.Rx; + }); + } else if (typeof module === 'object' && module && module.exports === freeExports) { + module.exports = factory(root, module.exports, require('./rx')); + } else { + root.Rx = factory(root, {}, root.Rx); + } +}(this, function (global, exp, Rx, undefined) { + + var freeExports = typeof exports == 'object' && exports, + freeModule = typeof module == 'object' && module && module.exports == freeExports && module, + freeGlobal = typeof global == 'object' && global; + if (freeGlobal.global === freeGlobal) { + window = freeGlobal; + } + + var Rx = window.Rx, + Observable = Rx.Observable, + observableProto = Observable.prototype, + observableCreate = Observable.create, + observableCreateWithDisposable = Observable.createWithDisposable, + disposableCreate = Rx.Disposable.create, + CompositeDisposable = Rx.CompositeDisposable, + Subject = Rx.Subject, + Scheduler = Rx.Scheduler, + dom = Rx.DOM = {}, + ajax = Rx.DOM.Request = {}; + + /** @private + * Creates an event listener on a single element with compat back to DOM Level 1. + */ + function createListener (element, name, handler) { + // Standards compliant + if (element.addEventListener) { + element.addEventListener(name, handler, false); + return disposableCreate(function () { + element.removeEventListener(name, handler, false); + }); + } else if (element.attachEvent) { + // IE Specific + var innerHandler = function (event) { + event || (event = window.event); + event.target = event.target || event.srcElement; + handler(event); + }; + element.attachEvent('on' + name, innerHandler); + return disposableCreate(function () { + element.detachEvent('on' + name, innerHandler); + }); + } else { + // Level 1 DOM Events + var innerHandler = function (event) { + event || (event = window.event); + event.target = event.target || event.srcElement; + handler(event); + }; + element['on' + name] = innerHandler; + return disposableCreate(function () { + element['on' + name] = null; + }); + } + } + + /** @private + * Creates event listeners on either a single element or NodeList + */ + function createEventListener (el, eventName, handler) { + var disposables = new CompositeDisposable(); + + if ( el && el.nodeName || el === window ) { + disposables.add(createListener(el, eventName, handler)); + } else if ( el && el.length ) { + for (var i = 0, len = el.length; i < len; i++) { + disposables.add(createEventListener(el[i], eventName, handler)); + } + } + + return disposables; + } + + /** + * Creates an observable sequence by adding an event listener to the matching DOMElement or each item in the NodeList. + * + * @example + * source = Rx.DOM.fromEvent(element, 'mouseup'); + * + * @param {Object} element The DOMElement or NodeList to attach a listener. + * @param {String} eventName The event name to attach the observable sequence. + * @returns {Observable} An observable sequence of events from the specified element and the specified event. + */ + dom.fromEvent = function (element, eventName) { + return observableCreateWithDisposable(function (observer) { + return createEventListener(element, eventName, function handler (e) { observer.onNext(e); }); + }); + }; + + /* @private + * Gets the proper XMLHttpRequest for support for older IE + */ + function getXMLHttpRequest() { + if (global.XMLHttpRequest) { + return new global.XMLHttpRequest; + } else { + try { + return new global.ActiveXObject('Microsoft.XMLHTTP'); + } catch (e) { + throw new Error('XMLHttpRequest is not supported by your browser'); + } + } + } + + /** + * Creates a cold observable for an Ajax request with either a settings object with url, headers, etc or a string for a URL. + * + * @example + * source = Rx.DOM.Request.ajaxCold('/products'); + * source = Rx.DOM.Request.ajaxCold( url: 'products', method: 'GET' }); + * + * @param {Object} settings Can be one of the following: + * + * A string of the URL to make the Ajax call. + * An object with the following properties + * - url: URL of the request + * - method: Method of the request, such as GET, POST, PUT, PATCH, DELETE + * - async: Whether the request is async + * - headers: Optional headers + * + * @returns {Observable} An observable sequence containing the XMLHttpRequest. + */ + ajax.ajaxCold = function (settings) { + return observableCreateWithDisposable( function (observer) { + if (typeof settings === 'string') { + settings = { method: 'GET', url: settings, async: true }; + } + if (settings.async === undefined) { + settings.async = true; + } + + var xhr; + try { + xhr = getXMLHttpRequest(); + } catch (err) { + observer.onError(err); + } + + try { + if (settings.user) { + xhr.open(settings.method, settings.url, settings.async, settings.user, settings.password); + } else { + xhr.open(settings.method, settings.url, settings.async); + } + + if (settings.headers) { + var headers = settings.headers; + for (var header in headers) { + if (headers.hasOwnProperty(header)) { + xhr.setRequestHeader(header, headers[header]); + } + } + } + + xhr.onreadystatechange = xhr.onload = function () { + if (xhr.readyState === 4) { + var status = xhr.status; + if ((status >= 200 && status <= 300) || status === 0 || status === '') { + observer.onNext(xhr); + observer.onCompleted(); + } else { + observer.onError(xhr); + } + } + }; + + xhr.onerror = function () { + observer.onError(xhr); + }; + + xhr.send(settings.body || null); + } catch (e) { + observer.onError(e); + } + + return disposableCreate( function () { + if (xhr.readyState !== 4) { + xhr.abort(); + } + }); + }); + }; + + /** @private */ + var ajaxCold = ajax.ajaxCold; + + /** + * Creates a hot observable for an Ajax request with either a settings object with url, headers, etc or a string for a URL. + * + * @example + * source = Rx.DOM.Request.ajax('/products'); + * source = Rx.DOM.Request.ajax( url: 'products', method: 'GET' }); + * + * @param {Object} settings Can be one of the following: + * + * A string of the URL to make the Ajax call. + * An object with the following properties + * - url: URL of the request + * - method: Method of the request, such as GET, POST, PUT, PATCH, DELETE + * - async: Whether the request is async + * - headers: Optional headers + * + * @returns {Observable} An observable sequence containing the XMLHttpRequest. + */ + var observableAjax = ajax.ajax = function (settings) { + return ajaxCold(settings).publishLast().refCount(); + }; + + /** + * Creates an observable sequence from an Ajax POST Request with the body. + * + * @param {String} url The URL to POST + * @param {Object} body The body to POST + * @returns {Observable} The observable sequence which contains the response from the Ajax POST. + */ + ajax.post = function (url, body) { + return observableAjax({ url: url, body: body, method: 'POST', async: true }); + }; + + /** + * Creates an observable sequence from an Ajax GET Request with the body. + * + * @param {String} url The URL to GET + * @returns {Observable} The observable sequence which contains the response from the Ajax GET. + */ + var observableGet = ajax.get = function (url) { + return observableAjax({ url: url, method: 'GET', async: true }); + }; + + if (JSON && typeof JSON.parse === 'function') { + /** + * Creates an observable sequence from JSON from an Ajax request + * + * @param {String} url The URL to GET + * @returns {Observable} The observable sequence which contains the parsed JSON. + */ + ajax.getJSON = function (url) { + return observableGet(url).select(function (xhr) { + return JSON.parse(xhr.responseText); + }); + }; + } + + /** @private + * Destroys the current element + */ + var destroy = (function () { + var trash = document.createElement('div'); + return function (element) { + trash.appendChild(element); + trash.innerHTML = ''; + }; + })(); + + /** + * Creates a cold observable JSONP Request with the specified settings. + * + * @example + * source = Rx.DOM.Request.jsonpRequestCold('http://www.bing.com/?q=foo&JSONPRequest=?'); + * source = Rx.DOM.Request.jsonpRequestCold( url: 'http://bing.com/?q=foo', jsonp: 'JSONPRequest' }); + * + * @param {Object} settings Can be one of the following: + * + * A string of the URL to make the JSONP call with the JSONPCallback=? in the url. + * An object with the following properties + * - url: URL of the request + * - jsonp: The named callback parameter for the JSONP call + * + * @returns {Observable} A cold observable containing the results from the JSONP call. + */ + ajax.jsonpRequestCold = (function () { + var uniqueId = 0; + return function (settings) { + return Observable.createWithDisposable(function (observer) { + + if (typeof settings === 'string') { + settings = { url: settings } + } + if (!settings.jsonp) { + settings.jsonp = 'JSONPCallback'; + } + + var head = document.getElementsByTagName('head')[0] || document.documentElement, + tag = document.createElement('script'), + handler = 'rxjscallback' + uniqueId++; + + settings.url = settings.url.replace('=' + settings.jsonp, '=' + handler); + + window[handler] = function (data) { + observer.onNext(data); + observer.onCompleted(); + }; + + tag.src = settings.url; + tag.async = true; + tag.onload = tag.onreadystatechange = function (_, abort) { + if ( abort || !tag.readyState || /loaded|complete/.test(tag.readyState) ) { + tag.onload = tag.onreadystatechange = null; + if (head && tag.parentNode) { + destroy(tag); + } + tag = undefined; + window[handler] = undefined; + } + + }; + head.insertBefore(tag, head.firstChild); + + return disposableCreate(function () { + if (!tag) { + return; + } + tag.onload = tag.onreadystatechange = null; + if (head && tag.parentNode) { + destroy(tag); + } + tag = undefined; + window[handler] = undefined; + }); + }); + }; + + })(); + + /** @private */ + var getJSONPRequestCold = ajax.jsonpRequestCold; + + /** + * Creates a hot observable JSONP Request with the specified settings. + * + * @example + * source = Rx.DOM.Request.getJSONPRequest('http://www.bing.com/?q=foo&JSONPRequest=?'); + * source = Rx.DOM.Request.getJSONPRequest( url: 'http://bing.com/?q=foo', jsonp: 'JSONPRequest' }); + * + * @param {Object} settings Can be one of the following: + * + * A string of the URL to make the JSONP call with the JSONPCallback=? in the url. + * An object with the following properties + * - url: URL of the request + * - jsonp: The named callback parameter for the JSONP call + * + * @returns {Observable} A hot observable containing the results from the JSONP call. + */ + ajax.jsonpRequest = function (settings) { + return getJSONPRequestCold(settings).publishLast().refCount(); + }; + if (window.WebSocket) { + /** + * Creates a WebSocket Subject with a given URL, protocol and an optional observer for the open event. + * + * @example + * var socket = Rx.DOM.fromWebSocket('http://localhost:8080', 'stock-protocol', function(e) { ... }); + * var socket = Rx.DOM.fromWebSocket('http://localhost:8080', 'stock-protocol', observer); + *s + * @param {String} url The URL of the WebSocket. + * @param {String} protocol The protocol of the WebSocket. + * @param {Function|Observer} [observerOrOnNext] An optional Observer or onNext function to capture the open event. + * @returns {Subject} An observable sequence wrapping a WebSocket. + */ + dom.fromWebSocket = function (url, protocol, observerOrOnNext) { + var socket = new window.WebSocket(url, protocol); + + var observable = observableCreate(function (obs) { + if (observerOrOnNext) { + socket.onopen = function (openEvent) { + if (typeof observerOrOnNext === 'function') { + observerOrOnNext(openEvent); + } else if (observerOrOnNext.onNext) { + observerOrOnNext.onNext(openEvent); + } + }; + } + + socket.onmessage = function (data) { + obs.onNext(data); + }; + + socket.onerror = function (err) { + obs.onError(err); + }; + + socket.onclose = function () { + obs.onCompleted(); + }; + + return function () { + socket.close(); + }; + }); + + var observer = observerCreate(function (data) { + if (socket.readyState === WebSocket.OPEN) { + socket.send(data); + } + }); + + return Subject.create(observer, observable); + }; + } + + + if (window.Worker) { + /** + * Creates a Web Worker with a given URL as a Subject. + * + * @example + * var worker = Rx.DOM.fromWebWorker('worker.js'); + * + * @param {String} url The URL of the Web Worker. + * @returns {Subject} A Subject wrapping the Web Worker. + */ + dom.fromWebWorker = function (url) { + var worker = new window.Worker(url); + + var observable = observableCreateWithDisposable(function (obs) { + worker.onmessage = function (data) { + obs.onNext(data); + }; + + worker.onerror = function (err) { + obs.onError(err); + }; + + return disposableCreate(function () { + worker.close(); + }); + }); + + var observer = observerCreate(function (data) { + worker.postMessage(data); + }); + + return Subject.create(observer, observable); + }; + } + + if (window.MutationObserver) { + + /** + * Creates an observable sequence from a Mutation Observer. + * MutationObserver provides developers a way to react to changes in a DOM. + * @example + * Rx.DOM.fromMutationObserver(document.getElementById('foo'), { attributes: true, childList: true, characterData: true }); + * + * @param {Object} target The Node on which to obserave DOM mutations. + * @param {Object} options A MutationObserverInit object, specifies which DOM mutations should be reported. + * @returns {Observable} An observable sequence which contains mutations on the given DOM target. + */ + dom.fromMutationObserver = function (target, options) { + + return observableCreate(function (observer) { + var mutationObserver = new MutationObserver(function (mutations) { + observer.onNext(mutations); + }); + + mutationObserver.observe(target, options); + + return function () { + mutationObserver.disconnect(); + }; + }); + + }; + + } + return Rx; +})); \ No newline at end of file diff --git a/src/bridges/html/rx.html.old.js b/src/bridges/html/rx.html.old.js new file mode 100644 index 000000000..1e7d4596c --- /dev/null +++ b/src/bridges/html/rx.html.old.js @@ -0,0 +1,229 @@ +/** +* Copyright 2011 Microsoft Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +(function (root, factory) { + var freeExports = typeof exports == 'object' && exports && + (typeof root == 'object' && root && root == root.global && (window = root), exports); + + // Because of build optimizers + if (typeof define === 'function' && define.amd) { + define(['rx', 'exports'], function (Rx, exports) { + root.Rx = factory(root, exports, Rx); + return root.Rx; + }); + } else if (typeof module == 'object' && module && module.exports == freeExports) { + var rxroot = factory(root, module.exports, require('rx')); + module.exports = rxroot.Rx; + } else { + root.Rx = factory(root, {}, root.Rx); + } +}(this, function (global, undefined) { + var freeExports = typeof exports == 'object' && exports && + (typeof global == 'object' && global && global == global.global && (window = global), exports); + + var root = global.Rx, + Observable = root.Observable, + observableProto = Observable.prototype, + observableCreateWithDisposable = Observable.createWithDisposable, + disposableCreate = root.Disposable.create, + CompositeDisposable = root.CompositeDisposable, + RefCountDisposable = root.RefCountDisposable, + AsyncSubject = root.AsyncSubject; + + var createEventListener = function (el, eventName, handler) { + var disposables = new CompositeDisposable(), + + createListener = function (element, eventName, handler) { + if (element.addEventListener) { + element.addEventListener(eventName, handler, false); + return disposableCreate(function () { + element.removeEventListener(eventName, handler, false); + }); + } else if (element.attachEvent) { + element.attachEvent('on' + eventName, handler); + return disposableCreate(function () { + element.detachEvent('on' + eventName, handler); + }); + } else { + element['on' + eventName] = handler; + return disposableCreate(function () { + element['on' + eventName] = null; + }); + } + }; + + if ( el && el.nodeName || el === global ) { + disposables.add(createListener(el, eventName, handler)); + } else if ( el && el.length ) { + for (var i = 0, len = el.length; i < len; i++) { + disposables.add(createEventListener(el[i], eventName, handler)); + } + } + + return disposables; + }; + + Observable.fromEvent = function (element, eventName) { + return observableCreateWithDisposable(function (observer) { + var handler = function (e) { + observer.onNext(e); + }; + return createEventListener(element, eventName, handler); + }); + }; + + var destroy = (function () { + var trash = document.createElement('div'); + return function (element) { + trash.appendChild(element); + trash.innerHTML = ''; + }; + })(); + + + Observable.getJSONPRequest = (function () { + var uniqueId = 0; + return function (url) { + var subject = new AsyncSubject(), + head = document.getElementsByTagName('head')[0] || document.documentElement, + tag = document.createElement('script'), + handler = 'rxjscallback' + uniqueId++, + url = url.replace('=JSONPCallback', '=' + handler); + + global[handler] = function (data) { + subject.onNext(data); + subject.onCompleted(); + }; + + tag.src = url; + tag.async = true; + tag.onload = tag.onreadystatechange = function (_, abort) { + if ( abort || !tag.readyState || /loaded|complete/.test(tag.readyState) ) { + tag.onload = tag.onreadystatechange = null; + if (head && tag.parentNode) { + destroy(tag); + } + tag = undefined; + delete global[handler]; + } + + }; + head.insertBefore(tag, head.firstChild ); + var refCount = new RefCountDisposable(disposableCreate( function () { + if (!/loaded|complete/.test(tag.readyState)) { + tag.abort(); + tag.onload = tag.onreadystatechange = null; + if (head && tag.parentNode) { + destroy(tag); + } + tag = undefined; + delete global[handler]; + subject.onError(new Error('The script has been aborted')); + } + })); + + return observableCreateWithDisposable( function (subscriber) { + return new CompositeDisposable(subject.subscribe(subscriber), refCount.getDisposable()); + }); + }; + + })(); + + + + function getXMLHttpRequest() { + if (global.XMLHttpRequest) { + return new global.XMLHttpRequest; + } else { + try { + return new global.ActiveXObject('Microsoft.XMLHTTP'); + } catch (e) { + throw new Error('XMLHttpRequest is not supported by your browser'); + } + } + } + + var observableAjax = Observable.ajax = function (settings) { + if (typeof settings === 'string') { + settings = { method: 'GET', url: settings, async: true }; + } + if (settings.async === undefined) { + settings.async = true; + } + var subject = new AsyncSubject(), + xhr = getXMLHttpRequest(); + + if (settings.headers) { + var headers = settings.headers, header; + for (header in headers) { + xhr.setRequestHeader(header, headers[header]); + } + } + try { + if (details.user) { + xhr.open(settings.method, settings.url, settings.async, settings.user, settings.password); + } else { + xhr.open(settings.method, settings.url, settings.async); + } + xhr.onreadystatechange = xhr.onload = function () { + if (xhr.readyState === 4) { + var status = xhr.status; + if ((status >= 200 && status <= 300) || status === 0 || status === '') { + subject.onNext(xhr); + subject.onCompleted(); + } else { + subject.onError(xhr); + } + } + }; + xhr.onerror = xhr.onabort = function () { + subject.onError(xhr); + }; + xhr.send(settings.body || null); + } catch (e) { + subject.onError(e); + } + + var refCount = new RefCountDisposable(disposableCreate( function () { + if (xhr.readyState !== 4) { + xhr.abort(); + subject.onError(xhr); + } + })); + + return observableCreateWithDisposable( function (subscriber) { + return new CompositeDisposable(subject.subscribe(subscriber), refCount.getDisposable()); + }); + }; + + Observable.post = function (url, body) { + return observableAjax({ url: url, body: body, method: 'POST', async: true }); + }; + + var observableGet = Observable.get = function (url) { + return observableAjax({ url: url, method: 'GET', async: true }); + }; + + if (JSON && JSON.parse) { + Observable.getJSON = function (url) { + return observableGet(url).select(function (xhr) { + return JSON.parse(xhr.responseText); + }); + }; + } + + return root; + +})); \ No newline at end of file