End-to-End Hypermedia: Building the Client

Now that we’ve built our hypermedia-rich server component, we can focus now on the client portion. Building the server piece, while somewhat involved, isn’t too much different than building a plain JSON API. We had to build a piece to translate our model to a richer hypermedia model, but I don’t see this as much different than what I have to do with views/templates.

The client is a completely different story. My payloads coming back from the server have a lot of metadata in them, and in order to achieve the loose coupling that REST can provide, my clients have to be driven off of that metadata.

Ultimately, my clients can’t be completely agnostic of what we’re doing on the server. A general-purpose client for a standard hypermedia format will look generic and boring. We’re not trying to build a generic client, we’re building one for a specific purpose.

In my case, I’d like to build a basic navigable details screen. Show a table of information, and supply links to drill down into further details.

First, I’ll need to have some understanding of what the server response looks like. I know I’ll have a list of instructors, then a list of courses, then a list of students attending those courses. Each of these will be a table, but I want a place to put that table on the screen. For this, I’ll create divs with IDs that match the RELs of the links in my collection+json response:

<div id="instructors"></div>
<div id="courses"></div>
<div id="students"></div>

When the page loads, I want to kick off the rendering with an initial request:

$(function () {
    fetchCollection('/instructors', 'instructors');
});

Nothing much going on here, I just call a method “fetchCollection”. I pass in the initial URL to hit, and the ID of DIV. The fetchCollection method:

var fetchCollection = function(relativeUrl, target) {
    $.getJSON(root + relativeUrl)
        .done(function (data) {
            renderCollectionJsonTable(data, target);
        })
        .fail(function (jqxhr, textStatus, error) {
            var err = textStatus + ", " + error;
            console.log("Request Failed: " + err);
        });
};

Will make a call to the API, return the result and render the result to the target DIV via the “renderCollectionJsonTable” method:

var renderCollectionJsonTable = function(data, target) {
    var $target = $('#' + target);
    $target.html('');

    var $table = $('<table>').addClass('table');
    var hasAddedHeader = false;
    var hasLinks = false;

    $.each(data.collection.items, function (idx, item) {

        if (item.links) {
            hasLinks = true;
        }

        if (!hasAddedHeader) {
            var $headerRow = $('<tr>');
            $.each(item.data, function(idx2, datum) {
                $('<th>').text(datum.prompt).appendTo($headerRow);
            });
            if (hasLinks) {
                $('<th>').appendTo($headerRow);
            }
            $headerRow.appendTo($table);
            hasAddedHeader = true;
        }

        var row = $('<tr>');
        $.each(item.data, function (idx2, datum) {
            var $cell = $('<td>');
            if (datum.value) {
                $cell.text(datum.value);
            }
            $cell.appendTo(row);
        });

        if (hasLinks) {
            var $linkCell = $('<td>');
            $.each(item.links, function (idx2, link) {
                var $link = $('<a>').attr('rel', link.rel).attr('href', '#').text(link.prompt + ' ');
                $link.click(function () {
                    fetchCollection(link.href, link.rel);
                    return false;
                });
                $link.appendTo($linkCell);
            });
            $linkCell.appendTo(row);
        }

        row.appendTo($table);
    });

    $table.appendTo($target);
};

This method is…quite involved. We’re trying to render a table, including the header and links. To render the header, I loop through the collection+json data elements and pluck off the prompt for the header text. If the data elements have links, then I render an extra column at the end.

I only render the header once, and since collection+json includes prompt information for every single element in the collection every single time, I have to just pluck off the first item in the collection and render the header row for it (assuming the prompts don’t change).

For rendering the actual data row, it’s pretty straightforward to loop through the data items and render the data portion for each one. Finally, if the item has links, I’ll render all the links together, using the link prompt and rel to indicate where to place the linked data on the screen.

The method to render a table of collection+json data is relatively decoupled from what the collection+data represents. The only real app-specific piece is how to navigate the collections and what to do in each case (render results in a new DIV).

Rendering the table was rather too involved for my taste, and I’d like to componentize it if I can. In the last post, I’ll look at using ReactJS to build React components for the different rendering pieces in the table.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in REST. Bookmark the permalink. Follow any comments here with the RSS feed for this post.