Adam Mark

Web design and development

Using Dropbox to Host Your App Cloud App in Development Mode

If you can’t create a local web server for any reason—too lazy, too many network restrictions, what’s a web server?—you can use a public Dropbox folder instead.

Just drop your template into the “Public” folder inside your Dropbox directory. Then right-click on your manifest file (manifest.json) and select Dropbox > Copy Public Link. You should get a URL like this:

http://dl.dropbox.com/u/12345678/mytemplate/manifest.json

Enter this URL in the Workshop app and you’re off to the races! You can also view your HTML pages in Chrome and debug them with the Developer Tools.

Warning! Your files will be accessible to anyone who knows the URL, so share wisely.

Discovering the Cache in App Cloud

Aaargh.

If you’ve never the cache in App Cloud, you’ll feel like you just discovered a buried treasure. Aaargh! Here are six ways to use the cache in your app:

1. Save user preferences

If your app lets users select a display preference—for example, whether to view items as a list or a grid—save the user’s selection for next time:

// set the preference
bc.core.cache("USER_DISPLAY_MODE", "grid");

// get the preference, defaulting to "list"
var displayMode = bc.core.cache("USER_DISPLAY_MODE") || "list";

2. Save user “favorites”

You can save entire objects in the cache. For example, an array of news articles:

// set favorites (an array) in the cache
function setFavorites(favorites) {
    bc.core.cache("favorites", favorites);
}

// get favorites from the cache, or return an empty array
function getFavorites() {
    return bc.core.cache("favorites") || [];
}

// add an article to favorites
function addToFavorites(articleId) {
    var favorites = getFavorites();

    if (!isFavorite(articleId)) {
        favorites.push(getArticle(articleId));
    }

    setFavorites(favorites);
}

// remove an article from favorites
function removeFromFavorites(articleId) {
    var favorites = getFavorites();

    for (var i in favorites) {
        if (favorites[i].articleId === articleId) {
            favorites.splice(i, 1);
            break;
        }
    }

    setFavorites(favorites);
}

// is an article already favorited?
function isFavorite(articleId) {
    var favorites = getFavorites();

    for (var i in favorites) {
        if (favorites[i].articleId === articleId) {
            return true;
        }
    }

    return false;
}

3. Save application state

Save the user some trouble by saving state—for example, the ID of the last viewed article—and then restoring the UI to that state when the user returns. You can use “viewblur” and “viewfocus” events to detect when a user comes and goes:

// save the last viewed article ID when user exits the view
$(bc).bind("viewblur", function (evt) {
    bc.core.cache("LAST_VIEWED_ARTICLE_ID", articleId);
});

// restore the view if necessary when user re-enters
$(bc).bind("viewfocus", function (evt) {
    var lastId = bc.core.cache("LAST_VIEWED_ARTICLE_ID");

    if (lastId) {
        // do stuff
    }
});

4. Save data with a long shelf life

If you’re working with data that has a long shelf life—for example, a blog that gets updated once every week or two—you can pull the data from the cache instead of hitting the server over and over again:

var one_week = 7 * 24 * 60 * 60 * 1000;
var time_saved = bc.core.cache("BLOG_DATA_LOADED_AT") || 0;
var time_now = new Date().getTime();

if (time_now - time_saved > one_week) {
    // load new data
    bc.core.getData("blog-feed", function (data) {
        // render blog with new data
        renderBlog(data);

        // update the data in cache
        bc.core.cache("blog-data", data);

        // update the timestamp in cache
        bc.core.cache("BLOG_DATA_LOADED_AT", time_now);
    });
}
else {
    // render blog with old data
    renderBlog(bc.core.cache("blog-data"));
}

5. Show placeholder data

While you’re loading new data, show old data as a placeholder. Anything beats a blank white screen:

var cachedData = bc.core.cache("blog-data") || [];

renderBlog(cachedData);

bc.core.getData("blog-feed", function (data) {
    renderBlog(data);
});

6. Run your app in offline mode

