2 <span class=
"h1-step">Step
4:
</span>
3 Open External Links With a Webview
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>.
11 <p>In this step, you will learn:
</p>
14 <li>How to show external web content inside your app in a secure and sandboxed way.
</li>
18 <em>Estimated time to complete this step:
10 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=
"overview">Learn about the webview tag
</h2>
25 <p>Some applications need to present external web content directly to the user but
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>
32 <img src=
"{{static}}/images/app_codelab/webview-example.png" alt=
"The Todo app using a webview">
35 <p class=
"note"><strong>Webviews are sandboxed processes:
</strong>
36 The enclosing Chrome App (also known as the
"embedder page")
37 cannot easily access the webview's loaded DOM.
38 You can only interact with the webview using its API.
</p>
40 <h2 id=
"implement-webview">Implement the webview tag
</h2>
42 <p>Update the Todo app to search for URLs in the todo item text and create a hyperlink.
43 The link, when clicked, opens a new Chrome App window
44 (not a browser tab) with a webview presenting the content.
</p>
46 <h3 id=
"update-permissions">Update permissions
</h3>
48 <p>In
<strong><em>manifest.json
</em></strong>, request the
<code>webview
</code> permission:
</p>
50 <pre data-filename=
"manifest.json">
51 "permissions": [
"storage",
"alarms",
"notifications"<b>,
"webview"</b>],
54 <h3 id=
"create-webview">Create a webview embedder page
</h3>
56 <p>Create a new file in the root of your project folder and name it
<strong><em>webview.html
</em></strong>.
57 This file is a basic webpage with one
<code><webview
></code> tag:
</p>
59 <pre data-filename=
"webview.html">
60 <b><!DOCTYPE html
>
63 <meta
charset=
"utf-8">
66 <webview
style=
"width: 100%; height: 100%;"></webview
>
71 <h3 id=
"parse-urls">Parse for URLs in todo items
</h3>
73 <p>At the end of
<strong><em>controller.js
</em></strong>, add a new method called
<code>_parseForURLs()
</code>:
</p>
75 <pre data-filename=
"controller.js">
76 Controller.prototype._getCurrentPage = function () {
77 return document.location.hash.split('/')[
1];
80 <b>Controller.prototype._parseForURLs = function (text) {
</b>
81 <b> var re = /(https?:\/\/[^\s
"<>,]+)/g;</b>
82 <b> return text.replace(re, '<a href="$
1" data-src="$
1">$1</a>');</b>
86 window.app.Controller = Controller;
90 <p>Whenever a string starting with "http://
" or "https://
" is found, a HTML anchor tag is created to wrap around the URL.</p>
92 <h3 id="render-links
">Render hyperlinks in the todo list</h3>
94 <p>Find <code>showAll()</code> in <em>controller.js</em>. Update <code>showAll()</code>
95 to parse for links by using the <code>_parseForURLs()</code> method added previously:</p>
97 <pre data-filename="controller.js
">
99 * An event to fire on load. Will get all items and display them in the
102 Controller.prototype.showAll = function () {
103 this.model.read(function (data) {
104 <strike>this.$todoList.innerHTML = this.view.show(data);</strike>
105 <b>this.$todoList.innerHTML = this._parseForURLs(this.view.show(data));</b>
110 <p>Do the same for <code>showActive()</code> and <code>showCompleted()</code>:</p>
112 <pre data-filename="controller.js
">
114 * Renders all active tasks
116 Controller.prototype.showActive = function () {
117 this.model.read({ completed: 0 }, function (data) {
118 <strike>this.$todoList.innerHTML = this.view.show(data);</strike>
119 <b>this.$todoList.innerHTML = this._parseForURLs(this.view.show(data));</b>
124 * Renders all completed tasks
126 Controller.prototype.showCompleted = function () {
127 this.model.read({ completed: 1 }, function (data) {
128 <strike>this.$todoList.innerHTML = this.view.show(data);</strike>
129 <b>this.$todoList.innerHTML = this._parseForURLs(this.view.show(data));</b>
134 <p>And finally, add <code>_parseForURLs()</code> to <code>editItem()</code>:</p>
136 <pre data-filename="controller.js
">
137 Controller.prototype.editItem = function (id, label) {
139 var onSaveHandler = function () {
141 // Instead of re-rendering the whole view just update
143 <strike>label.innerHTML = value;</strike>
144 <b>label.innerHTML = this._parseForURLs(value);</b>
151 <p>Still in <code>editItem()</code>, fix the code so that it uses the
152 <code>innerText</code> of the label instead of the label's <code>innerHTML</code>:</p>
154 <pre data-filename="controller.js
">
155 Controller.prototype.editItem = function (id, label) {
157 // Get the <strike>innerHTML</strike> <b>innerText</b> of the label instead of requesting the data from the
158 // ORM. If this were a real DB this would save a lot of time and would avoid
160 <strike>input.value = label.innerHTML;</strike>
161 <b>input.value = label.innerText;</b>
166 <h3 id="open-webview
">Open new window containing webview</h3>
168 <p>Add a <code>_doShowUrl()</code> method to <em>controller.js</em>. This method opens a new Chrome App window
169 via <a href="/apps/app_window.html#method-create
">chrome.app.window.create()</a>
170 with <em>webview.html</em> as the window source:</p>
172 <pre data-filename="controller.js
">
173 Controller.prototype._parseForURLs = function (text) {
174 var re = /(https?:\/\/[^\s"<>,]+)/g;
175 return text.replace(re, '
<a
href=
"$1" data-src=
"$1">$
1</a
>');
178 <b>Controller.prototype._doShowUrl = function(e) {
</b>
179 <b> // only applies to elements with data-src attributes
</b>
180 <b> if (!e.target.hasAttribute('data-src')) {
</b>
183 <b> e.preventDefault();
</b>
184 <b> var url = e.target.getAttribute('data-src');
</b>
185 <b> chrome.app.window.create(
</b>
186 <b> 'webview.html',
</b>
187 <b> {hidden: true}, // only show window when webview is configured
</b>
188 <b> function(appWin) {
</b>
189 <b> appWin.contentWindow.addEventListener('DOMContentLoaded',
</b>
190 <b> function(e) {
</b>
191 <b> // when window is loaded, set webview source
</b>
192 <b> var webview = appWin.contentWindow.
</b>
193 <b> document.querySelector('webview');
</b>
194 <b> webview.src = url;
</b>
195 <b> // now we can show it:
</b>
196 <b> appWin.show();
</b>
203 window.app.Controller = Controller;
208 <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 attribute
</a>.
</p>
210 <p>Lastly, add a click event listener inside the
<code>Controller
</code> constructor to call
211 <code>doShowUrl()
</code> when a user clicks on a link:
</p>
213 <pre data-filename=
"controller.js">
214 function Controller(model, view) {
216 this.router = new Router();
219 <b>this.$todoList.addEventListener('click', this._doShowUrl);
</b>
221 window.addEventListener('load', function () {
222 this._updateFilterState();
228 <h2 id=
"launch">Launch your finished Todo app
</h2>
230 <p>You are done Step
4! If you reload your app and add a todo item with a full URL
231 starting with http:// or https://, you should see something like this:
</p>
234 <img src=
"{{static}}/images/app_codelab/step4-completed.gif" alt=
"The Todo app with webview"/>
237 <h2 id=
"recap">For more information
</h2>
239 <p>For more detailed information about some of the APIs introduced in this step, refer to:
</p>
243 <a href=
"/apps/declare_permissions" title=
"Read 'Declare Permissions' in the Chrome developer docs">Declare Permissions
</a>
244 <a href=
"#update-permissions" class=
"anchor-link-icon" title=
"This feature mentioned in 'Update app permissions'">↑</a>
247 <a href=
"/apps/tags/webview" title=
"Read '<webview> Tag' in the Chrome developer docs"><webview
> Tag
</a>
248 <a href=
"#overview" class=
"anchor-link-icon" title=
"This feature mentioned in 'Learn about the webview tag'">↑</a>
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 mentioned in 'Open new window containing webview'">↑</a>
257 <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>