Giter Site home page Giter Site logo

rxjs-dom's Introduction

Build Status GitHub version NPM version Downloads NuGet Built with Grunt

RxJS-DOM 7.0 - HTML DOM Bindings for the Reactive Extensions for JavaScript

OVERVIEW

This project provides Reactive Extensions for JavaScript (RxJS) bindings for HTML DOM objects to abstract over the event binding, Ajax requests, Web Sockets, Web Workers, Server-Sent Events, Geolocation and more.

Batteries Included

Sure, there are a lot of libraries to get started with the RxJS bindings for the HTML DOM. Confused on where to get started? Start out with the complete set of functionality with rx.dom.js, then you can reduce it to the functionality you require such as only events, or ajax. If you use RxJS Lite, you can start with the rx.lite.dom.js file and then select the functionality you want from there.

This set of libraries include:

Main Libraries:

Lite Libraries:

GETTING STARTED

There are a number of ways to get started with the HTML DOM Bindings for RxJS. The files are available on cdnjs and jsDelivr.

Download the Source

To download the source of the HTML DOM Bindings for the Reactive Extensions for JavaScript, type in the following:

git clone https://github.com/Reactive-Extensions/rxjs-dom.git
cd ./rxjs-dom

Installing with NPM

npm install rx-dom

Installing with Bower

bower install rx-dom

Installing with Jam

jam install rx-dom

Installing with NuGet

PM> Install-Package RxJS-Bridges-HTML

Getting Started with the HTML DOM Bindings

Let's walk through a simple yet powerful example of the Reactive Extensions for JavaScript Bindings for HTML, autocomplete. In this example, we will take user input from a textbox and trim and throttle the input so that we're not overloading the server with requests for suggestions.

We'll start out with a basic skeleton for our application with script references to RxJS Lite based methods, and the RxJS Bindings for HTML DOM, along with a textbox for input and a list for our results.

<script type="text/javascript" src="rx.lite.js"></script>
<script type="text/javascript" src="rx.dom.js"></script>
<script type="text/javascript">

</script>
...
<input id="textInput" type="text"></input>
<ul id="results"></ul>
...

The goal here is to take the input from our textbox and debounce it in a way that it doesn't overload the service with requests. To do that, we'll get the reference to the textInput using the document.getElementById method, then bind to the 'keyup' event using the Rx.DOM.fromEvent specialization shortcut for keyups called Rx.DOM.keyup which then takes the DOM element event handler and transforms it into an RxJS Observable.

var textInput = document.querySelector('#textInput');
var throttledInput = Rx.DOM.keyup(textInput);

Since we're only interested in the text, we'll use the map method to take the event object and return the target's value, or we can call pluck to the same effect.

	.pluck('target','value')

We're also not interested in query terms less than two letters, so we'll trim that user input by using the where or filter method returning whether the string length is appropriate.

	.filter( function (text) {
		return text.length > 2;
	})

We also want to slow down the user input a little bit so that the external service won't be flooded with requests. To do that, we'll use the debounce method with a timeout of 500 milliseconds, which will ignore your fast typing and only return a value after you have paused for that time span.

	.debounce(500)

Lastly, we only want distinct values in our input stream, so we can ignore requests that are not unique, for example if I copy and paste the same value twice, the request will be ignored using the distinctUntilChanged method.

	.distinctUntilChanged();

Putting it all together, our throttledInput looks like the following:

var textInput = document.querySelector('#textInput');
var throttledInput = Rx.DOM.keyup(textInput)
	.pluck('target','value')
	.filter( function (text) {
		return text.length > 2;
	})
	.debounce(500)
	.distinctUntilChanged();

Now that we have the throttled input from the textbox, we need to query our service, in this case, the Wikipedia API, for suggestions based upon our input. To do this, we'll create a function called searchWikipedia which calls the Rx.DOM.jsonpRequest method which wraps making a JSONP call.

function searchWikipedia(term) {
  var url = 'http://en.wikipedia.org/w/api.php?action=opensearch&format=json&search='
    + encodeURIComponent(term) + '&callback=JSONPCallback';
  return Rx.DOM.jsonpRequest(url);
}

