OLD | NEW |
(Empty) | |
| 1 <h1 id="add-images"> |
| 2 <span class="h1-step">Step 5:</span> |
| 3 Add Images From the Web |
| 4 </h1> |
| 5 |
| 6 <p class="note"> |
| 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>. |
| 9 </p> |
| 10 |
| 11 <p>In this step, you will learn:</p> |
| 12 |
| 13 <ul> |
| 14 <li>How to load resources from outside your app and add them to the DOM throug
h XHR and ObjectURLs.</li> |
| 15 </ul> |
| 16 |
| 17 <p> |
| 18 <em>Estimated time to complete this step: 20 minutes.</em> |
| 19 <br> |
| 20 To preview what you will complete in this step, <a href="#launch">jump down to
the bottom of this page ↓</a>. |
| 21 </p> |
| 22 |
| 23 <h2 id="csp-compliance">Learn how CSP affects the use of external web resources<
/h2> |
| 24 |
| 25 <p>The Chrome Apps platform forces your app to be fully compliant with Content |
| 26 Security Policies (CSP). This includes not being able to directly load DOM |
| 27 resources like images, fonts, and CSS from outside of your packaged app.</p> |
| 28 |
| 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">XMLHt
tpRequest</a>, |
| 30 transform it into a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Bl
ob">Blob</a>, and create an <a href="https://developer.mozilla.org/en-US/docs/We
b/API/URL.createObjectURL">ObjectURL</a>. This ObjectURL can then be |
| 31 added to the DOM because it refers to an in-memory item in the context of the |
| 32 app.</p> |
| 33 |
| 34 <h2 id="show-images">Show thumbnail images for todo items</h2> |
| 35 |
| 36 <p>Let's change our app to look for image URLs in a todo item. If the URL |
| 37 looks like an image (e.g. ends with .png, .jpg, .svg, or .gif), we will apply th
e |
| 38 process mentioned above in order to show an image thumbnail next to the URL.</p> |
| 39 |
| 40 <h3 id="update-permissions">Update permissions</h3> |
| 41 |
| 42 <p>In a Chrome App you can make XMLHttpRequest calls to any URL as long as you |
| 43 whitelist its domain in the manifest. Since we won't know |
| 44 beforehand what image URL the user will type, |
| 45 we will ask permission to make requests to <code>"<all_urls>"</code>.</p> |
| 46 |
| 47 <p>In <strong><em>manifest.json</em></strong>, request the "<all_urls>" pe
rmission:</p> |
| 48 |
| 49 <pre data-filename="manifest.json"> |
| 50 "permissions": ["storage", "alarms", "notifications", |
| 51 "webview"<b>, "<all_urls>"</b>], |
| 52 </pre> |
| 53 |
| 54 <h3 id="object-urls">Create and clear ObjectURLs</h3> |
| 55 |
| 56 In <strong><em>controller.js</em></strong>, add a <code>_createObjectURL()</code
> method to create ObjectURLs from a Blob: |
| 57 |
| 58 <pre data-filename="controller.js"> |
| 59 Controller.prototype._createObjectURL = function(blob) { |
| 60 var objURL = URL.createObjectURL(blob); |
| 61 this.objectURLs = this.objectURLs || []; |
| 62 this.objectURLs.push(objURL); |
| 63 return objURL; |
| 64 }; |
| 65 </pre> |
| 66 |
| 67 <p>ObjectURLs hold memory so, when you no longer need the ObjectURL, you |
| 68 should revoke them. Add this |
| 69 <code>_clearObjectURL()</code> method to <em>controller.js</em> to handle that:<
/p> |
| 70 |
| 71 <pre data-filename="controller.js"> |
| 72 Controller.prototype._clearObjectURL = function() { |
| 73 if (this.objectURLs) { |
| 74 this.objectURLs.forEach(function(objURL) { |
| 75 URL.revokeObjectURL(objURL); |
| 76 }); |
| 77 this.objectURLs = null; |
| 78 } |
| 79 }; |
| 80 </pre> |
| 81 |
| 82 <h3 id="xhr">Make a XHR request</h3> |
| 83 |
| 84 <p>Add a <code>_requestRemoteImageAndAppend()</code> method to execute a XMLHttp
Request |
| 85 on a given image URL:</p> |
| 86 |
| 87 <pre data-filename="controller.js"> |
| 88 Controller.prototype._requestRemoteImageAndAppend = function(imageUrl, element)
{ |
| 89 var xhr = new XMLHttpRequest(); |
| 90 xhr.open('GET', imageUrl); |
| 91 xhr.responseType = 'blob'; |
| 92 xhr.onload = function() { |
| 93 var img = document.createElement('img'); |
| 94 img.setAttribute('data-src', imageUrl); |
| 95 img.className = 'icon'; |
| 96 var objURL = this._createObjectURL(xhr.response); |
| 97 img.setAttribute('src', objURL); |
| 98 element.appendChild(img); |
| 99 }.bind(this); |
| 100 xhr.send(); |
| 101 }; |
| 102 </pre> |
| 103 |
| 104 <p>On XHR load, this method will create an ObjectURL from the XHR's response, |
| 105 and add an <code><img></code> element with this ObjectURL to the DOM.</p> |
| 106 |
| 107 <h3 id="parse-urls">Parse for image URLs in todo items</h3> |
| 108 |
| 109 <p>Now add a <code>_parseForImageURLs()</code> method that finds all links not y
et processed and checks them |
| 110 for images. For each URL that looks like an image, execute <code>_requestRemoteI
mageAndAppend()</code>: |
| 111 |
| 112 <pre data-filename="controller.js"> |
| 113 Controller.prototype._parseForImageURLs = function () { |
| 114 // remove old blobs to avoid memory leak: |
| 115 this._clearObjectURL(); |
| 116 var links = this.$todoList.querySelectorAll('a[data-src]:not(.thumbnail)'); |
| 117 var re = /\.(png|jpg|jpeg|svg|gif)$/; |
| 118 for (var i = 0; i<links.length; i++) { |
| 119 var url = links[i].getAttribute('data-src'); |
| 120 if (re.test(url)) { |
| 121 links[i].classList.add('thumbnail'); |
| 122 this._requestRemoteImageAndAppend(url, links[i]); |
| 123 } |
| 124 } |
| 125 }; |
| 126 </pre> |
| 127 |
| 128 <h3 id="render-thumbnails">Render thumbnails in the todo list</h3> |
| 129 |
| 130 <p>Now call <code>_parseForImageURLs()</code> from <code>showAll()</code>, <code
>showActive()</code>, and |
| 131 <code>showCompleted()</code>:</p> |
| 132 |
| 133 <pre data-filename="controller.js"> |
| 134 /** |
| 135 * An event to fire on load. Will get all items and display them in the |
| 136 * todo-list |
| 137 */ |
| 138 Controller.prototype.showAll = function () { |
| 139 this.model.read(function (data) { |
| 140 this.$todoList.innerHTML = this._parseForURLs(this.view.show(data)); |
| 141 <b>this._parseForImageURLs();</b> |
| 142 }.bind(this)); |
| 143 }; |
| 144 |
| 145 /** |
| 146 * Renders all active tasks |
| 147 */ |
| 148 Controller.prototype.showActive = function () { |
| 149 this.model.read({ completed: 0 }, function (data) { |
| 150 this.$todoList.innerHTML = this._parseForURLs(this.view.show(data)); |
| 151 <b>this._parseForImageURLs();</b> |
| 152 }.bind(this)); |
| 153 }; |
| 154 |
| 155 /** |
| 156 * Renders all completed tasks |
| 157 */ |
| 158 Controller.prototype.showCompleted = function () { |
| 159 this.model.read({ completed: 1 }, function (data) { |
| 160 this.$todoList.innerHTML = this._parseForURLs(this.view.show(data)); |
| 161 <b>this._parseForImageURLs();</b> |
| 162 }.bind(this)); |
| 163 }; |
| 164 </pre> |
| 165 |
| 166 <p>Do the same for <code>editItem()</code>:</p> |
| 167 |
| 168 <pre data-filename="controller.js"> |
| 169 Controller.prototype.editItem = function (id, label) { |
| 170 ... |
| 171 var onSaveHandler = function () { |
| 172 ... |
| 173 if (value.length && !discarding) { |
| 174 ... |
| 175 label.innerHTML = this._parseForURLs(value); |
| 176 <b>this._parseForImageURLs();</b> |
| 177 } else if (value.length === 0) { |
| 178 ... |
| 179 } |
| 180 </pre> |
| 181 |
| 182 <h3 id="css">Constrain the displayed image dimensions</h3> |
| 183 |
| 184 <p>Finally, in <strong></em>bower_components/todomvc-common/base.css</strong></e
m>, add a CSS rule to limit |
| 185 the size of the image:</p> |
| 186 |
| 187 <pre data-filename="base.css"> |
| 188 .thumbnail img[data-src] { |
| 189 max-width: 100px; |
| 190 max-height: 28px; |
| 191 } |
| 192 </pre> |
| 193 |
| 194 <h2 id="launch">Launch your finished Todo app</h2> |
| 195 |
| 196 <p>You are done Step 5! Reload your app and add a todo item with a URL |
| 197 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 |
| 199 <p>The result should be something like this:</p> |
| 200 |
| 201 <figure> |
| 202 <img src="{{static}}/images/app_codelab/step5-completed.gif" alt="The Todo app
with image thumbnails"/> |
| 203 </figure> |
| 204 |
| 205 <p class="note"><strong>Tip</strong>: For real-world situations, when you need t
o control |
| 206 offline cache and dozens of simultaneous resource downloads, we have created |
| 207 <a href="https://github.com/GoogleChrome/apps-resource-loader#readme">a helper l
ibrary</a> |
| 208 to handle some common use cases.</p> |
| 209 |
| 210 <h2 id="recap">Recap APIs referenced in this step</h2> |
| 211 |
| 212 <p>For more detailed information about some of the APIs introduced in this step,
refer to:</p> |
| 213 |
| 214 <ul> |
| 215 <li> |
| 216 <a href="/apps/contentSecurityPolicy" title="Read 'Content Security Policy'
in the Chrome developer docs">Content Security Policy</a> |
| 217 <a href="#csp-compliance" class="anchor-link-icon" title="This feature menti
oned in 'Learn how CSP affects the use of external web resources'">↑</a> |
| 218 </li> |
| 219 <li> |
| 220 <a href="/apps/declare_permissions" title="Read 'Declare Permissions' in the
Chrome developer docs">Declare Permissions</a> |
| 221 <a href="#update-permissions" class="anchor-link-icon" title="This feature m
entioned in 'Update permissions'">↑</a> |
| 222 </li> |
| 223 </ul> |
| 224 |
| 225 <p>Ready to continue onto the next step? Go to <a href="app_codelab_filesystem.h
tml">Step 6 - Export todos to the filesystem »</a></p> |
OLD | NEW |