Deliteful Tutorial (Part 4) - The Photo List View

We will now begin to create our Flickr photo feed viewer application.

It is time to open your favorite editor or IDE and load the index.html file of the application in it.

If you have chosen to get the tutorial application from the ibm-js/deliteful-tutorial project, switch to the part4 branch now: $ git checkout part4

HTML and CSS

The layout of the default application that we generated using Yeoman is roughly similar to what we want: a header on top, and contents that fit the remaining page space below. So we will keep the existing structure to make our list view, that is, a (vertical) d-linear-layout and another nested, horizontal d-linear-layout for the header.

Edit the contents of the body element as follows :

<!-- left menu side pane -->
<d-side-pane mode="push" position="start" id="leftPane">
</d-side-pane>

<!-- page content -->
<d-linear-layout class="width100 height100" id="listView">
    <!-- view content header -->
    <d-linear-layout vertical="false" class="pageHeader">
        <div>
            <button is="d-button" onclick="leftPane.toggle()">Settings</button>
        </div>
        <div class="fill titleStyle">Flickr Photo Feed</div>
        <div>
            <button is="d-button">Refresh</button>
        </div>
    </d-linear-layout>
    <!-- view content will go here -->
</d-linear-layout>

We have removed the contents of the left pane (we will fill it again later when we implement the Settings view), changed some labels, set an id attribute on the view and added a second button after the title (the nested d-linear-layout will automatically stack it on the right because the middle div has a fill class).

We also need to change the CSS, so open css/app.css in your editor. To keep things simple, remove all the existing content and add these rules to define the look of our new header:

.pageHeader {
    background-color: #428bca;
    color: white;
    height: 48px;
    border-bottom: 2px solid #357ebd;
    padding: 0.3em;
}
.titleStyle {
    text-align: center;
    line-height: 40px;
    font-weight: bold;
    font-size: 16px;
}
.d-button {
    border-radius: 0;
}

You can also remove Font-Awesome dependency from the bower.json as we won't use it in this tutorial.

If you open index.html in your browser now, you should get something like this:

List view header

A difference with the default application, though, is that in our case, we want a second view (the details view) to completely replace the list view (including the header), whereas in the default application only the contents is replaced and the header stays.

To achieve that, we will wrap our toplevel d-linear-layout inside a d-view-stack:

<!-- page content -->
<d-view-stack class="width100 height100" id="vs">
    <d-linear-layout id="listView">
    ...
    </d-linear-layout>
</d-view-stack>

(Note that we moved the class="width100 height100" to the toplevel d-view-stack)

Finally, we want to display a list of photos, so let's add a d-list component as the contents of our view. For this, replace the <!-- view content will go here --> placeholder by a d-list.

<!-- scrollable list -->
<div class="fill">
    <d-list class="width100 height100" id="photolist">
    </d-list>
</div>

Note our List widget has a photolist id, this will allow us to reference the widget in our JavaScript code later.

And also add this in css/app.css to ensure the list is correctly sized (see the LinearLayout doc for more details on why this is needed)

#photolist {
    position: absolute
}

Getting the Photo List from Flickr

It is now time to write some JavaScript code.

Open js/app.js and let's remove things that we don't need: the "deliteful/StarRating" and "deliteful/ProgressBar" modules and the last instruction:

define([
    "delite/register", "delite/theme!delite/themes/{{theme}}/global.css", "deliteful/ViewStack",
    "deliteful/SidePane", "deliteful/LinearLayout", "deliteful/Button",
    "deliteful/list/List", "requirejs-domready/domReady!"
], function (register) {
    register.parse();
    document.body.style.display = "";

    /* app code will go here */

});

To get the photos, we will use the Flickr API to retrieve photos feeds based on tags. We will request the photos in json format and use the JSONP communication technique. (JSONP is not very commonly used in real world applications, but it has the advantage of not requiring to setup any server-side code, and it is good enough to simulate a server request in our example app).

Here is the code that does the JSONP request, use it to replace the /* app code will go here */ comment in js/app.js:

var script;

// Makes a request to the Flickr API to get recent photos with the specified tag.
// When the request completes, the "photosReceived" function will be called with json objects
// describing each photo.
function getPhotos(tags) {
    requestDone(); // abort current request if any

    var url = (window.location.protocol || "http:") +
        "//api.flickr.com/services/feeds/photos_public.gne?format=json&jsoncallback=photosReceived&tags=" +
        tags + "&tagmode=all";
    script = document.createElement("script");
    script.type = 'text/javascript';
    script.src = url;
    script.async = true;
    script.charset = 'utf-8';
    document.getElementsByTagName('head')[0].appendChild(script);
}

// Must be called to cleanup the current JSONP request (i.e. remove the "script" element).
function requestDone() {
    if (script && script.parentNode) {
        script.parentNode.removeChild(script);
        script = null;
    }
}

We won't go into the details of the code, but in short the getPhotos function sends a request to the Flickr server. The URL contains the photo tags that we are interested in, and the name of a callback function to call when the request completes. The reply sent by Flickr will be a JSON string containing a call to that function (photosReceived in our case), with an array of JavaScript objects as parameter, each object describing a photo: the URL of a thumbnail image, the photo title, etc.

Displaying the Photo List

OK, that was the hard part! We would like to see something on the screen now, the good news is that it's really easy. We have asked for a photoReceived global function to be called, so let's create it:

photosReceived = function (json) {
    // cleanup request
    requestDone();
    // show the photos in the list by simply setting the list's store
    photolist.store = new Memory({data: json.items});
};

We must first call requestDone() (that's part of our quick JSONP implementation).

Then, we just set the store property of our photolist widget. The store property is a common property that lets you connect data to many deliteful widgets. Deliteful has built-in connections to data stores defined by the dstore project. In our case, we will use a Memory store that wraps JavaScript objects, since that is what our JSONP request returned to us.

Note that you must also add "dstore/Memory" to the list of AMD dependencies and bind it to a Memory parameter in the define callback:

define([
    "delite/register", "dstore/Memory", ...
], function(register, Memory) {
    ...

We must now initiate the request somehow. Let's create a global function for this:

refreshPhotoList = function () {
    photolist.store = new Memory();
    getPhotos("bridges,famous");
};

The refreshPhotoList function first clears the list then sends a new request (with hardcoded tags for now). Let us add a call to the function now, so the photos are retrieved and displayed at startup.

refreshPhotoList();

Let us also set a click handler on the "Refresh" button if the user wants to reload the photos:

<button is="d-button" onclick="refreshPhotoList()">Refresh</button>

We can already try that and open index.html in a browser:

List View 1

OK, we can see that our code works because the list is populated with items, but they are empty. We just miss one piece: we need to tell the List widget what to display exactly. Our JSON photo descriptions have a title property that we would like to display in the list. Again that's very easy, just add a labelAttr attribute to the d-list element:

<d-list class="width100 height100" id="photolist" labelAttr="title">

And here is the result:

List View 2

Run the Demo

Click here to see the live demo: Deliteful Tutorial - Part 4

Next Step

We learned the basic techniques to connect a deliteful widget to data retrieved from a server. In the next step, we will further customize our list view to enhance the display.

Previous Step - Introducing the Photo Feed Application

Next Step - Enhancing the List View