OLD | NEW |
(Empty) | |
| 1 <h1 id="add-webview"> |
| 2 <span class="h1-step">Step 4:</span> |
| 3 Open External Links With a Webview |
| 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_step3</strong></em>. |
| 9 </p> |
| 10 |
| 11 <p>In this step, you will learn:</p> |
| 12 |
| 13 <ul> |
| 14 <li>How to show external web content inside your app in a secure and sandboxed
way.</li> |
| 15 </ul> |
| 16 |
| 17 <p> |
| 18 <em>Estimated time to complete this step: 10 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="overview">Learn about the webview tag</h2> |
| 24 |
| 25 <p>Some applications need to present external web content directly to the user b
ut |
| 26 keep them inside the application experience. For example, a news aggregator |
| 27 might want to embed the news from external sites with all the formatting, images
, |
| 28 and behavior of the original site. For these and other usages, Chrome Apps have |
| 29 a custom HTML tag called <a href="/apps/tags/webview">webview</a>.</p> |
| 30 |
| 31 <figure> |
| 32 <img src="{{static}}/images/app_codelab/webview-example.png" alt="The Todo app
using a webview"> |
| 33 </figure> |
| 34 |
| 35 <p class="note">A webview is a sandboxed process. For example, the enclosing Chr
ome App |
| 36 (also known as the "embedder page") cannot easily access the webview's loaded DO
M. |
| 37 You can only interact with the webview using its API.</p> |
| 38 |
| 39 <h2 id="implement-webview">Implement the webview tag</h2> |
| 40 |
| 41 <p>Let's update our Todo app to search for URLs in the todo item text |
| 42 in order to create a hyperlink. The link, when clicked, will open a new Chrome A
pp window |
| 43 (not a browser tab) with a webview presenting the content.</p> |
| 44 |
| 45 <h3 id="update-permissions">Update permissions</h3> |
| 46 |
| 47 <p>In <strong><em>manifest.json</em></strong>, request the <code>webview</code>
permission:</p> |
| 48 |
| 49 <pre data-filename="manifest.json"> |
| 50 "permissions": ["storage", "alarms", "notifications"<b>, "webview"</b>], |
| 51 </pre> |
| 52 |
| 53 <h3 id="create-webview">Create a webview embedder page</h3> |
| 54 |
| 55 <p>Create a new file in the root of your project folder and name it <strong><em>
webview.html</em></strong>. |
| 56 This file is a basic webpage with one <code><webview></code> tag:</p> |
| 57 |
| 58 <pre data-filename="webview.html"> |
| 59 <b><!DOCTYPE html> |
| 60 <html> |
| 61 <head> |
| 62 <meta charset="utf-8"> |
| 63 </head> |
| 64 <body> |
| 65 <webview style="width: 100%; height: 100%;"></webview> |
| 66 </body> |
| 67 </html></b> |
| 68 </pre> |
| 69 |
| 70 <h3 id="parse-urls">Parse for URLs in todo items</h3> |
| 71 |
| 72 <p>At the end of <strong><em>controller.js</em></strong>, add a new method calle
d <code>_parseForURLs()</code>:</p> |
| 73 |
| 74 <pre data-filename="controller.js"> |
| 75 Controller.prototype._getCurrentPage = function () { |
| 76 return document.location.hash.split('/')[1]; |
| 77 }; |
| 78 |
| 79 <b>Controller.prototype._parseForURLs = function (text) {</b> |
| 80 <b> var re = /(https?:\/\/[^\s"<>,]+)/g;</b> |
| 81 <b> return text.replace(re, '<a href="$1" data-src="$1">$1</a>');
</b> |
| 82 <b>};</b> |
| 83 |
| 84 // Export to window |
| 85 window.app.Controller = Controller; |
| 86 })(window); |
| 87 </pre> |
| 88 |
| 89 <p>Whenever a string starting with "http://" or "https://" is found, a HTML anch
or tag will be created to wrap around the URL.</p> |
| 90 |
| 91 <h3 id="render-links">Render hyperlinks in the todo list</h3> |
| 92 |
| 93 <p>Find <code>showAll()</code> in <em>controller.js</em>. Update <code>showAll()
</code> |
| 94 to parse for links by using the <code>_parseForURLs()</code> method that we adde
d previously:</p> |
| 95 |
| 96 <pre data-filename="controller.js"> |
| 97 /** |
| 98 * An event to fire on load. Will get all items and display them in the |
| 99 * todo-list |
| 100 */ |
| 101 Controller.prototype.showAll = function () { |
| 102 this.model.read(function (data) { |
| 103 <strike>this.$todoList.innerHTML = this.view.show(data);</strike> |
| 104 <b>this.$todoList.innerHTML = this._parseForURLs(this.view.show(data));</b> |
| 105 }.bind(this)); |
| 106 }; |
| 107 </pre> |
| 108 |
| 109 <p>Do the same for <code>showActive()</code> and <code>showCompleted()</code>:</
p> |
| 110 |
| 111 <pre data-filename="controller.js"> |
| 112 /** |
| 113 * Renders all active tasks |
| 114 */ |
| 115 Controller.prototype.showActive = function () { |
| 116 this.model.read({ completed: 0 }, function (data) { |
| 117 <strike>this.$todoList.innerHTML = this.view.show(data);</strike> |
| 118 <b>this.$todoList.innerHTML = this._parseForURLs(this.view.show(data));</b> |
| 119 }.bind(this)); |
| 120 }; |
| 121 |
| 122 /** |
| 123 * Renders all completed tasks |
| 124 */ |
| 125 Controller.prototype.showCompleted = function () { |
| 126 this.model.read({ completed: 1 }, function (data) { |
| 127 <strike>this.$todoList.innerHTML = this.view.show(data);</strike> |
| 128 <b>this.$todoList.innerHTML = this._parseForURLs(this.view.show(data));</b> |
| 129 }.bind(this)); |
| 130 }; |
| 131 </pre> |
| 132 |
| 133 <p>And finally, add <code>_parseForURLs()</code> to <code>editItem()</code>:</p> |
| 134 |
| 135 <pre data-filename="controller.js"> |
| 136 Controller.prototype.editItem = function (id, label) { |
| 137 ... |
| 138 var onSaveHandler = function () { |
| 139 ... |
| 140 // Instead of re-rendering the whole view just update |
| 141 // this piece of it |
| 142 <strike>label.innerHTML = value;</strike> |
| 143 <b>label.innerHTML = this._parseForURLs(value);</b> |
| 144 ... |
| 145 }.bind(this); |
| 146 ... |
| 147 } |
| 148 </pre> |
| 149 |
| 150 <p>Still in <code>editItem()</code>, fix the code so that it uses the |
| 151 <code>innerText</code> of the label instead of the label's <code>innerHTML</code
>:</p> |
| 152 |
| 153 <pre data-filename="controller.js"> |
| 154 Controller.prototype.editItem = function (id, label) { |
| 155 ... |
| 156 // Get the <strike>innerHTML</strike> <b>innerText</b> of the label instead of
requesting the data from the |
| 157 // ORM. If this were a real DB this would save a lot of time and would avoid |
| 158 // a spinner gif. |
| 159 <strike>input.value = label.innerHTML;</strike> |
| 160 <b>input.value = label.innerText;</b> |
| 161 ... |
| 162 } |
| 163 </pre> |
| 164 |
| 165 <h3 id="open-webview">Open new window containing webview</h3> |
| 166 |
| 167 <p>Add a <code>_doShowUrl()</code> method to <em>controller.js</em>. This method
will open a new Chrome App window |
| 168 via <a href="/apps/app_window.html#method-create">chrome.app.window.create()</a> |
| 169 with <em>webview.html</em> as the window source:</p> |
| 170 |
| 171 <pre data-filename="controller.js"> |
| 172 Controller.prototype._parseForURLs = function (text) { |
| 173 var re = /(https?:\/\/[^\s"<>,]+)/g; |
| 174 return text.replace(re, '<a href="$1" data-src="$1">$1</a>'); |
| 175 }; |
| 176 |
| 177 <b>Controller.prototype._doShowUrl = function(e) {</b> |
| 178 <b> // only applies to elements with data-src attributes</b> |
| 179 <b> if (!e.target.hasAttribute('data-src')) {</b> |
| 180 <b> return;</b> |
| 181 <b> }</b> |
| 182 <b> e.preventDefault();</b> |
| 183 <b> var url = e.target.getAttribute('data-src');</b> |
| 184 <b> chrome.app.window.create(</b> |
| 185 <b> 'webview.html',</b> |
| 186 <b> {hidden: true}, // only show window when webview is configured</b> |
| 187 <b> function(appWin) {</b> |
| 188 <b> appWin.contentWindow.addEventListener('DOMContentLoaded',</b> |
| 189 <b> function(e) {</b> |
| 190 <b> // when window is loaded, set webview source</b> |
| 191 <b> var webview = appWin.contentWindow.</b> |
| 192 <b> document.querySelector('webview');</b> |
| 193 <b> webview.src = url;</b> |
| 194 <b> // now we can show it:</b> |
| 195 <b> appWin.show();</b> |
| 196 <b> }</b> |
| 197 <b> );</b> |
| 198 <b> });</b> |
| 199 <b>};</b> |
| 200 |
| 201 // Export to window |
| 202 window.app.Controller = Controller; |
| 203 })(window); |
| 204 |
| 205 </pre> |
| 206 |
| 207 <p>In the <code>chrome.app.window.create()</code> callback, note how the webview
's URL is set via the <a href="/apps/tags/webview#tag"><code>src</code> tag attr
ibute</a>.</p> |
| 208 |
| 209 <p>Lastly, add a click event listener inside the <code>Controller</code> constru
ctor to call |
| 210 <code>doShowUrl()</code> when a user clicks on a link:</p> |
| 211 |
| 212 <pre data-filename="controller.js"> |
| 213 function Controller(model, view) { |
| 214 ... |
| 215 this.router = new Router(); |
| 216 this.router.init(); |
| 217 |
| 218 <b>this.$todoList.addEventListener('click', this._doShowUrl);</b> |
| 219 |
| 220 window.addEventListener('load', function () { |
| 221 this._updateFilterState(); |
| 222 }.bind(this)); |
| 223 ... |
| 224 } |
| 225 </pre> |
| 226 |
| 227 <h2 id="launch">Launch your finished Todo app</h2> |
| 228 |
| 229 <p>You are done Step 4! If you reload your app and add a todo item with a full U
RL |
| 230 starting with http:// or https://, you should see something like this:</p> |
| 231 |
| 232 <figure> |
| 233 <img src="{{static}}/images/app_codelab/step4-completed.gif" alt="The Todo app
with webview"/> |
| 234 </figure> |
| 235 |
| 236 <h2 id="recap">Recap APIs referenced in this step</h2> |
| 237 |
| 238 <p>For more detailed information about some of the APIs introduced in this step,
refer to:</p> |
| 239 |
| 240 <ul> |
| 241 <li> |
| 242 <a href="/apps/declare_permissions" title="Read 'Declare Permissions' in the
Chrome developer docs">Declare Permissions</a> |
| 243 <a href="#update-permissions" class="anchor-link-icon" title="This feature m
entioned in 'Update app permissions'">↑</a> |
| 244 </li> |
| 245 <li> |
| 246 <a href="/apps/tags/webview" title="Read '<webview> Tag' in the Chrome
developer docs"><webview> Tag</a> |
| 247 <a href="#overview" class="anchor-link-icon" title="This feature mentioned i
n 'Learn about the webview tag'">↑</a> |
| 248 <a href="#create-webview" class="anchor-link-icon" title="This feature menti
oned in 'Create a webview embedder page'">↑</a> |
| 249 </li> |
| 250 <li> |
| 251 <a href="/apps/app_window.html#method-create" title="Read 'chrome.app.window
.create()' in the Chrome developer docs">chrome.app.window.create()</a> |
| 252 <a href="#open-webview" class="anchor-link-icon" title="This feature mention
ed in 'Open new window containing webview'">↑</a> |
| 253 </li> |
| 254 <li> |
| 255 <a href="/apps/tags/webview#tag" title="Read '<webview> Tag attributes
' in the Chrome developer docs"><webview> Tag attributes</a> |
| 256 <a href="#open-webview" class="anchor-link-icon" title="This feature mention
ed in 'Open new window containing webview'">↑</a> |
| 257 </li> |
| 258 |
| 259 </ul> |
| 260 |
| 261 <p>Ready to continue onto the next step? Go to <a href="app_codelab_images.html"
>Step 5 - Add images from the web »</a></p> |
OLD | NEW |