Step 5: Add Images From the Web

In this step, you will learn:

  • How to load resources from outside your app and add them to the DOM through XHR and ObjectURLs.

Estimated time to complete this step: 20 minutes.
To preview what you will complete in this step, jump down to the bottom of this page ↓.

How CSP affects the use of external resources

The Chrome Apps platform forces your app to be fully compliant with Content Security Policies (CSP). You can't directly load DOM resources like images, fonts, and CSS from outside of your Chrome App package.

If you want to show an external image in your app, you need to request it via XMLHttpRequest, transform it into a Blob, and create an ObjectURL. This ObjectURL can be added to the DOM because it refers to an in-memory item in the context of the app.

Show thumbnail images for todo items

Let's change our app to look for image URLs in a todo item. If the URL looks like an image (for example, ends with .png, .jpg, .svg, or .gif), apply the process mentioned above in order to show an image thumbnail next to the URL.

Update permissions

In a Chrome App, you can make XMLHttpRequest calls to any URL as long as you specify its domain in the manifest. Since you won't know beforehand what image URL the user will type, ask permission to make requests to "<all_urls>".

In manifest.json, request the "" permission:

"permissions": ["storage", "alarms", "notifications",
                "webview", "<all_urls>"],

Create and clear ObjectURLs

In controller.js, add a _createObjectURL() method to create ObjectURLs from a Blob:

Controller.prototype._createObjectURL = function(blob) {
  var objURL = URL.createObjectURL(blob);
  this.objectURLs = this.objectURLs || [];
  this.objectURLs.push(objURL);
  return objURL;
};

ObjectURLs hold memory, so when you no longer need the ObjectURL, you should revoke them. Add this _clearObjectURL() method to controller.js to handle that:

Controller.prototype._clearObjectURL = function() {
  if (this.objectURLs) {
    this.objectURLs.forEach(function(objURL) {
      URL.revokeObjectURL(objURL);
    });
    this.objectURLs = null;
  }
};

Make a XHR request

Add a _requestRemoteImageAndAppend() method to execute a XMLHttpRequest on a given image URL:

Controller.prototype._requestRemoteImageAndAppend = function(imageUrl, element) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', imageUrl);
  xhr.responseType = 'blob';
  xhr.onload = function() {
    var img = document.createElement('img');
    img.setAttribute('data-src', imageUrl);
    img.className = 'icon';
    var objURL = this._createObjectURL(xhr.response);
    img.setAttribute('src', objURL);
    element.appendChild(img);
  }.bind(this);
  xhr.send();
};

On XHR load, this method creates an ObjectURL from the XHR's response, and adds an <img> element with this ObjectURL to the DOM.

Parse for image URLs in todo items

Now add a _parseForImageURLs() method that finds all links not yet processed and checks them for images. For each URL that looks like an image, execute _requestRemoteImageAndAppend():

Controller.prototype._parseForImageURLs = function () {
  // remove old blobs to avoid memory leak:
  this._clearObjectURL();
  var links = this.$todoList.querySelectorAll('a[data-src]:not(.thumbnail)');
  var re = /\.(png|jpg|jpeg|svg|gif)$/;
  for (var i = 0; i<links.length; i++) {
    var url = links[i].getAttribute('data-src');
    if (re.test(url)) {
      links[i].classList.add('thumbnail');
      this._requestRemoteImageAndAppend(url, links[i]);
    }
  }
};

Render thumbnails in the todo list

Now call _parseForImageURLs() from showAll(), showActive(), and showCompleted():

/**
 * An event to fire on load. Will get all items and display them in the
 * todo-list
 */
Controller.prototype.showAll = function () {
  this.model.read(function (data) {
    this.$todoList.innerHTML = this._parseForURLs(this.view.show(data));
    this._parseForImageURLs();
  }.bind(this));
};

/**
 * Renders all active tasks
 */
Controller.prototype.showActive = function () {
  this.model.read({ completed: 0 }, function (data) {
    this.$todoList.innerHTML = this._parseForURLs(this.view.show(data));
    this._parseForImageURLs();
  }.bind(this));
};

/**
 * Renders all completed tasks
 */
Controller.prototype.showCompleted = function () {
  this.model.read({ completed: 1 }, function (data) {
    this.$todoList.innerHTML = this._parseForURLs(this.view.show(data));
    this._parseForImageURLs();
  }.bind(this));
};

Do the same for editItem():

Controller.prototype.editItem = function (id, label) {
  ...
  var onSaveHandler = function () {
    ...
    if (value.length && !discarding) {
      ...
      label.innerHTML = this._parseForURLs(value);
      this._parseForImageURLs();
    } else if (value.length === 0) {
  ...
}

Constrain the displayed image dimensions

Finally, in _bowercomponents/todomvc-common/base.css, add a CSS rule to limit the size of the image:

.thumbnail img[data-src] {
  max-width: 100px;
  max-height: 28px;
}

Launch your finished Todo app

You are done Step 5! Reload your app and add a todo item with a URL to an image hosted online. Some URLs you could use: http://goo.gl/nqHMF#.jpg or http://goo.gl/HPBGR#.png.

For more information

For more detailed information about some of the APIs introduced in this step, refer to:

Ready to continue onto the next step? Go to Step 6 - Export todos to the filesystem »