Use the techniques described above to store non-critical data for offline use. Your users will thank you!

Remember, unlike HTML5 Local Storage, bc.core.cache() takes care of serializing and deserializing JavaScript objects for you.

Using Content Analytics in App Cloud

The latest version of the App Cloud SDK (1.7.4) introduces content-level analytics for tracking custom events in your template. It’s a snap to use, with just two methods:

bc.metrics.startContentSession(contentURI, name)

The above method initiates a content session—for example, when a user begins reading a news article. The first argument, contentURI, should be a unique ID or URI. The second argument, name, should be a human-readable description (e.g. an article title) for display in App Cloud Studio.

bc.metrics.endContentSession(contentURI)

The above method ends the content session for the given contentURI. In the case of tracking article views, you might invoke this method when the user clicks “Back.”

See the App Cloud docs for a complete rundown.

Two important notes: First, content-level analytics work only in production mode, meaning you won’t see data appear in App Cloud Studio until you publish and deploy an app. Use console.log() to test your code during development. Second, it’s your responsibility to pause and unpause content sessions when the user exits and reenters a view. You can do this by listening for “viewblur” and “viewfocus” events.

Content-level analytics appear in the “Analytics” section of App Cloud Studio:

Screen shot

Working With Arrays in App Cloud

App Cloud apps are voracious consumers of JavaScript arrays, which carry all kinds of data from server to client: news articles, photo gallery metadata, a list of places near a user’s location, and even tweets. You’re probably familiar with the two basic techniques for iterating over arrays:

for (var i = 0; i < stuff.length; i++) {
    ...
}

and

for (var i in stuff) {
    ...
}

Now comes another:

stuff.forEach(function (value, index, array) {
    ...
});

Yeah, you read that right. And it doesn’t use jQuery or any other syntactic sugar. Here’s a fuller example:

var fruits = ["apple", "banana", "cherry"];

fruits.forEach(function (value, index, array) {
    console.log(value, index, arr);
});

Run the above code in your browser to see the output—then feel free to use this technique in your App Cloud app.

There are some other native JavaScript methods that might look new to you: every(), filter(), map() and some(). Read all about them in the MDN docs.

Creating a Reading List in App Cloud

The function bc.core.cache() is great for all kinds of things: saving user preferences, saving application state, or saving “favorites,” as in a reading list.

Here are some functions for maintaining a list of “favorites” in the cache. In this case, the favorites are news articles selected by the user. Each article is represented by a JavaScript object with a unique ID:

// get the favorites from cache, or return an empty array
function getFavorites() {
    return bc.core.cache("favorites") || [];
}

// overwrite the favorites in cache
function setFavorites(favorites) {
    bc.core.cache("favorites", favorites);
}

// determine if an article is a favorite (by article ID)
function isFavorite(articleId) {
    var favorites = getFavorites();

    for (var i in favorites) {
        if (favorites[i].articleId === articleId) {
            return true;
        }
    }

    return false;
}

// save an article as a favorite (by article ID)
function addToFavorites(articleId) {
    var favorites = getFavorites();

    if (!isFavorite(articleId)) {
        favorites.push(getArticle(articleId));
    }

    setFavorites(favorites);
}

// remove an article from favorites (by article ID)
function removeFromFavorites(articleId) {
    var favorites = getFavorites();

    for (var i in favorites) {
        if (favorites[i].articleId === articleId) {
            favorites.splice(i, 1);
            break;
        }
    }

    setFavorites(favorites);
}

To see this in action, check out the “reading list” demo in the App Cloud Demos repository on Brightcove Open Source.

Working With the DOM in App Cloud

Apps are full of lists—articles, videos, events, and so on—and these lists are often created dynamically with JavaScript. Say you’re creating a list of news articles. Your HTML might begin like this:

<ul id="articles">

</ul>

How should you fill the empty <ul>with content? There are two basic approaches:

The BAD way

var elem = document.getElementById("articles");

for (var i = 0; i < data.length; i++) {
    elem.innerHTML += "<li>" + data[i].title + "</li>";
}

