Using new when.js 3.2.2 to build a front end for Spring Data REST

Engineering | Greg L. Turnquist | June 02, 2014 | ...

Greetings Spring community!

Roy Clarkson and I are presenting a talk at this year's SpringOne 2014 conference called Spring Data REST - Data Meets Hypermedia. We will explore how to quickly bridge the gap between a powerful Spring Data backend and a hypermedia enabled, RESTful front end.

In one part of the talk, we will delve into a javascript front end that lets the user takes pictures and upload them to a website. The website turns around and fetches images from the back end. By itself, this isn't that difficult thanks to the fully loaded RESTful API provided by Spring Data REST.

But fetching multiple images straight up isn't very efficient and is prone to freeze the web browser. Thanks to the CujoJS guys on our team (Brian Cavalier and John Hann), I was able to use the recently released when.js module and code a much smoother experience.

The segment below shows a core usage of rest.js combined with promises via when.js, and how it makes it super simple to write readable and functional code.

First, we pull in some key modules:

var rest = require('rest');
var when = require('when');
var defaultRequest = require('rest/interceptor/defaultRequest');
var mime = require('rest/interceptor/mime');
var hateoas = require('rest/interceptor/hateoas');

Then we configure an api object with a mime interceptor, a hateoas intercepter, and configure it to default the Accept header to application/hal+json so that Spring Data REST talks HAL.

var api = rest
    .wrap(mime)
    .wrap(hateoas)
    .wrap(defaultRequest, {headers: {'Accept': 'application/hal+json'}});

With that configuration, we can do some RESTful calls to fetch an array of images without wrecking the user's experience:

when.all(api({
    method: 'GET',
    path: gallery._links.items.href,
    params: {projection: "noImages"}
}).then(function (response) {
    if (response.entity._embedded) {
        return response.entity._embedded.items.map(function (itemWithoutImage) {
            return api({path: itemWithoutImage._links.self.href})
        })
    } else {
        return [];
    }
})).done(function(itemsWithImages) {
    itemsWithImages.forEach(function(item) {
        items[item._links.self.href] = item.entity;
        nestedTable.append(createItemRowForGallery(item.entity, gallery));
    })
})

So what's happening? Let's look at each little chunk and explore what is happening.

api({
    method: 'GET',
    path: gallery._links.items.href,
    params: {projection: "noImages"}
})

This is making a call to retrieve the array of items related to this particular gallery. It returns a promise giving us some nice options.

NOTE: It uses ?projection=noImages to fetch a list of item URIs without the image data. (Image retrieving ten 2MB images in one fell swoop!)

.then(function (response) {
	...
})

This function then takes the list of URIs and chops up the work of fetching their individual images.

    if (response.entity._embedded) {
        return response.entity._embedded.items.map(function (itemWithoutImage) {
            return api({path: itemWithoutImage._links.self.href})
        })
    } else {
        return [];
    }

Inside the then function, the code looks for _embedded data, and if it exists, it then transforms the array of imageless items 1-for-1 into an array of GET promises, fetching each item's actual image. If there is no _embedded data, then it returns an empty array.

when.all(
	...
).done(function(itemsWithImages) {
    itemsWithImages.forEach(function(item) {
        items[item._links.self.href] = item.entity;
        nestedTable.append(createItemRowForGallery(item.entity, gallery));
    })
})

The array of promises that are fetching image data is wrapped with when.all, a handy function that will wait until each and every promise is done before moving on.

Since we intend to consume the output of all these GETs by displaying them on the screen, we finish things off with done(). itemsWithImages, which is provided by when.all, is an array of equal size containing the results of each individual promise.

BTW: In case you didn't know...rule #1 when working with promises is that a then() function call MUST return either an object (that will get wrapped as a promise) or a promise itself. If you intend to actually consume the outcome and be done with it, use done() instead.

If you're a bit new to javascript, this might seem like quite a bit to take in. But being a javascript newbie myself, I have found that this API let me easily express what I wanted to do.

If you want to see more then be sure to sign up for our talk at this year's SpringOne! We'll look at a desktop version of this picture-snapping app. We'll also dig into a mobile friendly browser page and and a native mobile app, both which let you use your phone's camera to snap pics and upload to the site.

Cheers!

Get the Spring newsletter

Thank you for your interest. Someone will get back to you shortly.

Get ahead

VMware offers training and certification to turbo-charge your progress.

Learn more

Get support

Tanzu Spring Runtime offers support and binaries for OpenJDK™, Spring, and Apache Tomcat® in one simple subscription.

Learn more

Upcoming events

Check out all the upcoming events in the Spring community.

View all