Joining Paths Asynchronously

Join multiple paths asynchronously on a user ID in Firebase

by Kato Richardson

HTML

<script src="http://static.firebase.com/v0/firebase.js"></script>
    <h3>Joining Paths Asynchronously</h3>
<div class="left">
    <h5>Select a user</h5>
    <ul></ul>
</div>
<div class="right">
    <h5>Merged Record</h5>
    <pre>Pick a user...</pre>
</div>

CSS

div {
   float: left;
}

div.left { width: 30%; }
div.right { width: 69%; }

pre {
    padding: 20px;
    font-size: 20px;
    border-radius: 15px;
    background-color: #fafafa;
    border: 1px solid #999;
    font-family:'Courier New', 'Courier', monospaced;
}

h3 {
    clear: both;
    padding-top: 20px;
}

JavaScript

var fb = new Firebase("https://examples-sql-queries.firebaseio.com/");
listUsers();

function joinPaths(id, paths, callback) {
    var returnCount = 0;
    var expectedCount = paths.length;
    var mergedObject = {};

    paths.forEach(function (p) {
        fb.child(p + '/' + id).once('value',
            // success
            function (snap) {
                // add it to the merged data
                extend(mergedObject, snap.val());
                
                // when all paths have resolved, we invoke
                // the callback (jQuery.when would be handy here)
                if (++returnCount === expectedCount) {
                    callback(null, mergedObject);
                }
            },
            // error
            function (error) {
                returnCount = expectedCount + 1; // abort counters
                callback(error, null);
            }
        );
    });
}

function loadUser(userId) {
    joinPaths(userId, ['user', 'media', 'interests'], show);
}

function show(error, obj) {
    $('pre').text(error || JSON.stringify(obj, null, 2));
}

function listUsers() {
    var $ul = $('ul');
    fb.child('user').on('child_added', function (snap) {
        $('<li></li>').appendTo($ul).append( buildLink(snap) );
    });
}

function buildLink(snap) {
    return $('<a href="#"></a>')
        .attr('data-id', snap.name())
        .text(snap.val().name)
        .on('click', function(e) {
            e.preventDefault();
            loadUser($(this).attr('data-id'));
        });
}

function extend(base) {
    var parts = Array.prototype.slice.call(arguments, 1);
    parts.forEach(function (p) {
        if (p && typeof (p) === 'object') {
            for (var k in p) {
                if (p.hasOwnProperty(k)) {
                    base[k] = p[k];
                }
            }
        }
    });
    return base;
}