Intro to Rx

An example of a "Who to follow" suggestions box built with the Reactive Programming library RxJS.

by wojtiku

HTML

<div class="container">
    <div class="header">
         <h2>Who to follow</h2><a href="#" class="refresh">Refresh</a>
    </div>
    <ul class="suggestions">
        <li class="suggestion1">
            <img />
            <a href="#" target="_blank" class="username">this will not be displayed</a>
            <a href="#" class="close close1">x</a>
        </li>
        <li class="suggestion2">
            <img />
            <a href="#" target="_blank" class="username">neither this</a>
            <a href="#" class="close close2">x</a>
        </li>
        <li class="suggestion3">
            <img />
            <a href="#" target="_blank" class="username">nor this</a>
            <a href="#" class="close close3">x</a>
        </li>
    </ul>
</div>
<script src="http://cdnjs.cloudflare.com/ajax/libs/rxjs/2.2.26/rx.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/rxjs/2.2.26/rx.async.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/rxjs/2.2.26/rx.coincidence.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/rxjs/2.2.26/rx.binding.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/rxjs/2.2.26/rx.time.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/rxjs-dom/2.0.7/rx.dom.js"></script>

CSS

body {
    font-family: sans-serif;
    padding: 10px;
}
h2 {
    font-weight: bold;
    display: inline-block;
}
.refresh {
    font-size: 80%;
    margin-left: 10px;
}
.header {
    background: #ECECEC;
    padding: 5px;
}
.suggestions {
    border: 2px solid #ECECEC;
}
li {
    padding: 5px;
}
li img {
    width: 40px;
    height: 40px;
    border-radius: 20px;
}
li .username, li .close {
    display: inline-block;
    position: relative;
    bottom: 15px;
    left: 5px;
}

JavaScript

var refreshButton = document.querySelector('.refresh');
var closeButtonsStream = Rx.Observable.fromArray([
    document.querySelector('.close1'),
    document.querySelector('.close2'),
    document.querySelector('.close3')
]);


var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');
var closeButtonsClickStream = closeButtonsStream.flatMap(function (closeButtonElement) {
    return Rx.Observable.fromEvent(closeButtonElement, 'click');
});

var requestStream = refreshClickStream.startWith('startup click')
    .map(function() {
        var randomOffset = Math.floor(Math.random()*500);
        return 'https://api.github.com/users?since=' + randomOffset;
    });

var responseStream = requestStream
    .flatMap(function (requestUrl) {
        return Rx.Observable.fromPromise($.getJSON(requestUrl));
    });


var suggestionStream = closeButtonsClickStream.startWith('startup click')
        .combineLatest(
            responseStream,             
            function(click, listUsers) {
                return {
                    click: click,
                    user: listUsers[Math.floor(Math.random()*listUsers.length)]
                }
            }
        )
        .merge(
            refreshClickStream.map(function(){ 
                return null;
            })
        )
        .startWith(null);


// Rendering ---------------------------------------------------
suggestionStream.filter(existing).subscribe(function (obj) {
    console.log(obj);
    renderSuggestion(suggestedUser, '.suggestion1');
});
suggestionStream.filter(nulls).subscribe(function () {
    hideSuggestion('.suggestion1');
});

function existing(x) { return x != null}
function nulls(x) { return x == null}

function renderSuggestion(suggestedUser, selector) {
    var suggestionEl = document.querySelector(selector);
    suggestionEl.style.visibility = 'visible';
    var usernameEl = suggestionEl.querySelector('.username');
    usernameEl.href = suggestedUser.html_url;
   ...