Now that the Wikipedia Search has been wrapped, we can tie together throttled input and our service call. In this case, we will call select on the throttledInput to then take the text from our textInput and then use it to query Wikipedia, filtering out empty records. Finally, to deal with concurrency issues, we'll need to ensure we're getting only the latest value. Issues can arise with asynchronous programming where an earlier value, if not cancelled properly, can be returned before the latest value is returned, thus causing bugs. To ensure that this doesn't happen, we have the flatMapLatest method which returns only the latest value.

var suggestions = throttledInput.flatMapLatest(searchWikipedia);

Finally, we'll subscribe to our observable by calling subscribe which will receive the results and put them into an unordered list. We'll also handle errors, for example if the server is unavailable by passing in a second function which handles the errors.

var resultList = document.getElementById('results');

function clearSelector (element) {
  while (element.firstChild) {
    element.removeChild(element.firstChild);
  }
}

function createLineItem(text) {
	var li = document.createElement('li');
	li.innerHTML = text;
	return li;
}

suggestions.subscribe(
	function (data) {
	  var results = data.response[1];

	  clearSelector(resultList);

	  for (var i = 0; i < results.length; i++) {
	    resultList.appendChild(createLineItem(results[i]));
	  }
	},
	function (e) {
		clearSelector(resultList);
  	resultList.appendChild(createLineItem('Error: ' + e));
	}
);

We've only scratched the surface of this library in this simple example.

Dive In!

Please check out:

LICENSE

Copyright Microsoft

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.

rxjs-dom's People

Contributors

arthurgreef avatar benlesh avatar bumblehead avatar crissdev avatar fudini avatar guersam avatar hashplus avatar jeffbcross avatar jmalonzo avatar jzvandenoever avatar kuychaco avatar lozandier avatar lustdante avatar maks3w avatar mattpodwysocki avatar michl86 avatar miwillhite avatar neurostep avatar ntkog avatar paulpdaniels avatar petrsnobelt avatar qyuz avatar redetection avatar rudijs avatar sergi avatar sirbarrence avatar skoppe avatar tomchentw avatar trxcllnt avatar whiteinge avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rxjs-dom's Issues

Ajax cross-domain requests does not set 'with credentials'

Creating an Ajax request with the option crossDomain set to true does not set withCredentials.
Furthermore, there is no option to manually change this option.

Is this expected? I have overcome this issue by updating the source and adding a new line in getCORSRequest():

if ('withCredentials' in xhr) {
  return xhr;
}

is now:

if ('withCredentials' in xhr) {
  xhr.withCredentials = true;
  return xhr;
}

Any help/advice would be appreciated.

Rx.DOM.ready() seems to not work.

according to the MDN documentation, when document.readyState === 'interactive' then the DOMContentLoaded has already fired.

The Rx.DOM.ready() observable checks to see if the readyState === 'complete', otherwise it listens for the DOMContentLoaded event. This seems incorrect to me, as the observable will be forever empty if the DOMContentLoaded event has already fired, and the readyState === 'interactive'.

Browserify support

Currently, rx supports Browserify, but rx-dom does not.

Uncaught TypeError: Cannot read property 'Observable' of undefined 

For the following code:

var Rx = require('rx'),
    Rx = require('rx-dom');

Or:

var Rx = require('rx');
require('rx-dom');

Rx.DOM.Request.jsonpRequest documentation does not reflect current code.

When providing a settings object the jsonp field does not do what the documentation claims it does. The current documentation, both in the source and the documentation .md, say this

  • jsonp (String): The named callback parameter for the JSONP call

And provide the following example (source file only):

source = Rx.DOM.jsonpRequest( url: 'http://bing.com/?q=foo', jsonp: 'JSONPRequest' });

This implies that it gives the name of the parameter whose value should be changed to the actual callback function generated for the request. E.g. 'jsonp.request.json?whatCallbackToCallForRequest=callbackFunction' would be changed to 'jsonp.request.json?whatCallbackToCallForRequest=rxjscallback{number}'
At least if settings.jsonp === 'whatCallbackToCallForRequest'

However the actual behaviour is 'jsonp.request.json?whatCallbackToCallForRequest=callbackFunction' is changed to 'jsonp.request.json?whatCallbackToCallForRequest=rxjscallback{number}' only if settings.jsonp === 'callbackFunction'

Thus jsonp refers to the named callback parameter value for the JSONP call.

I'm willing to provide a fix for this but need to know if the code or the documentation is correct.

Normalize ajax load event issue

It looks like the normalization process here is not correct in case of "requestType": "json" at least. I've just tried to use Rx.DOM.getJSON from the latest release and got an issue: in response I've got a string instead of JS object (parsed string with JSON.parse).