The GOOD way

var elem = document.getElementById("articles");
var str = "";

for (var i = 0; i < data.length; i++) {
    str += "<li>" + data[i].title + "</li>";
}

elem.innerHTML = str;

What’s the difference? The bad technique modifies the DOM over and over again, causing the web view to repaint and reflow the document many times. What a drag!

The good technique builds up a string in memory, then injects it into the DOM once and only once. On mobile devices, this sort of optimization is essential to creating speedy apps.

App Cloud Content Feeds: Getting to Green

I just smashed my personal content feed optimization record in App Cloud. The original feed, a YouTube playlist, clocked in at 176KB. Whoa.

So how’d I do it? First, I limited the number of entries to 20 (the original feed had 25). Then I eliminated all the fields I didn’t need, leaving only 9 or 10.

Screen shot

The result: 20KB. A nearly 90% reduction! As soon as I tamed this bad boy, my app sped up noticeably. Not only does the payload come down faster, the app can process it faster.

Caching Data Feeds in App Cloud

Caching data with bc.core.cache() will improve the performance and “stickiness” of your app. Here are three reasons to do it:

Speed up your app

Seconds matter, especially in mobile phone apps. While you’re waiting for new data to load from the server, you can display cached data. Often, the data will be the same. In any case, you’re giving the user something to process instead of just a spinning wheel.

Save bandwidth

Bandwidth isn’t free. It costs time and money—especially for users without unlimited data plans. Lend a hand and don’t load data more frequently than is absolutely necessary. If the data is relatively permanent, load it once and cache it for a length of time (a day, a week, or more) before requesting it again. You can use timestamps (in the cache!) for this purpose. Let’s say you’re pulling down a list of instructional videos that rarely changes:

var loaded_at = bc.core.cache("video_feed_loaded_at") || 0; // defaults to 0
var time_now = new Date().getTime();
var interval = 1000 * 60 * 60 * 24 * 3; // 3 days

// if the cached data is older than 3 days
if (time_now - loaded_at > interval) {
    // load new data from server and overwrite the cached data
    bc.core.getData("videos",
        function (data) {
            bc.core.cache("video_feed", data);
            bc.core.cache("video_feed_loaded_at", new Date().getTime());
            displayVideos();
        },
        function (error) {
            // oops
        }
    );
}
// else show cached data
else {
    displayVideos();
}

function displayVideos() {
    var feed = bc.core.cache("video_feed");
    // do stuff
}

Above, we’re loading new data only if the cached data is empty (i.e. it has never been loaded) or if the cached data is more than three days old.

Note, you can always provide a “refresh” button for users to update data on their own. And sometimes it’s a good idea to display a “last updated” date alongside the results.

Save user preferences and other metadata

The cache is a great way to store preferences and other values that are specific to a particular user on a particular device. For example, you can save the user’s preferred display mode:

bc.core.cache("user_pref_display_mode", "grid");

Or, you can store a list of recently viewed articles, or favorites, by ID. I wrote some helper functions for this purpose:

// cache a "favorite" value in an array with the given key
// e.g. saveFavorite("fruits", "apple")
function saveFavorite(key, val) {
    var favorites = bc.core.cache(key) || [];

    if (favorites.indexOf(val) === -1) {
        favorites.push(val);
    }

    bc.core.cache(key, favorites);
};

// delete a "favorite" value from a cached array with the given key
// e.g. deleteFavorite("fruits", "apple")
function deleteFavorite(key, val) {
    var favorites = bc.core.cache(key) || [];
    var idx = favorites.indexOf(val);

    if (idx > -1) {
        favorites.splice(idx, 1);
        bc.core.cache(key, favorites);
    }
};

// determine if a value is contained in a list of favorites
// e.g. isFavorite("fruits", "apple")
function isFavorite(key, val) {
    var favorites = bc.core.cache(key) || [];

    return favorites.indexOf(val) > -1;
};

