2 <span class=
"h1-step">Step
5:
</span>
3 Add Images From the Web
7 <strong>Want to start fresh from here?
</strong>
8 Find the previous step's code in the
<a href=
"https://github.com/mangini/io13-codelab/archive/master.zip">reference code zip
</a> under
<strong><em>cheat_code
> solution_for_step4
</strong></em>.
11 <p>In this step, you will learn:
</p>
14 <li>How to load resources from outside your app and add them to the DOM through XHR and ObjectURLs.
</li>
18 <em>Estimated time to complete this step:
20 minutes.
</em>
20 To preview what you will complete in this step,
<a href=
"#launch">jump down to the bottom of this page
↓</a>.
23 <h2 id=
"csp-compliance">How CSP affects the use of external resources
</h2>
25 <p>The Chrome Apps platform forces your app to be fully compliant with Content
26 Security Policies (CSP). You can't directly load DOM
27 resources like images, fonts, and CSS from outside of your Chrome App package.
</p>
29 <p>If you want to show an external image in your app, you need to request it via
<a href=
"https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest">XMLHttpRequest
</a>,
30 transform it into a
<a href=
"https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob
</a>, and create an
<a href=
"https://developer.mozilla.org/en-US/docs/Web/API/URL.createObjectURL">ObjectURL
</a>. This
<code>ObjectURL
</code> can be added to the DOM
31 because it refers to an in-memory item in the context of the app.
</p>
33 <h2 id=
"show-images">Show thumbnail images for todo items
</h2>
35 <p>Let's change our app to look for image URLs in a todo item.
36 If the URL looks like an image (for example, ends with .png, .jpg, .svg, or .gif),
37 apply the process mentioned above in order to show an image thumbnail next to the URL.
</p>
39 <h3 id=
"update-permissions">Update permissions
</h3>
41 <p>In a Chrome App, you can make XMLHttpRequest calls to any URL as long as you
42 whitelist its domain in the manifest. Since you won't know
43 beforehand what image URL the user will type,
44 ask permission to make requests to
<code>"<all_urls>"</code>.
</p>
46 <p>In
<strong><em>manifest.json
</em></strong>, request the
"<all_urls>" permission:
</p>
48 <pre data-filename=
"manifest.json">
49 "permissions": [
"storage",
"alarms",
"notifications",
50 "webview"<b>,
"<all_urls>"</b>],
53 <h3 id=
"object-urls">Create and clear ObjectURLs
</h3>
55 In
<strong><em>controller.js
</em></strong>, add a
<code>_createObjectURL()
</code> method to create ObjectURLs from a Blob:
57 <pre data-filename=
"controller.js">
58 Controller.prototype._createObjectURL = function(blob) {
59 var objURL = URL.createObjectURL(blob);
60 this.objectURLs = this.objectURLs || [];
61 this.objectURLs.push(objURL);
66 <p>ObjectURLs hold memory, so when you no longer need the ObjectURL, you
67 should revoke them. Add this
68 <code>_clearObjectURL()
</code> method to
<em>controller.js
</em> to handle that:
</p>
70 <pre data-filename=
"controller.js">
71 Controller.prototype._clearObjectURL = function() {
72 if (this.objectURLs) {
73 this.objectURLs.forEach(function(objURL) {
74 URL.revokeObjectURL(objURL);
76 this.objectURLs = null;
81 <h3 id=
"xhr">Make a XHR request
</h3>
83 <p>Add a
<code>_requestRemoteImageAndAppend()
</code> method to execute a XMLHttpRequest
84 on a given image URL:
</p>
86 <pre data-filename=
"controller.js">
87 Controller.prototype._requestRemoteImageAndAppend = function(imageUrl, element) {
88 var xhr = new XMLHttpRequest();
89 xhr.open('GET', imageUrl);
90 xhr.responseType = 'blob';
91 xhr.onload = function() {
92 var img = document.createElement('img');
93 img.setAttribute('data-src', imageUrl);
94 img.className = 'icon';
95 var objURL = this._createObjectURL(xhr.response);
96 img.setAttribute('src', objURL);
97 element.appendChild(img);
103 <p>On XHR load, this method creates an
<code>ObjectURL
</code> from the XHR's response,
104 and adds an
<code><img
></code> element with this
<code>ObjectURL
</code> to the DOM.
</p>
106 <h3 id=
"parse-urls">Parse for image URLs in todo items
</h3>
108 <p>Now add a
<code>_parseForImageURLs()
</code> method that finds all links not yet processed and checks them
109 for images. For each URL that looks like an image, execute
<code>_requestRemoteImageAndAppend()
</code>:
111 <pre data-filename=
"controller.js">
112 Controller.prototype._parseForImageURLs = function () {
113 // remove old blobs to avoid memory leak:
114 this._clearObjectURL();
115 var links = this.$todoList.querySelectorAll('a[data-src]:not(.thumbnail)');
116 var re = /\.(png|jpg|jpeg|svg|gif)$/;
117 for (var i =
0; i
<links.length; i++) {
118 var url = links[i].getAttribute('data-src');
120 links[i].classList.add('thumbnail');
121 this._requestRemoteImageAndAppend(url, links[i]);
127 <h3 id=
"render-thumbnails">Render thumbnails in the todo list
</h3>
129 <p>Now call
<code>_parseForImageURLs()
</code> from
<code>showAll()
</code>,
<code>showActive()
</code>, and
130 <code>showCompleted()
</code>:
</p>
132 <pre data-filename=
"controller.js">
134 * An event to fire on load. Will get all items and display them in the
137 Controller.prototype.showAll = function () {
138 this.model.read(function (data) {
139 this.$todoList.innerHTML = this._parseForURLs(this.view.show(data));
140 <b>this._parseForImageURLs();
</b>
145 * Renders all active tasks
147 Controller.prototype.showActive = function () {
148 this.model.read({ completed:
0 }, function (data) {
149 this.$todoList.innerHTML = this._parseForURLs(this.view.show(data));
150 <b>this._parseForImageURLs();
</b>
155 * Renders all completed tasks
157 Controller.prototype.showCompleted = function () {
158 this.model.read({ completed:
1 }, function (data) {
159 this.$todoList.innerHTML = this._parseForURLs(this.view.show(data));
160 <b>this._parseForImageURLs();
</b>
165 <p>Do the same for
<code>editItem()
</code>:
</p>
167 <pre data-filename=
"controller.js">
168 Controller.prototype.editItem = function (id, label) {
170 var onSaveHandler = function () {
172 if (value.length
&& !discarding) {
174 label.innerHTML = this._parseForURLs(value);
175 <b>this._parseForImageURLs();
</b>
176 } else if (value.length ===
0) {
181 <h3 id=
"css">Constrain the displayed image dimensions
</h3>
183 <p>Finally, in
<strong><em>bower_components/todomvc-common/base.css
</strong></em>,
184 add a CSS rule to limit the size of the image:
</p>
186 <pre data-filename=
"base.css">
187 .thumbnail img[data-src] {
193 <h2 id=
"launch">Launch your finished Todo app
</h2>
195 <p>You are done Step
5! Reload your app and add a todo item with a URL
196 to an image hosted online. Some URLs you could use:
<strong>http://goo.gl/nqHMF#.jpg
</strong> or
<strong>http://goo.gl/HPBGR#.png
</strong>.
</p>
198 <p>The result should be something like this:
</p>
201 <img src=
"{{static}}/images/app_codelab/step5-completed.gif" alt=
"The Todo app with image thumbnails"/>
204 <p class=
"note"><strong>Tip
</strong>: For real-world situations, when you need to control
205 offline cache and dozens of simultaneous resource downloads, we have created
206 <a href=
"https://github.com/GoogleChrome/apps-resource-loader#readme">a helper library
</a>
207 to handle some common use cases.
</p>
209 <h2 id=
"recap">For more information
</h2>
211 <p>For more detailed information about some of the APIs introduced in this step, refer to:
</p>
215 <a href=
"/apps/contentSecurityPolicy" title=
"Read 'Content Security Policy' in the Chrome developer docs">Content Security Policy
</a>
216 <a href=
"#csp-compliance" class=
"anchor-link-icon" title=
"This feature mentioned in 'Learn how CSP affects the use of external web resources'">↑</a>
219 <a href=
"/apps/declare_permissions" title=
"Read 'Declare Permissions' in the Chrome developer docs">Declare Permissions
</a>
220 <a href=
"#update-permissions" class=
"anchor-link-icon" title=
"This feature mentioned in 'Update permissions'">↑</a>
224 <p>Ready to continue onto the next step? Go to
<a href=
"app_codelab_filesystem.html">Step
6 - Export todos to the filesystem
»</a></p>