sinon fake XMLHttpRequest hides this problem by using only responseText instead of both response & responseText fields.

My suggestion is to update the normalizeAjaxLoadEvent function like this:

function normalizeAjaxLoadEvent(e, xhr, settings) {
    var response = ('response' in xhr) ? xhr.response : xhr.responseText;
    response = settings.responseType === 'json' ? JSON.parse(response) : response;
    return {
      response: response,
      status: xhr.status,
      responseType: xhr.responseType,
      xhr: xhr,
      originalEvent: e
    };
  }

Thank you.

Subject returned by fromWebSocket no longer returns a disposable when subscribed to.

var socketSubject = Rx.DOM.fromWebSocket('ws://echo.websocket.org');

var disposable = socketSubject.subscribe(x => console.log(x));

console.log(disposable); // undefined

This seems to have been broken for a while, and related to a change I have yet to identify in RxJS proper. I didn't notice this until I updated RxJS in my project.

CDN out of date

cdnjs and jsdelivr both have old versions, is someone working on it? If not happy to try and get them updated?

Ajax defaulting to stringifying all bodies that are typeof === 'object'

The problem here it's not always desirable to stringify anything that is of type "object". Blobs for example test positive for typeof someBlob === 'object', but you can xhr.send(someBlob) directly.

In other words, the current implementation doesn't allow the developer to send:

  • ArrayBufferView
  • Blob
  • Document
  • FormData

which are all legitimate arguments for xhr.send(x);

Add URL observable

Hey,
would it be possible to add an observable that tracks the URL in the address bar?

jsonpRequest does't behave like a cold Observable

When subscribing more than one Observer to a jsonpRequest Observable, only one of the subscribers will be called back. In the example below only A will be ever logged, for example.

var test = Rx.Observable
  .interval(1000)
  .flatMap(function() {
    return Rx.DOM.jsonpRequest({
      url: https://gist.githubusercontent.com/sergi/8218eadccc167961ba1a/raw/9b23eaf092ca5c7aec65b5b93e1facc5ac1628cf/test_rx_jsonp.json,
      jsonpCallback: 'test_rx_jsonp'
    });
  });

var A = test.subscribe(function(res) { console.log('A', res) });
var B = test.subscribe(function(res) { console.log('B', res) });

A workaround is to use publish on the Observable, but it shouldn't be necessary.

