Index: chrome/common/extensions/docs/templates/articles/app_codelab_images.html |
diff --git a/chrome/common/extensions/docs/templates/articles/app_codelab_images.html b/chrome/common/extensions/docs/templates/articles/app_codelab_images.html |
new file mode 100644 |
index 0000000000000000000000000000000000000000..df161d8cb4bb093d68703c9b54c1bd64fcbfeb60 |
--- /dev/null |
+++ b/chrome/common/extensions/docs/templates/articles/app_codelab_images.html |
@@ -0,0 +1,225 @@ |
+<h1 id="add-images"> |
+ <span class="h1-step">Step 5:</span> |
+ Add Images From the Web |
+</h1> |
+ |
+<p class="note"> |
+ <strong>Want to start fresh from here?</strong> |
+ 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>. |
+</p> |
+ |
+<p>In this step, you will learn:</p> |
+ |
+<ul> |
+ <li>How to load resources from outside your app and add them to the DOM through XHR and ObjectURLs.</li> |
+</ul> |
+ |
+<p> |
+ <em>Estimated time to complete this step: 20 minutes.</em> |
+ <br> |
+ To preview what you will complete in this step, <a href="#launch">jump down to the bottom of this page ↓</a>. |
+</p> |
+ |
+<h2 id="csp-compliance">Learn how CSP affects the use of external web resources</h2> |
+ |
+<p>The Chrome Apps platform forces your app to be fully compliant with Content |
+Security Policies (CSP). This includes not being able to directly load DOM |
+resources like images, fonts, and CSS from outside of your packaged app.</p> |
+ |
+<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>, |
+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 ObjectURL can then be |
+added to the DOM because it refers to an in-memory item in the context of the |
+app.</p> |
+ |
+<h2 id="show-images">Show thumbnail images for todo items</h2> |
+ |
+<p>Let's change our app to look for image URLs in a todo item. If the URL |
+looks like an image (e.g. ends with .png, .jpg, .svg, or .gif), we will apply the |
+process mentioned above in order to show an image thumbnail next to the URL.</p> |
+ |
+<h3 id="update-permissions">Update permissions</h3> |
+ |
+<p>In a Chrome App you can make XMLHttpRequest calls to any URL as long as you |
+whitelist its domain in the manifest. Since we won't know |
+beforehand what image URL the user will type, |
+we will ask permission to make requests to <code>"<all_urls>"</code>.</p> |
+ |
+<p>In <strong><em>manifest.json</em></strong>, request the "<all_urls>" permission:</p> |
+ |
+<pre data-filename="manifest.json"> |
+"permissions": ["storage", "alarms", "notifications", |
+ "webview"<b>, "<all_urls>"</b>], |
+</pre> |
+ |
+<h3 id="object-urls">Create and clear ObjectURLs</h3> |
+ |
+In <strong><em>controller.js</em></strong>, add a <code>_createObjectURL()</code> method to create ObjectURLs from a Blob: |
+ |
+<pre data-filename="controller.js"> |
+Controller.prototype._createObjectURL = function(blob) { |
+ var objURL = URL.createObjectURL(blob); |
+ this.objectURLs = this.objectURLs || []; |
+ this.objectURLs.push(objURL); |
+ return objURL; |
+}; |
+</pre> |
+ |
+<p>ObjectURLs hold memory so, when you no longer need the ObjectURL, you |
+should revoke them. Add this |
+<code>_clearObjectURL()</code> method to <em>controller.js</em> to handle that:</p> |
+ |
+<pre data-filename="controller.js"> |
+Controller.prototype._clearObjectURL = function() { |
+ if (this.objectURLs) { |
+ this.objectURLs.forEach(function(objURL) { |
+ URL.revokeObjectURL(objURL); |
+ }); |
+ this.objectURLs = null; |
+ } |
+}; |
+</pre> |
+ |
+<h3 id="xhr">Make a XHR request</h3> |
+ |
+<p>Add a <code>_requestRemoteImageAndAppend()</code> method to execute a XMLHttpRequest |
+on a given image URL:</p> |
+ |
+<pre data-filename="controller.js"> |
+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(); |
+}; |
+</pre> |
+ |
+<p>On XHR load, this method will create an ObjectURL from the XHR's response, |
+and add an <code><img></code> element with this ObjectURL to the DOM.</p> |
+ |
+<h3 id="parse-urls">Parse for image URLs in todo items</h3> |
+ |
+<p>Now add a <code>_parseForImageURLs()</code> method that finds all links not yet processed and checks them |
+for images. For each URL that looks like an image, execute <code>_requestRemoteImageAndAppend()</code>: |
+ |
+<pre data-filename="controller.js"> |
+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]); |
+ } |
+ } |
+}; |
+</pre> |
+ |
+<h3 id="render-thumbnails">Render thumbnails in the todo list</h3> |
+ |
+<p>Now call <code>_parseForImageURLs()</code> from <code>showAll()</code>, <code>showActive()</code>, and |
+<code>showCompleted()</code>:</p> |
+ |
+<pre data-filename="controller.js"> |
+/** |
+ * 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)); |
+ <b>this._parseForImageURLs();</b> |
+ }.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)); |
+ <b>this._parseForImageURLs();</b> |
+ }.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)); |
+ <b>this._parseForImageURLs();</b> |
+ }.bind(this)); |
+}; |
+</pre> |
+ |
+<p>Do the same for <code>editItem()</code>:</p> |
+ |
+<pre data-filename="controller.js"> |
+Controller.prototype.editItem = function (id, label) { |
+ ... |
+ var onSaveHandler = function () { |
+ ... |
+ if (value.length && !discarding) { |
+ ... |
+ label.innerHTML = this._parseForURLs(value); |
+ <b>this._parseForImageURLs();</b> |
+ } else if (value.length === 0) { |
+ ... |
+} |
+</pre> |
+ |
+<h3 id="css">Constrain the displayed image dimensions</h3> |
+ |
+<p>Finally, in <strong></em>bower_components/todomvc-common/base.css</strong></em>, add a CSS rule to limit |
+ the size of the image:</p> |
+ |
+<pre data-filename="base.css"> |
+.thumbnail img[data-src] { |
+ max-width: 100px; |
+ max-height: 28px; |
+} |
+</pre> |
+ |
+<h2 id="launch">Launch your finished Todo app</h2> |
+ |
+<p>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: <strong>http://goo.gl/nqHMF#.jpg</strong> or <strong>http://goo.gl/HPBGR#.png</strong>.</p> |
+ |
+<p>The result should be something like this:</p> |
+ |
+<figure> |
+ <img src="{{static}}/images/app_codelab/step5-completed.gif" alt="The Todo app with image thumbnails"/> |
+</figure> |
+ |
+<p class="note"><strong>Tip</strong>: For real-world situations, when you need to control |
+offline cache and dozens of simultaneous resource downloads, we have created |
+<a href="https://github.com/GoogleChrome/apps-resource-loader#readme">a helper library</a> |
+to handle some common use cases.</p> |
+ |
+<h2 id="recap">Recap APIs referenced in this step</h2> |
+ |
+<p>For more detailed information about some of the APIs introduced in this step, refer to:</p> |
+ |
+<ul> |
+ <li> |
+ <a href="/apps/contentSecurityPolicy" title="Read 'Content Security Policy' in the Chrome developer docs">Content Security Policy</a> |
+ <a href="#csp-compliance" class="anchor-link-icon" title="This feature mentioned in 'Learn how CSP affects the use of external web resources'">↑</a> |
+ </li> |
+ <li> |
+ <a href="/apps/declare_permissions" title="Read 'Declare Permissions' in the Chrome developer docs">Declare Permissions</a> |
+ <a href="#update-permissions" class="anchor-link-icon" title="This feature mentioned in 'Update permissions'">↑</a> |
+ </li> |
+</ul> |
+ |
+<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> |