The function bc.core.cache() is actually a wrapper for Local Storage—sometimes called “cookies on steroids.” As you’ve seen, it’s a simple yet invaluable way to add state to your applications.

Understanding Tap Events in App Cloud

Remember doing stuff like this?

<div id="hello" onclick="alert('hello!')">Hello</div>

Or this?

document.getElementById("hello").onclick = function (evt) {
    alert("hello!");
});

Or this?

document.getElementById("hello").addEventListener("click", function (evt) {
    alert("hello!");
});

Or this, with jQuery?

$("#hello").click(function () {
    alert("hello!");
});

All four techniques accomplish the same goal. But on phones and tablets, there’s no such thing as a “click.” In fact, there’s no such thing as a “tap.” In App Cloud, a tap event is a custom jQuery event that monitors touch events (touchstart, touchmove, touchend) to determine when a user is actually performing a tap. Here’s how to capture it:

$("#hello").bind("tap", function (evt) {
    alert('hello!');
});

Above, we’re listening for a custom tap on the element with the ID “hello.” If you log the evt object to the console, you’ll see all kinds of juicy info.

Since jQuery works on collections of elements, it’s very easy to listen for a tap event on many things at once. For example, say you’ve got a list of stooges:

<ul id="stooges">
    <li id="larry">Larry Fine</li>
    <li id="curly">Curly Howard</li>
    <li id="moe">Moe Howard</li>
</ul>

You can bind a tap event to every item in the list:

$("#stooges li").bind("tap", function (evt) {
    alert(this.id);
});

Inside the callback function, this refers to the DOM element that triggered the event (because the function is bound to that element). So clicking on “Larry Fine” would alert “larry”. In addition to storing information in the ID attribute, you can hang all kinds of data on an element with HTML5 data attributes. For example:

<ul id="foods">
    <li id="apple" data-food-type="fruit">Apple</li>
    <li id="banana" data-food-type="fruit">Banana</li>
    <li id="cucumber" data-food-type="veggie">Cucumber</li>
</ul>

Then you can inspect this data as needed.

Finally, it’s important to note that the bind function works only on elements that already exist in the DOM. Since you’ll be generating content dynamically, you can use the live() function, which attaches event handlers to the matched elements now or in the future. (In jQuery 1.7, the recommended technique is to use on.)

Using a JavaScript Templating System in App Cloud

In a typical web application, a web server is largely responsible for compiling HTML code before sending it to the client. This can be done with languages like PHP, Java and Ruby. But in an App Cloud app, HTML is compiled in the client with the help of JavaScript. For example, building up a list of blog entries:

var html = "<ul>";
for (var i = 0; i < results.length; i++) {
    html += "<li><b>" + results[i].title.toUpperCase() + ":</b> "
         + results[i].desc + "</li>";
}
html += "</ul>";

This can get ugly fast. Enter JavaScript templating systems. A templating system compiles HTML code for you—all it needs is a template (string) and some context data (a JavaScript object). Here’s what a template might look like:

<ul>
    {{articles}}
        <li><b>{{title|upcase}}:</b> {{desc}}</li>
    {{/articles}}
</ul>

Easier to read, right? And definitely easier to write!

There are plenty of JavaScript templating systems, including Mustache, jQuery Templates, and Markup.js, which I created with App Cloud in mind. The above example is from Markup.js. So how do we actually populate a template with context data?

var context = {
    name: {
        first: "John",
        last: "Doe"
    }
};
var template = "Hi, !";
var markup = Mark.up(template, context); // "Hi, John!"

The resulting string can then be injected into your HTML document.

In an App Cloud app, the context data is likely to come from a content feed. For example:

bc.core.getData("blog",
    function (data) {
        var template = bc.templates["article-index"];
        var context = { articles: data };
        var markup = Mark.up(template, context);

        document.getElementById("blog-results").innerHTML = markup;
    },
    function (error) {
        bc.device.alert("Oops! " + error.errorMessage);
    }
);

Markup.js also supports control logic (if/else), loops, and “pipes” for transforming variables. Check out the full documentation on GitHub.