WebSocket subject onCompleted() and onError() functionality

  • socketSubject.onCompleted() should simply call .close() on the socket.
  • socketSubject.onError(x) should call .close(code, reason) on the socket, provided that a reason and code were passed to the onError(x) call (e.g. onError('reason') or onError({ reason: 'reason', code: 3001 }).

Reusing jsonpRequest settings objects multiple times results errors

When doing a jsonpRequest using a settings object as a parameter you can't reuse that settings object or you get a TypeError. Because the url has been changed. And the proper new callback won't be set.

Thus it tries to call a function on the success of the result that is no longer there if one of the previous requests has already been made.

This is undesired behaviour because you might be polling dynamic data that can change over time. Like when requesting real time data and keeping it updated.

This is related to #45 because if the code is incorrect then the fix is solving that issue.

AJAX improvements

Wishlist:

  • Add method: post_json(url, json)
  • Add urlparams: get(url, urlparams)
  • Add urlparams: getJson(url, urlparams)

Even better if urlparams could be either an object value or an Rx subject itself.

Support CORS

Need to support CORS for Ajax requests. See here for implementation details.

Success on error condition

When testing a very basic error condition, the subscription's success handler is called. Expecting error handler to be called in this case.

image

IE8 support is broken

First, IE8 doesn't support document.createEvent which is used in src/events/addeventlistenerpolyfill.js. The last working version is 6.0.0.

Second, requests via Rx.DOM.get never complete in IE8. Last working version for this is (ironically) 4.0.4. Here's a minimal example to demonstrate:

function logStart() {
    console.log('start');
}

function logEnd(data) {
    console.log('end');
}

var requests = Rx.Observable.just(1);
var responses = requests.flatMap(function() {
    return Rx.DOM.get('.');
});

requests.subscribe(logStart);
responses.subscribe(logEnd);

It should print

start
end

But in IE8 with any version of RxJS-DOM later than 4.0.4 it only prints start.

PhantomJS Bug and RxDom.DOM.ajax() Basic Authorization

This is all due to some bug in PhantomJS.

What happens is that PhantomJS strips the Authorization Header.

To fix, change you call to ajax() from:

RxDom.DOM.ajax({url:"http://example.com",user:"noob",password:"123456",headers:{"Authorization":api.auth}});

to:

RxDom.DOM.ajax({url:"http://example.com",headers:{"Authorization":"Basic "+btoa("noob:123456")}});

Watchout, btoa is not supported on <= IE 9. Use some other base64 encoding script in case you need IE9 support.

If RxJS-DOM could detect Phantom-JS environment it might auto-apply this workaround.

rx.dom.js 4.0

The following are items for rx.dom.js 4.0:

  • Removal of cold versus hot observables for ajax requests and JSONP
  • Re-adding fromEvent to deal with DOM only events
  • Adding shortcuts for the following events:
    • blur
    • focus
    • focusin
    • focusout
    • load
    • resize
    • scroll
    • unload
    • click
    • dblclick
    • mousedown
    • mouseup
    • mousemove
    • mouseover
    • mouseout
    • mouseenter
    • mouseleave
    • change
    • select
    • submit
    • keydown
    • keypress
    • keyup
    • error
    • contextmenu

In addition, we would support pointer events if they exist on the platform such as:

  • pointerdown
  • pointerup
  • pointermove
  • pointerover
  • pointerout
  • pointerenter
  • pointerleave

Implicit dependency on global Rx in 2.0.8

Using browserify & npm to include rx-dom doesn't work unless Rx is defined on window.

var Rx = require('rx');
require('rx-dom');

Results in Uncaught TypeError: Cannot read property 'Observable' of undefined on this line:

https://github.com/Reactive-Extensions/RxJS-DOM/blob/v2.0.8/rx.dom.js#L31

Looks like it's fixed in master though:

https://github.com/Reactive-Extensions/RxJS-DOM/blob/master/rx.dom.js#L36

For now, this is my workaround:

window.Rx = require('rx');
require('rx-dom');

Rx-DOM not found within window.Rx

I've simply put my scripts like this:

<script src='/scripts/rx.all.min.js'></script>
<script src='/scripts/rx.dom.min.js'></script>

but it looks like rx.dom does not correctly export itself to pollute the window.Rx object.

Subscribing to the result of a `jsonpRequest` call doesn't work

First of all, the README appears to be wrong or out of date in that it gives the syntax as Rx.DOM.Ajax.jsonpRequest(url) (there is no Ajax property in DOM, I assume that should be Request). But even when I do this:

var url = 'http://en.wikipedia.org/w/api.php?action=opensearch&format=json&search=reactive';

Rx.DOM.Request.jsonpRequest(url).subscribe( 
  function(data) {
    console.log('result', data);
  },
  function(error) {
    console.log('error', error);
  }
);

literally nothing happens. Neither function passed to subscribe gets invoked, and no errors show up in the console. I can see from the network traffic that the request succeeds with Wikipedia and it returns valid JSON.

I'm using the rx-dom 2.0.8.

webpack loading problems

Trying to bundle with webpack, the AMD loader chokes on requiring './rx', because there's no file rx.js relative to rx.dom.js. This can be worked around with require('imports?define=>false!rx-dom') to disable the AMD loader, but it's clunky.

I note that rx-jquery works because it requires 'rx', not './rx'. Maybe rx-dom should do the same?

Rx.DOM.ajax -- add support for timeout

Add support for setting timeout on an AJAX request ( e.g. Rx.DOM.ajax({ url: '/unresponsive/endpoint', timeout: 10000 }) ). Trigger subscription's onError() when timed out.

getJSONPRequest doesn't seem to work

This doesn't invoke the subscribe call:

    Rx.Observable
        .getJSONPRequest("https://api.github.com/users/cburgdorf/received_events")
        .subscribe(function(data){
            console.log(data);
        });

This does:

   $.ajaxAsObservable({
        url: "https://api.github.com/users/cburgdorf/received_events",
        dataType: "jsonp"
    })
    .subscribe(function(payload){
        console.log(payload.data.data);
    });

Splitting RxJS-DOM

RxJS-DOM can be easily partitioned into sub-libraries, since these partitions seem to be orthogonal with regard to functionality. Partitions being

  • DOM Events: fromEvent, ready, event shortcuts, pointer events, touch events.
  • Ajax
  • Server-Sent Events
  • Web Sockets
  • Web Workers
  • Mutation Observers
  • Geolocation
  • Schedulers
  • FileReader

These could be respectively split into:

  • rx.dom.events.js
  • rx.dom.ajax.js
  • rx.dom.sse.js
  • rx.dom.websockets.js
  • rx.dom.webworkers.js
  • rx.dom.mutations.js
  • rx.dom.geolocation.js
  • rx.dom.schedulers.js
  • rx.dom.filereader.js

The advantage of splitting is we can use only a subset of RxJS-DOM while not having to carry all the load of the whole library. For instance, for frameworks which already handle DOM events, like Cycle.js (and others, this isn't the only one), the DOM Events part of RxJS-DOM is not necessary, and we might want to take only the Ajax helpers.

I can help with this issue.

Add speech observables

RxJS is a nice fit, particularly for the SpeechRecognition api.

Here's a fun demo of both speech recognition and synthesis with RxJS (if you have your speakers on, it "hears" itself, LOL): http://jsbin.com/nunije/1/edit?js,output

The SpeechRecognition bit looks like this:

var SpeechRecognition = root.SpeechRecognition ||
                          root.webkitSpeechRecognition ||
                          root.mozSpeechRecognition ||
                          root.msSpeechRecognition ||
                          root.oSpeechRecognition;


function fromSpeechRecognition(options) {
  var config = extend({
    continuous: false,
    maxAlternatives: 5,
    lang: 'en-US'
  }, options);

  return Observable.create((obs) => {
    if(!SpeechRecognition) {
      throw new Error('speech recognition not supported');
    }

    var recognition = new SpeechRecognition();
    recognition.continuous = config.continuous;

    recognition.onresult = (e) => {
      obs.onNext(e);
      //obs.onCompleted();
    };

    recognition.onerror = (e) => {
      obs.onError(e);
    };

    recognition.onend = (e) => {
      console.log(e);
      obs.onCompleted();
    };

    recognition.start();

    return () => {
      recognition.stop();
    };
  });
}

And the speech synthesis looks like this:

function fromSpeechUtterance(text) {
  return Observable.create((obs) => {
    var msg = new SpeechSynthesisUtterance(text);

    speechSynthesis.speak(msg);

    msg.onend = (e) => {
      obs.onNext(msg);
      obs.onCompleted();
    }
  });
}

Ajax 'body' content is not formatted correctly

Per the docs, an Ajax operator's settings should allow for its 'body' property to be an object.

body (Object): Optional body

However, this appears to be incorrect as only strings are supported. My code is as follows:

Rx.DOM.ajax({
    body: {
        password: password,
        username: username
    },
    crossDomain: true,
    headers: {
        'content-type': 'application/x-www-form-urlencoded'
    },
    method: 'POST',
    responseType: 'json',
    url: url
})

Expected outcome:

Form data:
  username: 'username string',
  password: 'password string

Actual outcome:

Form data:
  [object Object]

If I modify the code to body's value to be like below, it will work.

body: 'username=username&password=password'

WebSocket needs hook for before close is called (before disposed)

The use case here is this:

If I have a websocket that is using publish().refCount(), and I'm buffering messages to be sent over the socket, and I need to flush that buffer before I tell the socket to close, I'm currently unable to do so, because onCompleted is called when the socket has been closed, and there are no hooks to trigger it before disposal.

WebSocket implementation unable to send Close Event via onCompleted()

So RxJS doesn't allow anything to be passed with it's completion event.

This is problematic for fromWebSocket because it means that close events are squelched and cannot be examined. In particular when looking to see if the socket was closed cleanly.

This means that either:

  1. We need to add yet another optional observer to the method to emit a stream of close events.
  2. RxJS onCompleted should really allow a parameter to be passed. (I'm sure there's some super-nerdy computer-sciencey reason involving terms like "monads" why this isn't a good idea that I probably won't agree with)
  3. or the socket implementation needs to be changed to be a stream of socket events in total, rather than just messages: .... { type: 'open', event: e }... { type: 'message', event: e }....... { type: 'close', event: e }.... That could then be a new class of SocketSubject that has methods on it to filter and map out those events for you.

Add more events

I noticed that RxJS-DOM does not support all (standard) events.

Is there a reason for that or is it just a matter of writing up the corresponding documentation?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.