Discovering the cache in App Cloud

The function bc.core.cache() is your best friend when it comes to making your App Cloud apps "sticky" and intelligent. If you’ve never used it, you’ll feel like you just discovered a buried treasure.

For newcomers, here’s a quick primer: bc.core.cache() has a dynamic signature. To save a value in the cache (on disk), you pass a key and a value, as in bc.core.cache("fruit", "apple"). To retrieve a value from disk, just pass a key: var fruit = bc.core.cache("fruit"). The saved value can be any kind of JavaScript object or array.

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:

  1. // set the preference
  2. bc.core.cache(“USER_DISPLAY_MODE”, “grid”);
  3. // get the preference, defaulting to list
  4. 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:

  1. // set favorites (an array) in the cache
  2. function setFavorites(favorites) {
  3. bc.core.cache(“favorites”, favorites);
  4. }
  5. // get favorites from the cache, or return an empty array
  6. function getFavorites() {
  7. return bc.core.cache(“favorites”) || [];
  8. }
  9. // add an article to favorites
  10. function addToFavorites(articleId) {
  11. var favorites = getFavorites();
  12. if (!isFavorite(articleId)) {
  13. favorites.push(getArticle(articleId));
  14. }
  15. setFavorites(favorites);
  16. }
  17. // remove an article from favorites
  18. function removeFromFavorites(articleId) {
  19. var favorites = getFavorites();
  20. for (var i in favorites) {
  21. if (favorites[i].articleId === articleId) {
  22. favorites.splice(i, 1);
  23. break;
  24. }
  25. }
  26. setFavorites(favorites);
  27. }
  28. // is an article already favorited?
  29. function isFavorite(articleId) {
  30. var favorites = getFavorites();
  31. for (var i in favorites) {
  32. if (favorites[i].articleId === articleId) {
  33. return true;
  34. }
  35. }
  36. return false;
  37. }

See a working demo on GitHub.

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:

  1. // save the last viewed article ID when user exits the view
  2. $(bc).bind(“viewblur”, function (evt) {
  3. bc.core.cache(“LAST_VIEWED_ARTICLE_ID”, articleId);
  4. });
  5. // restore the view if necessary when user re-enters
  6. $(bc).bind(“viewfocus”, function (evt) {
  7. var lastId = bc.core.cache(“LAST_VIEWED_ARTICLE_ID”);
  8. if (lastId) {
  9. // do stuff
  10. }
  11. });

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:

  1. var one_week = 7 * 24 * 60 * 60 * 1000;
  2. var time_saved = bc.core.cache(“BLOG_DATA_LOADED_AT”) || 0;
  3. var time_now = new Date().getTime();
  4. if (time_now – time_saved > one_week) {
  5. // load new data
  6. bc.core.getData(“blog-feed”, function (data) {
  7. // render blog with new data
  8. renderBlog(data);
  9. // update the data in cache
  10. bc.core.cache(“blog-data”, data);
  11. // update the timestamp in cache
  12. bc.core.cache(“BLOG_DATA_LOADED_AT”, time_now);
  13. });
  14. }
  15. else {
  16. // render blog with old data
  17. renderBlog(bc.core.cache(“blog-data”));
  18. }

5. Show placeholder data

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

  1. var cachedData = bc.core.cache(“blog-data”) || [];
  2. renderBlog(cachedData);
  3. bc.core.getData(“blog-feed”, function (data) {
  4. renderBlog(data);
  5. });

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. All you need to do is plug it into your application logic!

0 Comments

Cropping images with App Cloud

App Cloud’s image transcoding API just got even better with the addition of two new features. Now you can crop photos on the fly and specify a quality setting for JPEG images.

Cropping

To crop an image, just add crop=x,y,w,h to the transcode URL, where x,y is the top left coordinate. Take the following image (originally 425×282):

http://coffee.brightcove.com/wp-content/uploads/2011/10/female.jpg

Now make it 275×275, a perfect square:

http://transcode.appcloud.brightcove.com/?image=coffee.brightcove.com/wp-content/uploads/2011/10/female.jpg&crop=0,0,275,275

Now reduce it to 100×100:

http://transcode.appcloud.brightcove.com/?image=coffee.brightcove.com/wp-content/uploads/2011/10/female.jpg&crop=0,0,275,275&width=100&height=100

As you can see, cropping is applied before resizing. Important: Your image must be large enough to cover the cropped area or else the request will fail.

JPEG Quality Settings

You can now specify a quality setting for JPEG images on a scale of 0-100. (The default value is 70.) Take the following image:

http://coffee.brightcove.com/wp-content/uploads/2011/10/m-i-tea.jpg

It’s 140KB. Now run it through the transcoding service, maintaining the original size:

http://transcode.appcloud.brightcove.com/?image=http://coffee.brightcove.com/wp-content/uploads/2011/10/m-i-tea.jpg

Now it’s 17KB—whoa!—but you’ll notice the quality degrades in some solid-tone areas. Try setting the quality to 90:

http://transcode.appcloud.brightcove.com/?image=http://coffee.brightcove.com/wp-content/uploads/2011/10/m-i-tea.jpg&quality=90

Now it’s 25KB—still a huge savings!

The results will vary depending on the type of photograph. But for most pictures, the default quality (70) will do. Here’s the above image at the default quality—see if you can tell the difference:

http://transcode.appcloud.brightcove.com/?image=coffee.brightcove.com/wp-content/uploads/2011/10/female.jpg

Performance Boost

Finally, it takes a lot of of hamsters to spin the wheels of our image transcoding service. We just added 1,000 hamsters to speed up requests to the image cache by 70%! Try it and you’ll see!

0 Comments

Using properties files with JavaScript

i18n samples

While developing a multilingual template for App Cloud, I needed a convenient way to store strings for each language. Rather than use JavaScript arrays, as I discussed in an earlier post, I created external text files that mimic Java properties files:

  1. # en.txt
  2. hello_msg=Hello!
  3. hello_user_msg=Hello, {{user.first}}! Welcome to App Cloud!
  1. # es.txt
  2. hello_msg=¡Hola!
  3. hello_user_msg=¡Hola, {{user.first}}! ¡Bienvenido a App Cloud!
  1. # ja.txt
  2. hello_msg=こんにちは!
  3. hello_user_msg=こんにちは、{{user.first}}!アプリケーションクラウドへようこそ!

As you can see, a properties file is easy to both read and write. But how can it be loaded into a JavaScript structure? Here’s how to do it with jQuery’s $.get method:

  1. var properties = {};
  2. $.get(“en.txt”, function (text) {
  3. text = text.split(“\n”);
  4. for (var t in text) {
  5. var s = text[t].trim();
  6. if (!s.length || s.charAt(0) === “#”) {
  7. continue;
  8. }
  9. s = s.split(“=”);
  10. properties[s[0].trim()] = s[1].trim();
  11. }
  12. }, “html”);

Then, to access a string:

  1. properties.hello_msg

Tokens inside the strings, e.g. {{user.name}}, can be replaced with a good templating system like Markup.js. (See the documentation for implementation details.)

Since the properties files are part of my App Cloud template (and therefore stored on the device), they’re simply loaded from disk with little or no latency. (Note, a future filesystem API might replace $.get as a method of loading local resources.) If you’re loading properties files from a remote location, you can save the contents in Local Storage or the Application Cache for instantaneous retrieval the next time you need them.

See the working demo on GitHub.

0 Comments

Image transcoding with App Cloud

Image transcoding example

App Cloud provides a ridiculously simple way to resize and cache images on the fly. I reduced one of my blog images from 125KB at 425px wide to 8KB at 200px wide, a 94% reduction!

Here’s the image before …

http://coffee.brightcove.com/wp-content/uploads/2011/10/female.jpg

… and after:

http://transcode.appcloud.brightcove.com/?image=http://coffee.brightcove.com/wp-content/uploads/2011/10/female.jpg&width=200

You can also specify height, max_dimension, and format (jpg, png, or gif). Try it for yourself!

0 Comments

Writing custom pipes for Markup.js

Markup.js features a powerful system of “pipes” to transform variables inside templates. Here’s a basic example, which converts a string to upper case:

Title: {{title|upcase}}

Some pipes accept arguments. For example, the chop pipe chops a string to the given number of characters followed by “…”:

Description: {{description|chop>100}}

Pipes can be chained together:

Title: {{title|upcase|chop>50}}

Pipes can return boolean values for use in IF statements:

{{if age|more>75}} Old man! {{/if}}

And pipes can be used with arrays:

{{articles|limit>10}} … {{/articles}}

While Markup.js includes more than 40 built-in pipes, it’s easy to write your own. A pipe is just a function that returns a value. The first argument should always be the piped value itself; any other functions are passed as strings. For example:

  1. // add n exclamation points to the end of a string
  2. Mark.pipes.shout = function (str, n) {
  3. return str + new Array(parseInt(n || 1) + 1).join(“!”);
  4. };
  5. // wrap all links in anchor tags
  6. Mark.pipes.links = function (str) {
  7. return str.replace(/\b(https?:[^\b\s]+)\b/g, “<a href=\”$1\”>$1</a>”);
  8. };

Boolean pipes should return the inputted value if the expression is true. Otherwise they should return false. This way, pipes can be chained together to form complex AND statements. For example:

  1. Mark.pipes.big = function (num) {
  2. return num > 1000000 ? num : false;
  3. };
  4. var template = “{{if salary|big|even}} A nice round number! {{/if}}”;
  5. var context = { salary: 5000000 };
  6. var html = Mark.up(template, context);

Check out more example pipes and see the full documentation on github.

0 Comments

Video and apps: A perfect match

I recently wrote about how video and apps are symbiotic, like peanut butter and jelly:

  • Video breathes life into apps while apps provide a contextual framework for discovering and delivering video.

  • Video depends on the entire app landscape (phones, tablets, web sites, and smart TVs) for broad distribution. Likewise, the app landscape depends on pluggable, reusable forms of content like video.

  • Video drives engagement and engagement drives app usage.

Read the entire post on brightcove.com.

0 Comments

Caching data 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:

1. You can 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.

2. You can 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:

  1. var loaded_at = bc.core.cache(“video_feed_loaded_at”) || 0; // defaults to 0
  2. var time_now = new Date().getTime();
  3. var interval = 1000 * 60 * 60 * 24 * 3; // 3 days
  4. // if the cached data is older than 3 days
  5. if (time_now – loaded_at > interval) {
  6. // load new data from server and overwrite the cached data
  7. bc.core.getData(“videos”,
  8. function (data) {
  9. bc.core.cache(“video_feed”, data);
  10. bc.core.cache(“video_feed_loaded_at”, new Date().getTime());
  11. displayVideos();
  12. },
  13. function (error) {
  14. // oops
  15. }
  16. );
  17. }
  18. // else show cached data
  19. else {
  20. displayVideos();
  21. }
  22. function displayVideos() {
  23. var feed = bc.core.cache(“video_feed”);
  24. // do stuff
  25. }

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.

3. You can 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:

  1. 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 in my reference application for this purpose:

  1. // cache a “favorite” value in an array with the given key
  2. // e.g. saveFavorite(“fruits”, “apple”)
  3. function saveFavorite(key, val) {
  4. var favorites = bc.core.cache(key) || [];
  5. if (favorites.indexOf(val) === -1) {
  6. favorites.push(val);
  7. }
  8. bc.core.cache(key, favorites);
  9. };
  10. // delete a “favorite” value from a cached array with the given key
  11. // e.g. deleteFavorite(“fruits”, “apple”)
  12. function deleteFavorite(key, val) {
  13. var favorites = bc.core.cache(key) || [];
  14. var idx = favorites.indexOf(val);
  15. if (idx > -1) {
  16. favorites.splice(idx, 1);
  17. bc.core.cache(key, favorites);
  18. }
  19. };
  20. // determine if a value is contained in a list of favorites
  21. // e.g. isFavorite(“fruits”, “apple”)
  22. function isFavorite(key, val) {
  23. var favorites = bc.core.cache(key) || [];
  24. return favorites.indexOf(val) > -1;
  25. };

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

0 Comments

Using CSS Media Queries in App Cloud

Some developers have asked me how to change the layout of a view depending on the device’s orientation. The simplest way—and this is really very simple—is to use CSS3 Media Queries:

  1. @media all and (orientation: portrait) {
  2. /* CSS rules here */
  3. }
  4. @media all and (orientation: landscape) {
  5. /* CSS rules here */
  6. }

For example, let’s say you want to change the body color from red to green when the user rotates the device from portrait to landscape:

  1. @media all and (orientation: portrait) {
  2. body {
  3. color: red;
  4. }
  5. }
  6. @media all and (orientation: landscape) {
  7. body {
  8. color: green;
  9. }
  10. }

Add the above code to your stylesheet and see what happens. (You can test it in your browser by resizing the window.)

It’s not necessary to put all your styles inside the media queries—just the styles that are specific to portrait or landscape mode.

Combine Media Queries with the Flexible Box Model and now you’re cooking with gas!

Note, CSS will only affect the layout, not the behavior, of your app. You can listen for a “vieworientationchange” event if you need to perform some action when the user rotates the device.

0 Comments

App Cloud content feeds: Getting to green

App Cloud content feed optimization

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 has 25). Then I selected just the fields I needed (about 9 or 10, including title, description, duration, etc.).

The result: 20KB. A nearly 90% reduction! It’s not just academic—my app sped up noticeably after I tamed this bad boy. I’m saving on two fronts: the payload comes down faster and the app can process it faster.

You can see this feed at work in the reference app for phones.

0 Comments

Great Chrome extensions for App Cloud developers

Google Chrome logl

Check out these Chrome extensions for your next App Cloud project:

QR-Code Tag Extension

Generate a QR code for any URL, including your manifest file (manifest.json). Perfect for quickly loading your template into the Workshop app.

JSON formatter

Format any JSON page with syntax highlighting, collapsible trees, and clickable URLs..

Reload All Tabs

Reload all open tabs with a single click. A big timesaver if you’ve got each of your views running in a separate tab.

0 Comments