├── LICENSE
├── README.md
├── lab2_basic
├── icon.png
├── index.html
├── main.js
└── manifest.json
├── lab3_mvc
├── angularjs
│ ├── simpleview
│ │ ├── angular.min.js
│ │ ├── icon.png
│ │ ├── index.html
│ │ ├── main.js
│ │ ├── manifest.json
│ │ └── todo.css
│ └── withcontroller
│ │ ├── angular.min.js
│ │ ├── controller.js
│ │ ├── icon.png
│ │ ├── index.html
│ │ ├── main.js
│ │ ├── manifest.json
│ │ └── todo.css
└── javascript
│ ├── simpleview
│ ├── controller.js
│ ├── icon.png
│ ├── index.html
│ ├── main.js
│ ├── manifest.json
│ └── todo.css
│ └── withcontroller
│ ├── controller.js
│ ├── icon.png
│ ├── index.html
│ ├── main.js
│ ├── manifest.json
│ └── todo.css
├── lab5_data
├── angularjs
│ ├── 1_storage_sync
│ │ ├── angular.min.js
│ │ ├── controller.js
│ │ ├── icon.png
│ │ ├── index.html
│ │ ├── main.js
│ │ ├── manifest.json
│ │ └── todo.css
│ └── 2_drop_files
│ │ ├── angular.min.js
│ │ ├── controller.js
│ │ ├── icon.png
│ │ ├── index.html
│ │ ├── main.js
│ │ ├── manifest.json
│ │ └── todo.css
└── javascript
│ ├── 1_storage_sync
│ ├── controller.js
│ ├── icon.png
│ ├── index.html
│ ├── main.js
│ ├── manifest.json
│ └── todo.css
│ └── 2_drop_files
│ ├── controller.js
│ ├── icon.png
│ ├── index.html
│ ├── main.js
│ ├── manifest.json
│ └── todo.css
├── lab6_lifecycle
├── angularjs
│ ├── angular.min.js
│ ├── controller.js
│ ├── icon.png
│ ├── index.html
│ ├── main.js
│ ├── manifest.json
│ └── todo.css
└── javascript
│ ├── controller.js
│ ├── icon.png
│ ├── index.html
│ ├── main.js
│ ├── manifest.json
│ └── todo.css
├── lab7_useridentification
├── angularjs
│ ├── angular.min.js
│ ├── controller.js
│ ├── gapi_tasks.js
│ ├── icon.png
│ ├── index.html
│ ├── main.js
│ ├── manifest.json
│ └── todo.css
└── javascript
│ ├── controller.js
│ ├── gapi_tasks.js
│ ├── icon.png
│ ├── index.html
│ ├── main.js
│ ├── manifest.json
│ └── todo.css
├── lab8_webresources
├── angularjs
│ ├── 1_webview
│ │ ├── angular.min.js
│ │ ├── controller.js
│ │ ├── icon.png
│ │ ├── index.html
│ │ ├── main.js
│ │ ├── manifest.json
│ │ └── todo.css
│ └── 2_loading_resources
│ │ ├── angular.min.js
│ │ ├── controller.js
│ │ ├── icon.png
│ │ ├── index.html
│ │ ├── loader.js
│ │ ├── loading.gif
│ │ ├── main.js
│ │ ├── manifest.json
│ │ └── todo.css
└── javascript
│ ├── 1_webview
│ ├── controller.js
│ ├── icon.png
│ ├── index.html
│ ├── main.js
│ ├── manifest.json
│ └── todo.css
│ └── 2_loading_resources
│ ├── controller.js
│ ├── icon.png
│ ├── index.html
│ ├── loader.js
│ ├── loading.gif
│ ├── main.js
│ ├── manifest.json
│ └── todo.css
└── lab9_multipleviews
├── angularjs
├── angular.min.js
├── controller.js
├── droparea.html
├── droparea.js
├── icon.png
├── index.html
├── main.js
├── manifest.json
└── todo.css
└── javascript
├── controller.js
├── droparea.html
├── droparea.js
├── gapi_tasks.js
├── icon.png
├── index.html
├── loader.js
├── loading.gif
├── main.js
├── manifest.json
└── todo.css
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | LICENSE
2 | =======
3 |
4 | Copyright 2013 Google Inc. All Rights Reserved.
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 |
18 | Get Ready to Build Chrome Apps!
19 | ===============================
20 | Chrome apps are written in HTML5, JavaScript, and CSS, just like web apps.
21 | But they look and behave more like native apps, and they have super-powerful capabilities,
22 | like the ability to interact with network and hardware devices, media tools, and much more.
23 |
24 |
25 | > This repository contains only the code for the Codelab. Please, make sense of this code by following the step-by-step tutorial in the [Chrome Apps docs site](http://developer.chrome.com/trunk/apps/app_codelab.html)
26 |
--------------------------------------------------------------------------------
/lab2_basic/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChrome/chrome-app-codelab/78e1782ffe1fbfc2a25f8bf22b9c140be9891495/lab2_basic/icon.png
--------------------------------------------------------------------------------
/lab2_basic/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | My first Chrome App
6 |
7 |
8 | Hello, World!
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/lab2_basic/main.js:
--------------------------------------------------------------------------------
1 | chrome.app.runtime.onLaunched.addListener(function() {
2 | chrome.app.window.create('index.html',
3 | {width: 500, height: 309});
4 | });
5 |
6 |
--------------------------------------------------------------------------------
/lab2_basic/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Lab2 Simple app",
4 | "version": "1",
5 | "app": {
6 | "background": {
7 | "scripts": ["main.js"]
8 | }
9 | },
10 | "icons": { "128": "icon.png" }
11 | }
12 |
--------------------------------------------------------------------------------
/lab3_mvc/angularjs/simpleview/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChrome/chrome-app-codelab/78e1782ffe1fbfc2a25f8bf22b9c140be9891495/lab3_mvc/angularjs/simpleview/icon.png
--------------------------------------------------------------------------------
/lab3_mvc/angularjs/simpleview/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Todo
9 |
10 |
11 |
12 | {{todoText}}
13 |
14 |
15 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/lab3_mvc/angularjs/simpleview/main.js:
--------------------------------------------------------------------------------
1 | chrome.app.runtime.onLaunched.addListener(function() {
2 | chrome.app.window.create('index.html',
3 | {width: 500, height: 309});
4 | });
5 |
6 |
--------------------------------------------------------------------------------
/lab3_mvc/angularjs/simpleview/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Lab3a Simple MVC",
4 | "version": "1",
5 | "app": {
6 | "background": {
7 | "scripts": ["main.js"]
8 | }
9 | },
10 | "icons": { "128": "icon.png" }
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/lab3_mvc/angularjs/simpleview/todo.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
3 | }
4 |
5 | ul {
6 | list-style: none;
7 | }
8 |
9 | button, input[type=submit] {
10 | background-color: #0074CC;
11 | background-image: linear-gradient(top, #08C, #05C);
12 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
13 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
14 | color: white;
15 | }
16 |
17 | .done-true {
18 | text-decoration: line-through;
19 | color: grey;
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/lab3_mvc/angularjs/withcontroller/controller.js:
--------------------------------------------------------------------------------
1 | function TodoCtrl($scope) {
2 | $scope.todos = [
3 | {text:'learn angular', done:true},
4 | {text:'build an angular app', done:false}];
5 |
6 | $scope.addTodo = function() {
7 | $scope.todos.push({text:$scope.todoText, done:false});
8 | $scope.todoText = '';
9 | };
10 |
11 | $scope.remaining = function() {
12 | var count = 0;
13 | angular.forEach($scope.todos, function(todo) {
14 | count += todo.done ? 0 : 1;
15 | });
16 | return count;
17 | };
18 |
19 | $scope.archive = function() {
20 | var oldTodos = $scope.todos;
21 | $scope.todos = [];
22 | angular.forEach(oldTodos, function(todo) {
23 | if (!todo.done) $scope.todos.push(todo);
24 | });
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/lab3_mvc/angularjs/withcontroller/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChrome/chrome-app-codelab/78e1782ffe1fbfc2a25f8bf22b9c140be9891495/lab3_mvc/angularjs/withcontroller/icon.png
--------------------------------------------------------------------------------
/lab3_mvc/angularjs/withcontroller/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Todo
10 |
11 |
{{remaining()}} of {{todos.length}} remaining
12 | [
archive ]
13 |
19 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lab3_mvc/angularjs/withcontroller/main.js:
--------------------------------------------------------------------------------
1 | chrome.app.runtime.onLaunched.addListener(function() {
2 | chrome.app.window.create('index.html',
3 | {width: 500, height: 309});
4 | });
5 |
6 |
--------------------------------------------------------------------------------
/lab3_mvc/angularjs/withcontroller/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Lab3b MVC with controller",
4 | "version": "1",
5 | "app": {
6 | "background": {
7 | "scripts": ["main.js"]
8 | }
9 | },
10 | "icons": { "128": "icon.png" }
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/lab3_mvc/angularjs/withcontroller/todo.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
3 | }
4 |
5 | ul {
6 | list-style: none;
7 | }
8 |
9 | button, input[type=submit] {
10 | background-color: #0074CC;
11 | background-image: linear-gradient(top, #08C, #05C);
12 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
13 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
14 | color: white;
15 | }
16 |
17 | .done-true {
18 | text-decoration: line-through;
19 | color: grey;
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/lab3_mvc/javascript/simpleview/controller.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 |
4 | A poor man's controller - a very simple module with basic MVC functionaliies
5 |
6 | **/
7 | (function(exports) {
8 |
9 | var TodoModel = function() {
10 | this.contents = '';
11 | this.listeners = [];
12 | }
13 |
14 | TodoModel.prototype.setContents = function(contents) {
15 | if (this.contents !== contents) {
16 | this.contents = contents;
17 | this.notifyListeners();
18 | }
19 | }
20 |
21 | TodoModel.prototype.bindToInput = function(input) {
22 | var _this = this;
23 |
24 | var eventHandler = function(e) {
25 | if (typeof(e.target.value) != 'undefined') {
26 | _this.setContents(e.target.value);
27 | }
28 | };
29 |
30 | input.addEventListener('keyup', eventHandler);
31 | input.addEventListener('change', eventHandler);
32 | }
33 |
34 | TodoModel.prototype.bindToView = function(view) {
35 | this.addListener(function(model) {
36 | if (view.setValue) {
37 | view.setValue(model.contents);
38 | } else {
39 | view.innerText=model.contents;
40 | }
41 | });
42 | }
43 |
44 | TodoModel.prototype.addListener = function(listener) {
45 | this.listeners.push(listener);
46 | }
47 |
48 | TodoModel.prototype.notifyListeners = function() {
49 | var this_ = this;
50 | this.listeners.forEach(function(listener) {
51 | listener(this_);
52 | });
53 | }
54 |
55 | exports.TodoModel = TodoModel;
56 |
57 | })(window);
58 |
59 |
60 | window.addEventListener('DOMContentLoaded', function() {
61 |
62 | var model = new TodoModel();
63 | var newTodo = document.getElementById('newTodo');
64 | var todoText = document.getElementById('todoText');
65 |
66 | model.bindToView(todoText);
67 | model.bindToInput(newTodo);
68 |
69 | })
70 |
71 |
--------------------------------------------------------------------------------
/lab3_mvc/javascript/simpleview/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChrome/chrome-app-codelab/78e1782ffe1fbfc2a25f8bf22b9c140be9891495/lab3_mvc/javascript/simpleview/icon.png
--------------------------------------------------------------------------------
/lab3_mvc/javascript/simpleview/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Todo
8 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/lab3_mvc/javascript/simpleview/main.js:
--------------------------------------------------------------------------------
1 | chrome.app.runtime.onLaunched.addListener(function() {
2 | chrome.app.window.create('index.html',
3 | {width: 500, height: 309});
4 | });
5 |
6 |
--------------------------------------------------------------------------------
/lab3_mvc/javascript/simpleview/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Lab3a Simple MVC",
4 | "version": "1",
5 | "app": {
6 | "background": {
7 | "scripts": ["main.js"]
8 | }
9 | },
10 | "icons": { "128": "icon.png" }
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/lab3_mvc/javascript/simpleview/todo.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
3 | }
4 |
5 | ul {
6 | list-style: none;
7 | }
8 |
9 | button, input[type=submit] {
10 | background-color: #0074CC;
11 | background-image: linear-gradient(top, #08C, #05C);
12 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
13 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
14 | color: white;
15 | }
16 |
17 | .done-true {
18 | text-decoration: line-through;
19 | color: grey;
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/lab3_mvc/javascript/withcontroller/controller.js:
--------------------------------------------------------------------------------
1 | /**
2 |
3 | A poor man's controller - a very simple module with basic MVC functionaliies
4 |
5 | **/
6 | (function(exports) {
7 |
8 | var nextId = 1;
9 |
10 | var TodoModel = function() {
11 | this.todos = {};
12 | this.listeners = [];
13 | }
14 |
15 | TodoModel.prototype.clearTodos = function() {
16 | this.todos = {};
17 | this.notifyListeners('removed');
18 | }
19 |
20 | TodoModel.prototype.archiveDone = function() {
21 | var oldTodos = this.todos;
22 | this.todos={};
23 | for (var id in oldTodos) {
24 | if ( ! oldTodos[id].isDone ) {
25 | this.todos[id] = oldTodos[id];
26 | }
27 | }
28 | this.notifyListeners('archived');
29 | }
30 |
31 | TodoModel.prototype.setTodoState = function(id, isDone) {
32 | if ( this.todos[id].isDone != isDone ) {
33 | this.todos[id].isDone = isDone;
34 | this.notifyListeners('stateChanged', id);
35 | }
36 | }
37 |
38 | TodoModel.prototype.addTodo = function(text, isDone) {
39 | var id = nextId++;
40 | this.todos[id]={'id': id, 'text': text, 'isDone': isDone};
41 | this.notifyListeners('added', id);
42 | }
43 |
44 | TodoModel.prototype.addListener = function(listener) {
45 | this.listeners.push(listener);
46 | }
47 |
48 | TodoModel.prototype.notifyListeners = function(change, param) {
49 | var this_ = this;
50 | this.listeners.forEach(function(listener) {
51 | listener(this_, change, param);
52 | });
53 | }
54 |
55 | exports.TodoModel = TodoModel;
56 |
57 | })(window);
58 |
59 |
60 | window.addEventListener('DOMContentLoaded', function() {
61 |
62 | var model = new TodoModel();
63 | var form = document.querySelector('form');
64 | var archive = document.getElementById('archive');
65 | var list = document.getElementById('list');
66 | var todoTemplate = document.querySelector('#templates > [data-name="list"]');
67 |
68 | /**
69 | * When the form is submitted, we need to add a new todo and clear the input
70 | **/
71 | form.addEventListener('submit', function(e) {
72 | var textEl = e.target.querySelector('input[type="text"]');
73 | model.addTodo(textEl.value, false);
74 | textEl.value=null;
75 | e.preventDefault();
76 | });
77 |
78 |
79 | /**
80 | * A simple handler for the archive link
81 | **/
82 | archive.addEventListener('click', function(e) {
83 | model.archiveDone();
84 | e.preventDefault();
85 | });
86 |
87 |
88 | /**
89 | * Listen to changes in the model and trigger the appropriate changes in the view
90 | **/
91 | model.addListener(function(model, changeType, param) {
92 | if ( changeType === 'removed' || changeType === 'archived') {
93 | redrawUI(model);
94 | } else if ( changeType === 'added' ) {
95 | drawTodo(model.todos[param], list);
96 | } else if ( changeType === 'stateChanged') {
97 | updateTodo(model.todos[param]);
98 | }
99 | updateCounters(model);
100 | });
101 |
102 | /**
103 | * Clean the current ToDo list and add elements again
104 | **/
105 | var redrawUI = function(model) {
106 | list.innerHTML='';
107 | for (var id in model.todos) {
108 | drawTodo(model.todos[id], list);
109 | }
110 | };
111 |
112 | /**
113 | * Deep clone a template node, set its ID and add it to the DOM container.
114 | * Add a listener to the newly added checkbox, so it can trigger the state flip
115 | * when the checkbox is clicked.
116 | **/
117 | var drawTodo = function(todoObj, container) {
118 | var el = todoTemplate.cloneNode(true);
119 | el.setAttribute('data-id', todoObj.id);
120 | container.appendChild(el);
121 | updateTodo(todoObj);
122 | var checkbox = el.querySelector('input[type="checkbox"]');
123 | checkbox.addEventListener('change', function(e) {
124 | model.setTodoState(todoObj.id, e.target.checked);
125 | });
126 | }
127 |
128 |
129 | /**
130 | * Find the element corresponding to the given ToDo model object and update its
131 | * state and description from the model.
132 | */
133 | var updateTodo = function(model) {
134 | var todoElement = list.querySelector('li[data-id="'+model.id+'"]');
135 | if (todoElement) {
136 | var checkbox = todoElement.querySelector('input[type="checkbox"]');
137 | var desc = todoElement.querySelector('span');
138 | checkbox.checked = model.isDone;
139 | desc.innerText = model.text;
140 | desc.className = "done-"+model.isDone;
141 | }
142 | }
143 |
144 | /**
145 | * Recalculate total number of ToDos and remaining ToDos and update
146 | * appropriate elements in the DOM.
147 | **/
148 | var updateCounters = function(model) {
149 | var count = 0;
150 | var notDone = 0;
151 | for (var id in model.todos) {
152 | count++;
153 | if ( ! model.todos[id].isDone ) {
154 | notDone ++;
155 | }
156 | }
157 | document.getElementById('remaining').innerText = notDone;
158 | document.getElementById('length').innerText = count;
159 | }
160 |
161 | updateCounters(model);
162 |
163 | });
164 |
165 |
--------------------------------------------------------------------------------
/lab3_mvc/javascript/withcontroller/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChrome/chrome-app-codelab/78e1782ffe1fbfc2a25f8bf22b9c140be9891495/lab3_mvc/javascript/withcontroller/icon.png
--------------------------------------------------------------------------------
/lab3_mvc/javascript/withcontroller/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Todo
8 |
9 |
of remaining
10 | [
archive ]
11 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/lab3_mvc/javascript/withcontroller/main.js:
--------------------------------------------------------------------------------
1 | chrome.app.runtime.onLaunched.addListener(function() {
2 | chrome.app.window.create('index.html',
3 | {width: 500, height: 309});
4 | });
5 |
6 |
--------------------------------------------------------------------------------
/lab3_mvc/javascript/withcontroller/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Lab3b MVC with controller",
4 | "version": "1",
5 | "app": {
6 | "background": {
7 | "scripts": ["main.js"]
8 | }
9 | },
10 | "icons": { "128": "icon.png" }
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/lab3_mvc/javascript/withcontroller/todo.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
3 | }
4 |
5 | ul {
6 | list-style: none;
7 | }
8 |
9 | button, input[type=submit] {
10 | background-color: #0074CC;
11 | background-image: linear-gradient(top, #08C, #05C);
12 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
13 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
14 | color: white;
15 | }
16 |
17 | .done-true {
18 | text-decoration: line-through;
19 | color: grey;
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/lab5_data/angularjs/1_storage_sync/controller.js:
--------------------------------------------------------------------------------
1 | function TodoCtrl($scope) {
2 |
3 | // Notice that chrome.storage.sync.get is asynchronous
4 | chrome.storage.sync.get('todolist', function(value) {
5 | // The $apply is only necessary to execute the function inside Angular scope
6 | $scope.$apply(function() {
7 | $scope.load(value);
8 | });
9 | });
10 |
11 | // If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
12 | $scope.load = function(value) {
13 | if (value && value.todolist) {
14 | $scope.todos = value.todolist;
15 | } else {
16 | $scope.todos = [
17 | {text:'learn angular', done:true},
18 | {text:'build an angular app', done:false}];
19 | }
20 | }
21 |
22 | $scope.save = function() {
23 | chrome.storage.sync.set({'todolist': $scope.todos});
24 | };
25 |
26 | $scope.addTodo = function() {
27 | $scope.todos.push({text:$scope.todoText, done:false});
28 | $scope.todoText = '';
29 | };
30 |
31 | $scope.remaining = function() {
32 | var count = 0;
33 | angular.forEach($scope.todos, function(todo) {
34 | count += todo.done ? 0 : 1;
35 | });
36 | return count;
37 | };
38 |
39 | $scope.archive = function() {
40 | var oldTodos = $scope.todos;
41 | $scope.todos = [];
42 | angular.forEach(oldTodos, function(todo) {
43 | if (!todo.done) $scope.todos.push(todo);
44 | });
45 | };
46 | }
47 |
--------------------------------------------------------------------------------
/lab5_data/angularjs/1_storage_sync/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChrome/chrome-app-codelab/78e1782ffe1fbfc2a25f8bf22b9c140be9891495/lab5_data/angularjs/1_storage_sync/icon.png
--------------------------------------------------------------------------------
/lab5_data/angularjs/1_storage_sync/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Todo
10 |
11 |
{{remaining()}} of {{todos.length}} remaining
12 | [
archive ]
13 |
19 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lab5_data/angularjs/1_storage_sync/main.js:
--------------------------------------------------------------------------------
1 | chrome.app.runtime.onLaunched.addListener(function() {
2 | chrome.app.window.create('index.html',
3 | {width: 500, height: 309});
4 | });
5 |
6 |
--------------------------------------------------------------------------------
/lab5_data/angularjs/1_storage_sync/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Lab5a Storage sync",
4 | "version": "1",
5 | "app": {
6 | "background": {
7 | "scripts": ["main.js"]
8 | }
9 | },
10 | "permissions": ["storage"],
11 | "icons": { "128": "icon.png" }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/lab5_data/angularjs/1_storage_sync/todo.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
3 | }
4 |
5 | ul {
6 | list-style: none;
7 | max-height: 120px;
8 | overflow-y: auto;
9 | }
10 |
11 | button, input[type=submit] {
12 | background-color: #0074CC;
13 | background-image: linear-gradient(top, #08C, #05C);
14 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
15 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
16 | color: white;
17 | }
18 |
19 | .done-true {
20 | text-decoration: line-through;
21 | color: grey;
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/lab5_data/angularjs/2_drop_files/controller.js:
--------------------------------------------------------------------------------
1 | function TodoCtrl($scope) {
2 |
3 | // Notice that chrome.storage.sync.get is asynchronous
4 | chrome.storage.sync.get('todolist', function(value) {
5 | // The $apply is only necessary to execute the function inside Angular scope
6 | $scope.$apply(function() {
7 | $scope.load(value);
8 | });
9 | });
10 |
11 | var defaultDropText = "Or drop files here...";
12 | $scope.dropText = defaultDropText;
13 |
14 | // on dragOver, we will change the style and text accordingly, depending on
15 | // the data being transfered
16 | var dragOver = function(e) {
17 | e.stopPropagation();
18 | e.preventDefault();
19 | var valid = e.dataTransfer && e.dataTransfer.types
20 | && ( e.dataTransfer.types.indexOf('Files') >= 0
21 | || e.dataTransfer.types.indexOf('text/uri-list') >=0 )
22 | $scope.$apply(function() {
23 | $scope.dropText = valid ? "Drop files and remote images and they will become Todos"
24 | : "Can only drop files and remote images here";
25 | $scope.dropClass = valid ? "dragging" : "invalid-dragging";
26 | });
27 | }
28 |
29 | // reset style and text to the default
30 | var dragLeave = function(e) {
31 | $scope.$apply(function() {
32 | $scope.dropText = defaultDropText;
33 | $scope.dropClass = '';
34 | });
35 | }
36 |
37 | // on drop, we create the appropriate TODOs using dropped data
38 | var drop = function(e) {
39 | e.preventDefault();
40 | e.stopPropagation();
41 |
42 | var newTodos=[];
43 | if (e.dataTransfer.types.indexOf('Files') >= 0) {
44 | var files = e.dataTransfer.files;
45 | for (var i = 0; i < files.length; i++) {
46 | var text = files[i].name+', '+files[i].size+' bytes';
47 | newTodos.push({text:text, done:false, file: files[i]});
48 | }
49 | } else { // uris
50 | var uri=e.dataTransfer.getData("text/uri-list");
51 | if (/\.png$/.test(uri)) {
52 |
53 | }
54 | newTodos.push({text:uri, done:false, uri: uri});
55 | }
56 |
57 | $scope.$apply(function() {
58 | $scope.dropText = defaultDropText;
59 | $scope.dropClass = '';
60 | for (var i = 0; i < newTodos.length; i++) {
61 | $scope.todos.push(newTodos[i]);
62 | }
63 | $scope.save();
64 | });
65 | }
66 |
67 | document.body.addEventListener("dragover", dragOver, false);
68 | document.body.addEventListener("dragleave", dragLeave, false);
69 | document.body.addEventListener("drop", drop, false);
70 |
71 | // If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
72 | $scope.load = function(value) {
73 | if (value && value.todolist) {
74 | $scope.todos = value.todolist;
75 | } else {
76 | $scope.todos = [
77 | {text:'learn angular', done:true},
78 | {text:'build an angular app', done:false}];
79 | }
80 | }
81 |
82 | $scope.save = function() {
83 | chrome.storage.sync.set({'todolist': $scope.todos});
84 | };
85 |
86 | $scope.addTodo = function() {
87 | $scope.todos.push({text:$scope.todoText, done:false});
88 | $scope.todoText = '';
89 | };
90 |
91 | $scope.remaining = function() {
92 | var count = 0;
93 | angular.forEach($scope.todos, function(todo) {
94 | count += todo.done ? 0 : 1;
95 | });
96 | return count;
97 | };
98 |
99 | $scope.archive = function() {
100 | var oldTodos = $scope.todos;
101 | $scope.todos = [];
102 | angular.forEach(oldTodos, function(todo) {
103 | if (!todo.done) $scope.todos.push(todo);
104 | });
105 | };
106 | }
107 |
--------------------------------------------------------------------------------
/lab5_data/angularjs/2_drop_files/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChrome/chrome-app-codelab/78e1782ffe1fbfc2a25f8bf22b9c140be9891495/lab5_data/angularjs/2_drop_files/icon.png
--------------------------------------------------------------------------------
/lab5_data/angularjs/2_drop_files/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Todo
10 |
11 |
{{remaining()}} of {{todos.length}} remaining
12 | [
archive ]
13 |
19 |
24 |
25 | {{dropText}}
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/lab5_data/angularjs/2_drop_files/main.js:
--------------------------------------------------------------------------------
1 | chrome.app.runtime.onLaunched.addListener(function() {
2 | chrome.app.window.create('index.html',
3 | {width: 500, height: 309});
4 | });
5 |
6 |
--------------------------------------------------------------------------------
/lab5_data/angularjs/2_drop_files/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Lab5b Drop files",
4 | "version": "1",
5 | "app": {
6 | "background": {
7 | "scripts": ["main.js"]
8 | }
9 | },
10 | "permissions": ["storage"],
11 | "icons": { "128": "icon.png" }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/lab5_data/angularjs/2_drop_files/todo.css:
--------------------------------------------------------------------------------
1 | html {
2 | height: 100%;
3 | overflow: hidden;
4 | }
5 |
6 | body {
7 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
8 | height: 100%;
9 | }
10 |
11 | @-webkit-keyframes switch-green {
12 | from { background-color: white;} to {background-color: rgb(163, 255, 163);}
13 | }
14 | @-webkit-keyframes switch-red {
15 | from { background-color: white;} to {background-color: rgb(255, 203, 203);}
16 | }
17 | .dragging {
18 | -webkit-animation: switch-green 0.5s ease-in-out 0 infinite alternate;
19 | }
20 |
21 | .invalid-dragging {
22 | -webkit-animation: switch-red 0.5s ease-in-out 0 infinite alternate;
23 | }
24 |
25 | ul {
26 | list-style: none;
27 | max-height: 120px;
28 | overflow-y: auto;
29 | }
30 |
31 | button, input[type=submit] {
32 | background-color: #0074CC;
33 | background-image: linear-gradient(top, #08C, #05C);
34 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
35 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
36 | color: white;
37 | }
38 |
39 | .done-true {
40 | text-decoration: line-through;
41 | color: grey;
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/lab5_data/javascript/1_storage_sync/controller.js:
--------------------------------------------------------------------------------
1 | /**
2 |
3 | A poor man's controller - a very simple module with basic MVC functionaliies
4 |
5 | **/
6 | (function(exports) {
7 |
8 | var nextId = 1;
9 |
10 | var TodoModel = function() {
11 | this.todos = {};
12 | this.listeners = [];
13 | }
14 |
15 | TodoModel.prototype.setTodos = function(todos) {
16 | this.todos = todos;
17 | var maxId = 0;
18 | for (var id in todos) {
19 | var idInt = parseInt(id);
20 | maxId = idInt > maxId ? idInt : maxId;
21 | }
22 | nextId = maxId + 1;
23 | this.notifyListeners('reset');
24 | }
25 |
26 | TodoModel.prototype.clearTodos = function() {
27 | this.todos = {};
28 | this.notifyListeners('removed');
29 | }
30 |
31 | TodoModel.prototype.archiveDone = function() {
32 | var oldTodos = this.todos;
33 | this.todos={};
34 | for (var id in oldTodos) {
35 | if ( ! oldTodos[id].isDone ) {
36 | this.todos[id] = oldTodos[id];
37 | }
38 | }
39 | this.notifyListeners('archived');
40 | }
41 |
42 | TodoModel.prototype.setTodoState = function(id, isDone) {
43 | if ( this.todos[id].isDone != isDone ) {
44 | this.todos[id].isDone = isDone;
45 | this.notifyListeners('stateChanged', id);
46 | }
47 | }
48 |
49 | TodoModel.prototype.addTodo = function(text, isDone) {
50 | var id = nextId++;
51 | this.todos[id]={'id': id, 'text': text, 'isDone': isDone};
52 | this.notifyListeners('added', id);
53 | }
54 |
55 | TodoModel.prototype.addListener = function(listener) {
56 | this.listeners.push(listener);
57 | }
58 |
59 | TodoModel.prototype.notifyListeners = function(change, param) {
60 | var this_ = this;
61 | this.listeners.forEach(function(listener) {
62 | listener(this_, change, param);
63 | });
64 | }
65 |
66 | exports.TodoModel = TodoModel;
67 |
68 | })(window);
69 |
70 |
71 | window.addEventListener('DOMContentLoaded', function() {
72 |
73 | var model = new TodoModel();
74 | var form = document.querySelector('form');
75 | var archive = document.getElementById('archive');
76 | var list = document.getElementById('list');
77 | var todoTemplate = document.querySelector('#templates > [data-name="list"]');
78 |
79 | /**
80 | * When the form is submitted, we need to add a new todo and clear the input
81 | **/
82 | form.addEventListener('submit', function(e) {
83 | var textEl = e.target.querySelector('input[type="text"]');
84 | model.addTodo(textEl.value, false);
85 | textEl.value=null;
86 | e.preventDefault();
87 | });
88 |
89 |
90 | /**
91 | * A simple handler for the archive link
92 | **/
93 | archive.addEventListener('click', function(e) {
94 | model.archiveDone();
95 | e.preventDefault();
96 | });
97 |
98 |
99 | /**
100 | * Listen to changes in the model and trigger the appropriate changes in the view
101 | **/
102 | model.addListener(function(model, changeType, param) {
103 | if ( changeType === 'removed' || changeType === 'archived' || changeType === 'reset') {
104 | redrawUI(model);
105 | } else if ( changeType === 'added' ) {
106 | drawTodo(model.todos[param], list);
107 | } else if ( changeType === 'stateChanged') {
108 | updateTodo(model.todos[param]);
109 | }
110 | storageSave();
111 | updateCounters(model);
112 | });
113 |
114 |
115 | // If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
116 | var storageLoad = function() {
117 | chrome.storage.sync.get('todolist', function(value) {
118 | if (value && value.todolist) {
119 | model.setTodos(value.todolist);
120 | } else {
121 | model.addTodo('learn Chrome Apps', true);
122 | model.addTodo('build a Chrome App', false);
123 | }
124 | });
125 | }
126 |
127 | var storageSave = function() {
128 | chrome.storage.sync.set({'todolist': model.todos});
129 | };
130 |
131 | /**
132 | * Clean the current ToDo list and add elements again
133 | **/
134 | var redrawUI = function(model) {
135 | list.innerHTML='';
136 | for (var id in model.todos) {
137 | drawTodo(model.todos[id], list);
138 | }
139 | };
140 |
141 | /**
142 | * Deep clone a template node, set its ID and add it to the DOM container.
143 | * Add a listener to the newly added checkbox, so it can trigger the state flip
144 | * when the checkbox is clicked.
145 | **/
146 | var drawTodo = function(todoObj, container) {
147 | var el = todoTemplate.cloneNode(true);
148 | el.setAttribute('data-id', todoObj.id);
149 | container.appendChild(el);
150 | updateTodo(todoObj);
151 | var checkbox = el.querySelector('input[type="checkbox"]');
152 | checkbox.addEventListener('change', function(e) {
153 | model.setTodoState(todoObj.id, e.target.checked);
154 | });
155 | }
156 |
157 |
158 | /**
159 | * Find the element corresponding to the given ToDo model object and update its
160 | * state and description from the model.
161 | */
162 | var updateTodo = function(model) {
163 | var todoElement = list.querySelector('li[data-id="'+model.id+'"]');
164 | if (todoElement) {
165 | var checkbox = todoElement.querySelector('input[type="checkbox"]');
166 | var desc = todoElement.querySelector('span');
167 | checkbox.checked = model.isDone;
168 | desc.innerText = model.text;
169 | desc.className = "done-"+model.isDone;
170 | }
171 | }
172 |
173 | /**
174 | * Recalculate total number of ToDos and remaining ToDos and update
175 | * appropriate elements in the DOM.
176 | **/
177 | var updateCounters = function(model) {
178 | var count = 0;
179 | var notDone = 0;
180 | for (var id in model.todos) {
181 | count++;
182 | if ( ! model.todos[id].isDone ) {
183 | notDone ++;
184 | }
185 | }
186 | document.getElementById('remaining').innerText = notDone;
187 | document.getElementById('length').innerText = count;
188 | }
189 |
190 | storageLoad();
191 |
192 | });
193 |
194 |
--------------------------------------------------------------------------------
/lab5_data/javascript/1_storage_sync/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChrome/chrome-app-codelab/78e1782ffe1fbfc2a25f8bf22b9c140be9891495/lab5_data/javascript/1_storage_sync/icon.png
--------------------------------------------------------------------------------
/lab5_data/javascript/1_storage_sync/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Todo
8 |
9 |
of remaining
10 | [
archive ]
11 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/lab5_data/javascript/1_storage_sync/main.js:
--------------------------------------------------------------------------------
1 | chrome.app.runtime.onLaunched.addListener(function() {
2 | chrome.app.window.create('index.html',
3 | {width: 500, height: 309});
4 | });
5 |
6 |
--------------------------------------------------------------------------------
/lab5_data/javascript/1_storage_sync/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Lab5a Storage sync",
4 | "version": "1",
5 | "app": {
6 | "background": {
7 | "scripts": ["main.js"]
8 | }
9 | },
10 | "permissions": ["storage"],
11 | "icons": { "128": "icon.png" }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/lab5_data/javascript/1_storage_sync/todo.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
3 | }
4 |
5 | ul {
6 | list-style: none;
7 | max-height: 120px;
8 | overflow-y: auto;
9 | }
10 |
11 | button, input[type=submit] {
12 | background-color: #0074CC;
13 | background-image: linear-gradient(top, #08C, #05C);
14 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
15 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
16 | color: white;
17 | }
18 |
19 | .done-true {
20 | text-decoration: line-through;
21 | color: grey;
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/lab5_data/javascript/2_drop_files/controller.js:
--------------------------------------------------------------------------------
1 | /**
2 |
3 | A poor man's controller - a very simple module with basic MVC functionaliies
4 |
5 | **/
6 | (function(exports) {
7 |
8 | var nextId = 1;
9 |
10 | var TodoModel = function() {
11 | this.todos = {};
12 | this.listeners = [];
13 | }
14 |
15 | TodoModel.prototype.setTodos = function(todos) {
16 | this.todos = todos;
17 | var maxId = 0;
18 | for (var id in todos) {
19 | var idInt = parseInt(id);
20 | maxId = idInt > maxId ? idInt : maxId;
21 | }
22 | nextId = maxId + 1;
23 | this.notifyListeners('reset');
24 | }
25 |
26 | TodoModel.prototype.clearTodos = function() {
27 | this.todos = {};
28 | this.notifyListeners('removed');
29 | }
30 |
31 | TodoModel.prototype.archiveDone = function() {
32 | var oldTodos = this.todos;
33 | this.todos={};
34 | for (var id in oldTodos) {
35 | if ( ! oldTodos[id].isDone ) {
36 | this.todos[id] = oldTodos[id];
37 | }
38 | }
39 | this.notifyListeners('archived');
40 | }
41 |
42 | TodoModel.prototype.setTodoState = function(id, isDone) {
43 | if ( this.todos[id].isDone != isDone ) {
44 | this.todos[id].isDone = isDone;
45 | this.notifyListeners('stateChanged', id);
46 | }
47 | }
48 |
49 | TodoModel.prototype.addTodo = function(text, isDone, extras) {
50 | var id = nextId++;
51 | this.todos[id]={'id': id, 'text': text, 'isDone': isDone, 'extras': extras};
52 | this.notifyListeners('added', id);
53 | }
54 |
55 | TodoModel.prototype.addListener = function(listener) {
56 | this.listeners.push(listener);
57 | }
58 |
59 | TodoModel.prototype.notifyListeners = function(change, param) {
60 | var this_ = this;
61 | this.listeners.forEach(function(listener) {
62 | listener(this_, change, param);
63 | });
64 | }
65 |
66 | exports.TodoModel = TodoModel;
67 |
68 | })(window);
69 |
70 |
71 |
72 | (function(exports) {
73 |
74 | var defaultDropText = "Or drop files here...";
75 |
76 | var dropText = document.getElementById('dropText');
77 | dropText.innerText = defaultDropText;
78 |
79 | // on dragOver, we will change the style and text accordingly, depending on
80 | // the data being transfered
81 | var dragOver = function(e) {
82 | e.stopPropagation();
83 | e.preventDefault();
84 | var valid = isValid(e.dataTransfer);
85 | if (valid) {
86 | dropText.innerText="Drop files and remote images and they will become Todos";
87 | document.body.classList.add("dragging");
88 | } else {
89 | dropText.innerText="Can only drop files and remote images here";
90 | document.body.classList.add("invalid-dragging");
91 | }
92 | }
93 |
94 | var isValid = function(dataTransfer) {
95 | return dataTransfer && dataTransfer.types
96 | && ( dataTransfer.types.indexOf('Files') >= 0
97 | || dataTransfer.types.indexOf('text/uri-list') >=0 )
98 | }
99 |
100 | // reset style and text to the default
101 | var dragLeave = function(e) {
102 | dropText.innerText=defaultDropText;
103 | document.body.classList.remove('dragging');
104 | document.body.classList.remove('invalid-dragging');
105 | }
106 |
107 | // on drop, we create the appropriate TODOs using dropped data
108 | var drop = function(e, model) {
109 | e.preventDefault();
110 | e.stopPropagation();
111 | if (isValid(e.dataTransfer)) {
112 | if (e.dataTransfer.types.indexOf('Files') >= 0) {
113 | var files = e.dataTransfer.files;
114 | for (var i = 0; i < files.length; i++) {
115 | var text = files[i].name+', '+files[i].size+' bytes';
116 | model.addTodo(text, false, {file: files[i]});
117 | }
118 | } else { // uris
119 | var uri=e.dataTransfer.getData("text/uri-list");
120 | model.addTodo(uri, false, {uri: uri});
121 | }
122 | }
123 |
124 | dragLeave();
125 | }
126 |
127 | exports.setDragHandlers = function(model) {
128 | document.body.addEventListener("dragover", dragOver, false);
129 | document.body.addEventListener("dragleave", dragLeave, false);
130 | document.body.addEventListener("drop", function(e) {
131 | drop(e, model);
132 | }, false);
133 | }
134 |
135 | })(window);
136 |
137 |
138 | window.addEventListener('DOMContentLoaded', function() {
139 |
140 | var model = new TodoModel();
141 | var form = document.querySelector('form');
142 | var archive = document.getElementById('archive');
143 | var list = document.getElementById('list');
144 | var todoTemplate = document.querySelector('#templates > [data-name="list"]');
145 |
146 | /**
147 | * When the form is submitted, we need to add a new todo and clear the input
148 | **/
149 | form.addEventListener('submit', function(e) {
150 | var textEl = e.target.querySelector('input[type="text"]');
151 | model.addTodo(textEl.value, false);
152 | textEl.value=null;
153 | e.preventDefault();
154 | });
155 |
156 |
157 | /**
158 | * A simple handler for the archive link
159 | **/
160 | archive.addEventListener('click', function(e) {
161 | model.archiveDone();
162 | e.preventDefault();
163 | });
164 |
165 |
166 | /**
167 | * Listen to changes in the model and trigger the appropriate changes in the view
168 | **/
169 | model.addListener(function(model, changeType, param) {
170 | if ( changeType === 'removed' || changeType === 'archived' || changeType === 'reset') {
171 | redrawUI(model);
172 | } else if ( changeType === 'added' ) {
173 | drawTodo(model.todos[param], list);
174 | } else if ( changeType === 'stateChanged') {
175 | updateTodo(model.todos[param]);
176 | }
177 | storageSave();
178 | updateCounters(model);
179 | });
180 |
181 |
182 | // If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
183 | var storageLoad = function() {
184 | chrome.storage.sync.get('todolist', function(value) {
185 | if (value && value.todolist) {
186 | model.setTodos(value.todolist);
187 | } else {
188 | model.addTodo('learn Chrome Apps', true);
189 | model.addTodo('build a Chrome App', false);
190 | }
191 | });
192 | }
193 |
194 | var storageSave = function() {
195 | chrome.storage.sync.set({'todolist': model.todos});
196 | };
197 |
198 | /**
199 | * Clean the current ToDo list and add elements again
200 | **/
201 | var redrawUI = function(model) {
202 | list.innerHTML='';
203 | for (var id in model.todos) {
204 | drawTodo(model.todos[id], list);
205 | }
206 | };
207 |
208 | /**
209 | * Deep clone a template node, set its ID and add it to the DOM container.
210 | * Add a listener to the newly added checkbox, so it can trigger the state flip
211 | * when the checkbox is clicked.
212 | **/
213 | var drawTodo = function(todoObj, container) {
214 | var el = todoTemplate.cloneNode(true);
215 | el.setAttribute('data-id', todoObj.id);
216 | container.appendChild(el);
217 | updateTodo(todoObj);
218 | var checkbox = el.querySelector('input[type="checkbox"]');
219 | checkbox.addEventListener('change', function(e) {
220 | model.setTodoState(todoObj.id, e.target.checked);
221 | });
222 | }
223 |
224 |
225 | /**
226 | * Find the element corresponding to the given ToDo model object and update its
227 | * state and description from the model.
228 | */
229 | var updateTodo = function(model) {
230 | var todoElement = list.querySelector('li[data-id="'+model.id+'"]');
231 | if (todoElement) {
232 | var checkbox = todoElement.querySelector('input[type="checkbox"]');
233 | var desc = todoElement.querySelector('span');
234 | checkbox.checked = model.isDone;
235 | desc.innerText = model.text;
236 | desc.className = "done-"+model.isDone;
237 | }
238 | }
239 |
240 | /**
241 | * Recalculate total number of ToDos and remaining ToDos and update
242 | * appropriate elements in the DOM.
243 | **/
244 | var updateCounters = function(model) {
245 | var count = 0;
246 | var notDone = 0;
247 | for (var id in model.todos) {
248 | count++;
249 | if ( ! model.todos[id].isDone ) {
250 | notDone ++;
251 | }
252 | }
253 | document.getElementById('remaining').innerText = notDone;
254 | document.getElementById('length').innerText = count;
255 | }
256 |
257 | storageLoad();
258 | setDragHandlers(model);
259 |
260 | });
261 |
262 |
--------------------------------------------------------------------------------
/lab5_data/javascript/2_drop_files/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChrome/chrome-app-codelab/78e1782ffe1fbfc2a25f8bf22b9c140be9891495/lab5_data/javascript/2_drop_files/icon.png
--------------------------------------------------------------------------------
/lab5_data/javascript/2_drop_files/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Todo
8 |
9 |
of remaining
10 | [
archive ]
11 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/lab5_data/javascript/2_drop_files/main.js:
--------------------------------------------------------------------------------
1 | chrome.app.runtime.onLaunched.addListener(function() {
2 | chrome.app.window.create('index.html',
3 | {width: 500, height: 309});
4 | });
5 |
6 |
--------------------------------------------------------------------------------
/lab5_data/javascript/2_drop_files/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Lab5b Drop files",
4 | "version": "1",
5 | "app": {
6 | "background": {
7 | "scripts": ["main.js"]
8 | }
9 | },
10 | "permissions": ["storage"],
11 | "icons": { "128": "icon.png" }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/lab5_data/javascript/2_drop_files/todo.css:
--------------------------------------------------------------------------------
1 | html {
2 | height: 100%;
3 | overflow: hidden;
4 | }
5 |
6 | body {
7 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
8 | height: 100%;
9 | }
10 |
11 | @-webkit-keyframes switch-green {
12 | from { background-color: white;} to {background-color: rgb(163, 255, 163);}
13 | }
14 | @-webkit-keyframes switch-red {
15 | from { background-color: white;} to {background-color: rgb(255, 203, 203);}
16 | }
17 | .dragging {
18 | -webkit-animation: switch-green 0.5s ease-in-out 0 infinite alternate;
19 | }
20 |
21 | .invalid-dragging {
22 | -webkit-animation: switch-red 0.5s ease-in-out 0 infinite alternate;
23 | }
24 |
25 | ul {
26 | list-style: none;
27 | max-height: 120px;
28 | overflow-y: auto;
29 | }
30 |
31 | button, input[type=submit] {
32 | background-color: #0074CC;
33 | background-image: linear-gradient(top, #08C, #05C);
34 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
35 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
36 | color: white;
37 | }
38 |
39 | .done-true {
40 | text-decoration: line-through;
41 | color: grey;
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/lab6_lifecycle/angularjs/controller.js:
--------------------------------------------------------------------------------
1 | function TodoCtrl($scope) {
2 |
3 | // Notice that chrome.storage.sync.get is asynchronous
4 | chrome.storage.sync.get('todolist', function(value) {
5 | // The $apply is only necessary to execute the function inside Angular scope
6 | $scope.$apply(function() {
7 | $scope.load(value);
8 | });
9 | });
10 |
11 | var defaultDropText = "Or drop files here...";
12 | $scope.dropText = defaultDropText;
13 |
14 | // on dragOver, we will change the style and text accordingly, depending on
15 | // the data being transfered
16 | var dragOver = function(e) {
17 | e.stopPropagation();
18 | e.preventDefault();
19 | var valid = e.dataTransfer && e.dataTransfer.types
20 | && ( e.dataTransfer.types.indexOf('Files') >= 0
21 | || e.dataTransfer.types.indexOf('text/uri-list') >=0 )
22 | $scope.$apply(function() {
23 | $scope.dropText = valid ? "Drop files and remote images and they will become Todos"
24 | : "Can only drop files and remote images here";
25 | $scope.dropClass = valid ? "dragging" : "invalid-dragging";
26 | });
27 | }
28 |
29 | // reset style and text to the default
30 | var dragLeave = function(e) {
31 | $scope.$apply(function() {
32 | $scope.dropText = defaultDropText;
33 | $scope.dropClass = '';
34 | });
35 | }
36 |
37 | // on drop, we create the appropriate TODOs using dropped data
38 | var drop = function(e) {
39 | e.preventDefault();
40 | e.stopPropagation();
41 |
42 | var newTodos=[];
43 | if (e.dataTransfer.types.indexOf('Files') >= 0) {
44 | var files = e.dataTransfer.files;
45 | for (var i = 0; i < files.length; i++) {
46 | var text = files[i].name+', '+files[i].size+' bytes';
47 | newTodos.push({text:text, done:false, file: files[i]});
48 | }
49 | } else { // uris
50 | var uri=e.dataTransfer.getData("text/uri-list");
51 | if (/\.png$/.test(uri)) {
52 |
53 | }
54 | newTodos.push({text:uri, done:false, uri: uri});
55 | }
56 |
57 | $scope.$apply(function() {
58 | $scope.dropText = defaultDropText;
59 | $scope.dropClass = '';
60 | for (var i = 0; i < newTodos.length; i++) {
61 | $scope.todos.push(newTodos[i]);
62 | }
63 | $scope.save();
64 | });
65 | }
66 |
67 | document.body.addEventListener("dragover", dragOver, false);
68 | document.body.addEventListener("dragleave", dragLeave, false);
69 | document.body.addEventListener("drop", drop, false);
70 |
71 | // If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
72 | $scope.load = function(value) {
73 | if (value && value.todolist) {
74 | $scope.todos = value.todolist;
75 | } else {
76 | $scope.todos = [
77 | {text:'learn angular', done:true},
78 | {text:'build an angular app', done:false}];
79 | }
80 | }
81 |
82 | $scope.save = function() {
83 | chrome.storage.sync.set({'todolist': $scope.todos});
84 | };
85 |
86 | $scope.addTodo = function() {
87 | $scope.todos.push({text:$scope.todoText, done:false});
88 | $scope.todoText = '';
89 | };
90 |
91 | $scope.remaining = function() {
92 | var count = 0;
93 | angular.forEach($scope.todos, function(todo) {
94 | count += todo.done ? 0 : 1;
95 | });
96 | return count;
97 | };
98 |
99 | $scope.archive = function() {
100 | var oldTodos = $scope.todos;
101 | $scope.todos = [];
102 | angular.forEach(oldTodos, function(todo) {
103 | if (!todo.done) $scope.todos.push(todo);
104 | });
105 | };
106 | }
107 |
108 |
109 | var newTodoInput = null;
110 |
111 | var clearInitialState = function() {
112 | chrome.storage.local.set({'newtodo': null});
113 | }
114 |
115 | var setInitialState = function() {
116 | chrome.storage.local.get('newtodo', function(data) {
117 | if (newTodoInput && data && data.newtodo) {
118 | newTodoInput.value = data.newtodo;
119 | newTodoInput.focus();
120 | }
121 | });
122 | };
123 |
124 | window.addEventListener('load', function() {
125 | var saveTransientState = function() {
126 | chrome.storage.local.set({'newtodo': newTodoInput.value});
127 | };
128 | newTodoInput = document.querySelector('input[type="text"]');
129 | newTodoInput.addEventListener('keypress' , function() {
130 | saveTransientState();
131 | })
132 | });
133 |
134 |
135 |
--------------------------------------------------------------------------------
/lab6_lifecycle/angularjs/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChrome/chrome-app-codelab/78e1782ffe1fbfc2a25f8bf22b9c140be9891495/lab6_lifecycle/angularjs/icon.png
--------------------------------------------------------------------------------
/lab6_lifecycle/angularjs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Todo
10 |
11 |
{{remaining()}} of {{todos.length}} remaining
12 | [
archive ]
13 |
19 |
24 |
25 | {{dropText}}
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/lab6_lifecycle/angularjs/main.js:
--------------------------------------------------------------------------------
1 |
2 | chrome.app.runtime.onLaunched.addListener(function() {
3 | // normal launch initiated by the user, let's start clean.
4 | // note that this is not related to the persistent state, which is
5 | // appropriately handled in the window code.
6 | runApp(false);
7 | });
8 |
9 | chrome.app.runtime.onRestarted.addListener(function() {
10 | // if restarted, try to get the transient saved state
11 | runApp(true);
12 | });
13 |
14 | function runApp(readInitialState) {
15 | chrome.app.window.create('index.html',
16 | {id: 'mainwindow', width: 500, height: 309},
17 | // the create callback gets a reference to the AppWindow obj
18 | function(win) {
19 | // when the callback is executed, the DOM is loaded but no script was
20 | // loaded yet. So, let's attach to the load event.
21 | win.contentWindow.addEventListener('load', function() {
22 | if (readInitialState) {
23 | win.contentWindow.setInitialState();
24 | } else {
25 | win.contentWindow.clearInitialState();
26 | }
27 | });
28 | });
29 | }
--------------------------------------------------------------------------------
/lab6_lifecycle/angularjs/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Lab6 Manage lifecycle",
4 | "version": "1",
5 | "app": {
6 | "background": {
7 | "scripts": ["main.js"]
8 | }
9 | },
10 | "permissions": ["storage"],
11 | "icons": { "128": "icon.png" }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/lab6_lifecycle/angularjs/todo.css:
--------------------------------------------------------------------------------
1 | html {
2 | height: 100%;
3 | overflow: hidden;
4 | }
5 |
6 | body {
7 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
8 | height: 100%;
9 | }
10 |
11 | @-webkit-keyframes switch-green {
12 | from { background-color: white;} to {background-color: rgb(163, 255, 163);}
13 | }
14 | @-webkit-keyframes switch-red {
15 | from { background-color: white;} to {background-color: rgb(255, 203, 203);}
16 | }
17 | .dragging {
18 | -webkit-animation: switch-green 0.5s ease-in-out 0 infinite alternate;
19 | }
20 |
21 | .invalid-dragging {
22 | -webkit-animation: switch-red 0.5s ease-in-out 0 infinite alternate;
23 | }
24 |
25 | ul {
26 | list-style: none;
27 | max-height: 120px;
28 | overflow-y: auto;
29 | }
30 |
31 | button, input[type=submit] {
32 | background-color: #0074CC;
33 | background-image: linear-gradient(top, #08C, #05C);
34 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
35 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
36 | color: white;
37 | }
38 |
39 | .done-true {
40 | text-decoration: line-through;
41 | color: grey;
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/lab6_lifecycle/javascript/controller.js:
--------------------------------------------------------------------------------
1 | /**
2 |
3 | A poor man's controller - a very simple module with basic MVC functionaliies
4 |
5 | **/
6 | (function(exports) {
7 |
8 | var nextId = 1;
9 |
10 | var TodoModel = function() {
11 | this.todos = {};
12 | this.listeners = [];
13 | }
14 |
15 | TodoModel.prototype.setTodos = function(todos) {
16 | this.todos = todos;
17 | var maxId = 0;
18 | for (var id in todos) {
19 | var idInt = parseInt(id);
20 | maxId = idInt > maxId ? idInt : maxId;
21 | }
22 | nextId = maxId + 1;
23 | this.notifyListeners('reset');
24 | }
25 |
26 | TodoModel.prototype.clearTodos = function() {
27 | this.todos = {};
28 | this.notifyListeners('removed');
29 | }
30 |
31 | TodoModel.prototype.archiveDone = function() {
32 | var oldTodos = this.todos;
33 | this.todos={};
34 | for (var id in oldTodos) {
35 | if ( ! oldTodos[id].isDone ) {
36 | this.todos[id] = oldTodos[id];
37 | }
38 | }
39 | this.notifyListeners('archived');
40 | }
41 |
42 | TodoModel.prototype.setTodoState = function(id, isDone) {
43 | if ( this.todos[id].isDone != isDone ) {
44 | this.todos[id].isDone = isDone;
45 | this.notifyListeners('stateChanged', id);
46 | }
47 | }
48 |
49 | TodoModel.prototype.addTodo = function(text, isDone, extras) {
50 | var id = nextId++;
51 | this.todos[id]={'id': id, 'text': text, 'isDone': isDone, 'extras': extras};
52 | this.notifyListeners('added', id);
53 | }
54 |
55 | TodoModel.prototype.addListener = function(listener) {
56 | this.listeners.push(listener);
57 | }
58 |
59 | TodoModel.prototype.notifyListeners = function(change, param) {
60 | var this_ = this;
61 | this.listeners.forEach(function(listener) {
62 | listener(this_, change, param);
63 | });
64 | }
65 |
66 | exports.TodoModel = TodoModel;
67 |
68 | })(window);
69 |
70 |
71 |
72 | (function(exports) {
73 |
74 | var defaultDropText = "Or drop files here...";
75 |
76 | var dropText = document.getElementById('dropText');
77 | dropText.innerText = defaultDropText;
78 |
79 | // on dragOver, we will change the style and text accordingly, depending on
80 | // the data being transfered
81 | var dragOver = function(e) {
82 | e.stopPropagation();
83 | e.preventDefault();
84 | var valid = isValid(e.dataTransfer);
85 | if (valid) {
86 | dropText.innerText="Drop files and remote images and they will become Todos";
87 | document.body.classList.add("dragging");
88 | } else {
89 | dropText.innerText="Can only drop files and remote images here";
90 | document.body.classList.add("invalid-dragging");
91 | }
92 | }
93 |
94 | var isValid = function(dataTransfer) {
95 | return dataTransfer && dataTransfer.types
96 | && ( dataTransfer.types.indexOf('Files') >= 0
97 | || dataTransfer.types.indexOf('text/uri-list') >=0 )
98 | }
99 |
100 | // reset style and text to the default
101 | var dragLeave = function(e) {
102 | dropText.innerText=defaultDropText;
103 | document.body.classList.remove('dragging');
104 | document.body.classList.remove('invalid-dragging');
105 | }
106 |
107 | // on drop, we create the appropriate TODOs using dropped data
108 | var drop = function(e, model) {
109 | e.preventDefault();
110 | e.stopPropagation();
111 | if (isValid(e.dataTransfer)) {
112 | if (e.dataTransfer.types.indexOf('Files') >= 0) {
113 | var files = e.dataTransfer.files;
114 | for (var i = 0; i < files.length; i++) {
115 | var text = files[i].name+', '+files[i].size+' bytes';
116 | model.addTodo(text, false, {file: files[i]});
117 | }
118 | } else { // uris
119 | var uri=e.dataTransfer.getData("text/uri-list");
120 | model.addTodo(uri, false, {uri: uri});
121 | }
122 | }
123 |
124 | dragLeave();
125 | }
126 |
127 | exports.setDragHandlers = function(model) {
128 | document.body.addEventListener("dragover", dragOver, false);
129 | document.body.addEventListener("dragleave", dragLeave, false);
130 | document.body.addEventListener("drop", function(e) {
131 | drop(e, model);
132 | }, false);
133 | }
134 |
135 | })(window);
136 |
137 |
138 | window.addEventListener('DOMContentLoaded', function() {
139 |
140 | var model = new TodoModel();
141 | var form = document.querySelector('form');
142 | var archive = document.getElementById('archive');
143 | var list = document.getElementById('list');
144 | var todoTemplate = document.querySelector('#templates > [data-name="list"]');
145 |
146 | /**
147 | * When the form is submitted, we need to add a new todo and clear the input
148 | **/
149 | form.addEventListener('submit', function(e) {
150 | var textEl = e.target.querySelector('input[type="text"]');
151 | model.addTodo(textEl.value, false);
152 | textEl.value=null;
153 | e.preventDefault();
154 | });
155 |
156 |
157 | /**
158 | * A simple handler for the archive link
159 | **/
160 | archive.addEventListener('click', function(e) {
161 | model.archiveDone();
162 | e.preventDefault();
163 | });
164 |
165 |
166 | /**
167 | * Listen to changes in the model and trigger the appropriate changes in the view
168 | **/
169 | model.addListener(function(model, changeType, param) {
170 | if ( changeType === 'removed' || changeType === 'archived' || changeType === 'reset') {
171 | redrawUI(model);
172 | } else if ( changeType === 'added' ) {
173 | drawTodo(model.todos[param], list);
174 | } else if ( changeType === 'stateChanged') {
175 | updateTodo(model.todos[param]);
176 | }
177 | storageSave();
178 | updateCounters(model);
179 | });
180 |
181 |
182 | // If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
183 | var storageLoad = function() {
184 | chrome.storage.sync.get('todolist', function(value) {
185 | if (value && value.todolist) {
186 | model.setTodos(value.todolist);
187 | } else {
188 | model.addTodo('learn Chrome Apps', true);
189 | model.addTodo('build a Chrome App', false);
190 | }
191 | });
192 | }
193 |
194 | var storageSave = function() {
195 | chrome.storage.sync.set({'todolist': model.todos});
196 | };
197 |
198 | /**
199 | * Clean the current ToDo list and add elements again
200 | **/
201 | var redrawUI = function(model) {
202 | list.innerHTML='';
203 | for (var id in model.todos) {
204 | drawTodo(model.todos[id], list);
205 | }
206 | };
207 |
208 | /**
209 | * Deep clone a template node, set its ID and add it to the DOM container.
210 | * Add a listener to the newly added checkbox, so it can trigger the state flip
211 | * when the checkbox is clicked.
212 | **/
213 | var drawTodo = function(todoObj, container) {
214 | var el = todoTemplate.cloneNode(true);
215 | el.setAttribute('data-id', todoObj.id);
216 | container.appendChild(el);
217 | updateTodo(todoObj);
218 | var checkbox = el.querySelector('input[type="checkbox"]');
219 | checkbox.addEventListener('change', function(e) {
220 | model.setTodoState(todoObj.id, e.target.checked);
221 | });
222 | }
223 |
224 |
225 | /**
226 | * Find the element corresponding to the given ToDo model object and update its
227 | * state and description from the model.
228 | */
229 | var updateTodo = function(model) {
230 | var todoElement = list.querySelector('li[data-id="'+model.id+'"]');
231 | if (todoElement) {
232 | var checkbox = todoElement.querySelector('input[type="checkbox"]');
233 | var desc = todoElement.querySelector('span');
234 | checkbox.checked = model.isDone;
235 | desc.innerText = model.text;
236 | desc.className = "done-"+model.isDone;
237 | }
238 | }
239 |
240 | /**
241 | * Recalculate total number of ToDos and remaining ToDos and update
242 | * appropriate elements in the DOM.
243 | **/
244 | var updateCounters = function(model) {
245 | var count = 0;
246 | var notDone = 0;
247 | for (var id in model.todos) {
248 | count++;
249 | if ( ! model.todos[id].isDone ) {
250 | notDone ++;
251 | }
252 | }
253 | document.getElementById('remaining').innerText = notDone;
254 | document.getElementById('length').innerText = count;
255 | }
256 |
257 | storageLoad();
258 | setDragHandlers(model);
259 |
260 |
261 |
262 | var newTodoInput = document.querySelector('input[type="text"]');
263 |
264 | window.clearInitialState = function() {
265 | chrome.storage.local.set({'newtodo': null});
266 | }
267 |
268 | window.setInitialState = function() {
269 | chrome.storage.local.get('newtodo', function(data) {
270 | if (newTodoInput && data && data.newtodo) {
271 | newTodoInput.value = data.newtodo;
272 | newTodoInput.focus();
273 | }
274 | });
275 | };
276 |
277 | var saveTransientState = function() {
278 | chrome.storage.local.set({'newtodo': newTodoInput.value});
279 | };
280 |
281 | newTodoInput.addEventListener('keypress' , function() {
282 | saveTransientState();
283 | })
284 |
285 | });
286 |
287 |
--------------------------------------------------------------------------------
/lab6_lifecycle/javascript/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChrome/chrome-app-codelab/78e1782ffe1fbfc2a25f8bf22b9c140be9891495/lab6_lifecycle/javascript/icon.png
--------------------------------------------------------------------------------
/lab6_lifecycle/javascript/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Todo
8 |
9 |
of remaining
10 | [
archive ]
11 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/lab6_lifecycle/javascript/main.js:
--------------------------------------------------------------------------------
1 |
2 | chrome.app.runtime.onLaunched.addListener(function() {
3 | // normal launch initiated by the user, let's start clean.
4 | // note that this is not related to the persistent state, which is
5 | // appropriately handled in the window code.
6 | runApp(false);
7 | });
8 |
9 | chrome.app.runtime.onRestarted.addListener(function() {
10 | // if restarted, try to get the transient saved state
11 | runApp(true);
12 | });
13 |
14 | function runApp(readInitialState) {
15 | chrome.app.window.create('index.html',
16 | {id: 'mainwindow', width: 500, height: 309},
17 | // the create callback gets a reference to the AppWindow obj
18 | function(win) {
19 | // when the callback is executed, the DOM is loaded but no script was
20 | // loaded yet. So, let's attach to the load event.
21 | win.contentWindow.addEventListener('load', function() {
22 | if (readInitialState) {
23 | win.contentWindow.setInitialState();
24 | } else {
25 | win.contentWindow.clearInitialState();
26 | }
27 | });
28 | });
29 | }
--------------------------------------------------------------------------------
/lab6_lifecycle/javascript/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Lab6 Manage lifecycle",
4 | "version": "1",
5 | "app": {
6 | "background": {
7 | "scripts": ["main.js"]
8 | }
9 | },
10 | "permissions": ["storage"],
11 | "icons": { "128": "icon.png" }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/lab6_lifecycle/javascript/todo.css:
--------------------------------------------------------------------------------
1 | html {
2 | height: 100%;
3 | overflow: hidden;
4 | }
5 |
6 | body {
7 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
8 | height: 100%;
9 | }
10 |
11 | @-webkit-keyframes switch-green {
12 | from { background-color: white;} to {background-color: rgb(163, 255, 163);}
13 | }
14 | @-webkit-keyframes switch-red {
15 | from { background-color: white;} to {background-color: rgb(255, 203, 203);}
16 | }
17 | .dragging {
18 | -webkit-animation: switch-green 0.5s ease-in-out 0 infinite alternate;
19 | }
20 |
21 | .invalid-dragging {
22 | -webkit-animation: switch-red 0.5s ease-in-out 0 infinite alternate;
23 | }
24 |
25 | ul {
26 | list-style: none;
27 | max-height: 120px;
28 | overflow-y: auto;
29 | }
30 |
31 | button, input[type=submit] {
32 | background-color: #0074CC;
33 | background-image: linear-gradient(top, #08C, #05C);
34 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
35 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
36 | color: white;
37 | }
38 |
39 | .done-true {
40 | text-decoration: line-through;
41 | color: grey;
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/lab7_useridentification/angularjs/controller.js:
--------------------------------------------------------------------------------
1 | function TodoCtrl($scope) {
2 |
3 | // Notice that chrome.storage.sync.get is asynchronous
4 | chrome.storage.sync.get('todolist', function(value) {
5 | // The $apply is only necessary to execute the function inside Angular scope
6 | $scope.$apply(function() {
7 | $scope.load(value);
8 | });
9 | });
10 |
11 | var defaultDropText = "Or drop files here...";
12 | $scope.dropText = defaultDropText;
13 |
14 | // on dragOver, we will change the style and text accordingly, depending on
15 | // the data being transfered
16 | var dragOver = function(e) {
17 | e.stopPropagation();
18 | e.preventDefault();
19 | var valid = e.dataTransfer && e.dataTransfer.types
20 | && ( e.dataTransfer.types.indexOf('Files') >= 0
21 | || e.dataTransfer.types.indexOf('text/uri-list') >=0 )
22 | $scope.$apply(function() {
23 | $scope.dropText = valid ? "Drop files and remote images and they will become Todos"
24 | : "Can only drop files and remote images here";
25 | $scope.dropClass = valid ? "dragging" : "invalid-dragging";
26 | });
27 | }
28 |
29 | // reset style and text to the default
30 | var dragLeave = function(e) {
31 | $scope.$apply(function() {
32 | $scope.dropText = defaultDropText;
33 | $scope.dropClass = '';
34 | });
35 | }
36 |
37 | // on drop, we create the appropriate TODOs using dropped data
38 | var drop = function(e) {
39 | e.preventDefault();
40 | e.stopPropagation();
41 |
42 | var newTodos=[];
43 | if (e.dataTransfer.types.indexOf('Files') >= 0) {
44 | var files = e.dataTransfer.files;
45 | for (var i = 0; i < files.length; i++) {
46 | var text = files[i].name+', '+files[i].size+' bytes';
47 | newTodos.push({text:text, done:false, file: files[i]});
48 | }
49 | } else { // uris
50 | var uri=e.dataTransfer.getData("text/uri-list");
51 | if (/\.png$/.test(uri)) {
52 |
53 | }
54 | newTodos.push({text:uri, done:false, uri: uri});
55 | }
56 |
57 | $scope.$apply(function() {
58 | $scope.dropText = defaultDropText;
59 | $scope.dropClass = '';
60 | for (var i = 0; i < newTodos.length; i++) {
61 | $scope.todos.push(newTodos[i]);
62 | }
63 | $scope.save();
64 | });
65 | }
66 |
67 | document.body.addEventListener("dragover", dragOver, false);
68 | document.body.addEventListener("dragleave", dragLeave, false);
69 | document.body.addEventListener("drop", drop, false);
70 |
71 | // If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
72 | $scope.load = function(value) {
73 | if (value && value.todolist) {
74 | $scope.todos = value.todolist;
75 | } else {
76 | $scope.todos = [
77 | {text:'learn angular', done:true},
78 | {text:'build an angular app', done:false}];
79 | }
80 | }
81 |
82 | $scope.save = function() {
83 | chrome.storage.sync.set({'todolist': $scope.todos});
84 | };
85 |
86 | $scope.addTodo = function() {
87 | $scope.todos.push({text:$scope.todoText, done:false});
88 | $scope.todoText = '';
89 | };
90 |
91 | $scope.remaining = function() {
92 | var count = 0;
93 | angular.forEach($scope.todos, function(todo) {
94 | count += todo.done ? 0 : 1;
95 | });
96 | return count;
97 | };
98 |
99 | $scope.archive = function() {
100 | var oldTodos = $scope.todos;
101 | $scope.todos = [];
102 | angular.forEach(oldTodos, function(todo) {
103 | if (!todo.done) $scope.todos.push(todo);
104 | });
105 | };
106 |
107 | $scope.importFromGTasks = function() {
108 | var api = new TasksAPI();
109 | var clientId = "";
110 | api.authenticate(clientId, function() {
111 | api.getLists(function(result) {
112 | console.log(result);
113 | if (!result || !result.items || result.items.length==0) {
114 | throw "No task lists available";
115 | }
116 | var listId=result.items[0].id;
117 | api.getTasks(listId, function(tasks) {
118 | console.log(tasks);
119 | for (var j=0; j
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Todo
11 |
12 |
{{remaining()}} of {{todos.length}} remaining
13 | [
archive ]
14 |
20 |
25 |
import tasks from GTasks
26 |
27 | {{dropText}}
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/lab7_useridentification/angularjs/main.js:
--------------------------------------------------------------------------------
1 |
2 | chrome.app.runtime.onLaunched.addListener(function() {
3 | // normal launch initiated by the user, let's start clean.
4 | // note that this is not related to the persistent state, which is
5 | // appropriately handled in the window code.
6 | runApp(false);
7 | });
8 |
9 | chrome.app.runtime.onRestarted.addListener(function() {
10 | // if restarted, try to get the transient saved state
11 | runApp(true);
12 | });
13 |
14 | function runApp(readInitialState) {
15 | chrome.app.window.create('index.html',
16 | {id: 'mainwindow', width: 500, height: 309},
17 | // the create callback gets a reference to the AppWindow obj
18 | function(win) {
19 | // when the callback is executed, the DOM is loaded but no script was
20 | // loaded yet. So, let's attach to the load event.
21 | win.contentWindow.addEventListener('load', function() {
22 | if (readInitialState) {
23 | win.contentWindow.setInitialState();
24 | } else {
25 | win.contentWindow.clearInitialState();
26 | }
27 | });
28 | });
29 | }
--------------------------------------------------------------------------------
/lab7_useridentification/angularjs/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Lab7 User identification",
4 | "version": "1",
5 | "app": {
6 | "background": {
7 | "scripts": ["main.js"]
8 | }
9 | },
10 | "permissions": ["storage", "experimental", "https://www.googleapis.com/tasks/*"],
11 | "icons": { "128": "icon.png" }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/lab7_useridentification/angularjs/todo.css:
--------------------------------------------------------------------------------
1 | html {
2 | height: 100%;
3 | overflow: hidden;
4 | }
5 |
6 | body {
7 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
8 | height: 100%;
9 | }
10 |
11 | @-webkit-keyframes switch-green {
12 | from { background-color: white;} to {background-color: rgb(163, 255, 163);}
13 | }
14 | @-webkit-keyframes switch-red {
15 | from { background-color: white;} to {background-color: rgb(255, 203, 203);}
16 | }
17 | .dragging {
18 | -webkit-animation: switch-green 0.5s ease-in-out 0 infinite alternate;
19 | }
20 |
21 | .invalid-dragging {
22 | -webkit-animation: switch-red 0.5s ease-in-out 0 infinite alternate;
23 | }
24 |
25 | ul {
26 | list-style: none;
27 | max-height: 120px;
28 | overflow-y: auto;
29 | }
30 |
31 | button, input[type=submit] {
32 | background-color: #0074CC;
33 | background-image: linear-gradient(top, #08C, #05C);
34 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
35 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
36 | color: white;
37 | }
38 |
39 | .done-true {
40 | text-decoration: line-through;
41 | color: grey;
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/lab7_useridentification/javascript/controller.js:
--------------------------------------------------------------------------------
1 | /**
2 |
3 | A poor man's controller - a very simple module with basic MVC functionaliies
4 |
5 | **/
6 | (function(exports) {
7 |
8 | var nextId = 1;
9 |
10 | var TodoModel = function() {
11 | this.todos = {};
12 | this.listeners = [];
13 | }
14 |
15 | TodoModel.prototype.setTodos = function(todos) {
16 | this.todos = todos;
17 | var maxId = 0;
18 | for (var id in todos) {
19 | var idInt = parseInt(id);
20 | maxId = idInt > maxId ? idInt : maxId;
21 | }
22 | nextId = maxId + 1;
23 | this.notifyListeners('reset');
24 | }
25 |
26 | TodoModel.prototype.clearTodos = function() {
27 | this.todos = {};
28 | this.notifyListeners('removed');
29 | }
30 |
31 | TodoModel.prototype.archiveDone = function() {
32 | var oldTodos = this.todos;
33 | this.todos={};
34 | for (var id in oldTodos) {
35 | if ( ! oldTodos[id].isDone ) {
36 | this.todos[id] = oldTodos[id];
37 | }
38 | }
39 | this.notifyListeners('archived');
40 | }
41 |
42 | TodoModel.prototype.setTodoState = function(id, isDone) {
43 | if ( this.todos[id].isDone != isDone ) {
44 | this.todos[id].isDone = isDone;
45 | this.notifyListeners('stateChanged', id);
46 | }
47 | }
48 |
49 | TodoModel.prototype.addTodo = function(text, isDone, extras) {
50 | var id = nextId++;
51 | this.todos[id]={'id': id, 'text': text, 'isDone': isDone, 'extras': extras};
52 | this.notifyListeners('added', id);
53 | }
54 |
55 | TodoModel.prototype.addListener = function(listener) {
56 | this.listeners.push(listener);
57 | }
58 |
59 | TodoModel.prototype.notifyListeners = function(change, param) {
60 | var this_ = this;
61 | this.listeners.forEach(function(listener) {
62 | listener(this_, change, param);
63 | });
64 | }
65 |
66 | exports.TodoModel = TodoModel;
67 |
68 | })(window);
69 |
70 |
71 |
72 | (function(exports) {
73 |
74 | var defaultDropText = "Or drop files here...";
75 |
76 | var dropText = document.getElementById('dropText');
77 | dropText.innerText = defaultDropText;
78 |
79 | // on dragOver, we will change the style and text accordingly, depending on
80 | // the data being transfered
81 | var dragOver = function(e) {
82 | e.stopPropagation();
83 | e.preventDefault();
84 | var valid = isValid(e.dataTransfer);
85 | if (valid) {
86 | dropText.innerText="Drop files and remote images and they will become Todos";
87 | document.body.classList.add("dragging");
88 | } else {
89 | dropText.innerText="Can only drop files and remote images here";
90 | document.body.classList.add("invalid-dragging");
91 | }
92 | }
93 |
94 | var isValid = function(dataTransfer) {
95 | return dataTransfer && dataTransfer.types
96 | && ( dataTransfer.types.indexOf('Files') >= 0
97 | || dataTransfer.types.indexOf('text/uri-list') >=0 )
98 | }
99 |
100 | // reset style and text to the default
101 | var dragLeave = function(e) {
102 | dropText.innerText=defaultDropText;
103 | document.body.classList.remove('dragging');
104 | document.body.classList.remove('invalid-dragging');
105 | }
106 |
107 | // on drop, we create the appropriate TODOs using dropped data
108 | var drop = function(e, model) {
109 | e.preventDefault();
110 | e.stopPropagation();
111 | if (isValid(e.dataTransfer)) {
112 | if (e.dataTransfer.types.indexOf('Files') >= 0) {
113 | var files = e.dataTransfer.files;
114 | for (var i = 0; i < files.length; i++) {
115 | var text = files[i].name+', '+files[i].size+' bytes';
116 | model.addTodo(text, false, {file: files[i]});
117 | }
118 | } else { // uris
119 | var uri=e.dataTransfer.getData("text/uri-list");
120 | model.addTodo(uri, false, {uri: uri});
121 | }
122 | }
123 |
124 | dragLeave();
125 | }
126 |
127 | exports.setDragHandlers = function(model) {
128 | document.body.addEventListener("dragover", dragOver, false);
129 | document.body.addEventListener("dragleave", dragLeave, false);
130 | document.body.addEventListener("drop", function(e) {
131 | drop(e, model);
132 | }, false);
133 | }
134 |
135 | })(window);
136 |
137 |
138 | window.addEventListener('DOMContentLoaded', function() {
139 |
140 | var model = new TodoModel();
141 | var form = document.querySelector('form');
142 | var archive = document.getElementById('archive');
143 | var list = document.getElementById('list');
144 | var todoTemplate = document.querySelector('#templates > [data-name="list"]');
145 |
146 | /**
147 | * When the form is submitted, we need to add a new todo and clear the input
148 | **/
149 | form.addEventListener('submit', function(e) {
150 | var textEl = e.target.querySelector('input[type="text"]');
151 | model.addTodo(textEl.value, false);
152 | textEl.value=null;
153 | e.preventDefault();
154 | });
155 |
156 |
157 | /**
158 | * A simple handler for the archive link
159 | **/
160 | archive.addEventListener('click', function(e) {
161 | model.archiveDone();
162 | e.preventDefault();
163 | });
164 |
165 |
166 | /**
167 | * Listen to changes in the model and trigger the appropriate changes in the view
168 | **/
169 | model.addListener(function(model, changeType, param) {
170 | if ( changeType === 'removed' || changeType === 'archived' || changeType === 'reset') {
171 | redrawUI(model);
172 | } else if ( changeType === 'added' ) {
173 | drawTodo(model.todos[param], list);
174 | } else if ( changeType === 'stateChanged') {
175 | updateTodo(model.todos[param]);
176 | }
177 | storageSave();
178 | updateCounters(model);
179 | });
180 |
181 |
182 | // If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
183 | var storageLoad = function() {
184 | chrome.storage.sync.get('todolist', function(value) {
185 | if (value && value.todolist) {
186 | model.setTodos(value.todolist);
187 | } else {
188 | model.addTodo('learn Chrome Apps', true);
189 | model.addTodo('build a Chrome App', false);
190 | }
191 | });
192 | }
193 |
194 | var storageSave = function() {
195 | chrome.storage.sync.set({'todolist': model.todos});
196 | };
197 |
198 | /**
199 | * Clean the current ToDo list and add elements again
200 | **/
201 | var redrawUI = function(model) {
202 | list.innerHTML='';
203 | for (var id in model.todos) {
204 | drawTodo(model.todos[id], list);
205 | }
206 | };
207 |
208 | /**
209 | * Deep clone a template node, set its ID and add it to the DOM container.
210 | * Add a listener to the newly added checkbox, so it can trigger the state flip
211 | * when the checkbox is clicked.
212 | **/
213 | var drawTodo = function(todoObj, container) {
214 | var el = todoTemplate.cloneNode(true);
215 | el.setAttribute('data-id', todoObj.id);
216 | container.appendChild(el);
217 | updateTodo(todoObj);
218 | var checkbox = el.querySelector('input[type="checkbox"]');
219 | checkbox.addEventListener('change', function(e) {
220 | model.setTodoState(todoObj.id, e.target.checked);
221 | });
222 | }
223 |
224 |
225 | /**
226 | * Find the element corresponding to the given ToDo model object and update its
227 | * state and description from the model.
228 | */
229 | var updateTodo = function(model) {
230 | var todoElement = list.querySelector('li[data-id="'+model.id+'"]');
231 | if (todoElement) {
232 | var checkbox = todoElement.querySelector('input[type="checkbox"]');
233 | var desc = todoElement.querySelector('span');
234 | checkbox.checked = model.isDone;
235 | desc.innerText = model.text;
236 | desc.className = "done-"+model.isDone;
237 | }
238 | }
239 |
240 | /**
241 | * Recalculate total number of ToDos and remaining ToDos and update
242 | * appropriate elements in the DOM.
243 | **/
244 | var updateCounters = function(model) {
245 | var count = 0;
246 | var notDone = 0;
247 | for (var id in model.todos) {
248 | count++;
249 | if ( ! model.todos[id].isDone ) {
250 | notDone ++;
251 | }
252 | }
253 | document.getElementById('remaining').innerText = notDone;
254 | document.getElementById('length').innerText = count;
255 | }
256 |
257 | storageLoad();
258 | setDragHandlers(model);
259 |
260 |
261 |
262 | /**
263 | * On each keypress of text input, save contents to the local
264 | * storage. On browser restart (for browser upgrade, for example)
265 | * the method setInitialState is called and the saved value
266 | * restored.
267 | *
268 | * Added for Codelab 6
269 | **/
270 | var newTodoInput = document.querySelector('input[type="text"]');
271 |
272 | window.clearInitialState = function() {
273 | chrome.storage.local.set({'newtodo': null});
274 | }
275 |
276 | window.setInitialState = function() {
277 | chrome.storage.local.get('newtodo', function(data) {
278 | if (newTodoInput && data && data.newtodo) {
279 | newTodoInput.value = data.newtodo;
280 | newTodoInput.focus();
281 | }
282 | });
283 | };
284 |
285 | var saveTransientState = function() {
286 | chrome.storage.local.set({'newtodo': newTodoInput.value});
287 | };
288 |
289 | newTodoInput.addEventListener('keypress' , function() {
290 | saveTransientState();
291 | })
292 |
293 |
294 |
295 | /**
296 | * Using the GoogleTasks API, get the logged user's tasks of his first
297 | * task list and add them to the ToDo list
298 | *
299 | * Added for Codelab 7
300 | **/
301 | document.getElementById('importGTasks').addEventListener('click', function() {
302 | var api = new TasksAPI();
303 | var clientId = "";
304 | api.authenticate(clientId, function() {
305 | api.getLists(function(result) {
306 | console.log(result);
307 | if (!result || !result.items || result.items.length==0) {
308 | throw "No task lists available";
309 | }
310 | var listId=result.items[0].id;
311 | api.getTasks(listId, function(tasks) {
312 | console.log(tasks);
313 | for (var j=0; j
2 |
3 |
4 |
5 |
6 |
7 | Todo
8 |
9 |
of remaining
10 | [
archive ]
11 |
13 |
18 |
import tasks from GTasks
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/lab7_useridentification/javascript/main.js:
--------------------------------------------------------------------------------
1 |
2 | chrome.app.runtime.onLaunched.addListener(function() {
3 | // normal launch initiated by the user, let's start clean.
4 | // note that this is not related to the persistent state, which is
5 | // appropriately handled in the window code.
6 | runApp(false);
7 | });
8 |
9 | chrome.app.runtime.onRestarted.addListener(function() {
10 | // if restarted, try to get the transient saved state
11 | runApp(true);
12 | });
13 |
14 | function runApp(readInitialState) {
15 | chrome.app.window.create('index.html',
16 | {id: 'mainwindow', width: 500, height: 309},
17 | // the create callback gets a reference to the AppWindow obj
18 | function(win) {
19 | // when the callback is executed, the DOM is loaded but no script was
20 | // loaded yet. So, let's attach to the load event.
21 | win.contentWindow.addEventListener('load', function() {
22 | if (readInitialState) {
23 | win.contentWindow.setInitialState();
24 | } else {
25 | win.contentWindow.clearInitialState();
26 | }
27 | });
28 | });
29 | }
--------------------------------------------------------------------------------
/lab7_useridentification/javascript/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Lab7 User identification",
4 | "version": "1",
5 | "app": {
6 | "background": {
7 | "scripts": ["main.js"]
8 | }
9 | },
10 | "permissions": ["storage", "experimental", "https://www.googleapis.com/tasks/*"],
11 | "icons": { "128": "icon.png" }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/lab7_useridentification/javascript/todo.css:
--------------------------------------------------------------------------------
1 | html {
2 | height: 100%;
3 | overflow: hidden;
4 | }
5 |
6 | body {
7 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
8 | height: 100%;
9 | }
10 |
11 | @-webkit-keyframes switch-green {
12 | from { background-color: white;} to {background-color: rgb(163, 255, 163);}
13 | }
14 | @-webkit-keyframes switch-red {
15 | from { background-color: white;} to {background-color: rgb(255, 203, 203);}
16 | }
17 | .dragging {
18 | -webkit-animation: switch-green 0.5s ease-in-out 0 infinite alternate;
19 | }
20 |
21 | .invalid-dragging {
22 | -webkit-animation: switch-red 0.5s ease-in-out 0 infinite alternate;
23 | }
24 |
25 | ul {
26 | list-style: none;
27 | max-height: 120px;
28 | overflow-y: auto;
29 | }
30 |
31 | button, input[type=submit] {
32 | background-color: #0074CC;
33 | background-image: linear-gradient(top, #08C, #05C);
34 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
35 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
36 | color: white;
37 | }
38 |
39 | .done-true {
40 | text-decoration: line-through;
41 | color: grey;
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/lab8_webresources/angularjs/1_webview/controller.js:
--------------------------------------------------------------------------------
1 | function TodoCtrl($scope) {
2 |
3 | // Notice that chrome.storage.sync.get is asynchronous
4 | chrome.storage.sync.get('todolist', function(value) {
5 | // The $apply is only necessary to execute the function inside Angular scope
6 | $scope.$apply(function() {
7 | $scope.load(value);
8 | });
9 | });
10 |
11 | var defaultDropText = "Or drop files here...";
12 | $scope.dropText = defaultDropText;
13 |
14 | // on dragOver, we will change the style and text accordingly, depending on
15 | // the data being transfered
16 | var dragOver = function(e) {
17 | e.stopPropagation();
18 | e.preventDefault();
19 | var valid = e.dataTransfer && e.dataTransfer.types
20 | && ( e.dataTransfer.types.indexOf('Files') >= 0
21 | || e.dataTransfer.types.indexOf('text/uri-list') >=0 )
22 | $scope.$apply(function() {
23 | $scope.dropText = valid ? "Drop files and remote images and they will become Todos"
24 | : "Can only drop files and remote images here";
25 | $scope.dropClass = valid ? "dragging" : "invalid-dragging";
26 | });
27 | }
28 |
29 | // reset style and text to the default
30 | var dragLeave = function(e) {
31 | $scope.$apply(function() {
32 | $scope.dropText = defaultDropText;
33 | $scope.dropClass = '';
34 | });
35 | }
36 |
37 | // on drop, we create the appropriate TODOs using dropped data
38 | var drop = function(e) {
39 | e.preventDefault();
40 | e.stopPropagation();
41 |
42 | var newTodos=[];
43 | if (e.dataTransfer.types.indexOf('Files') >= 0) {
44 | var files = e.dataTransfer.files;
45 | for (var i = 0; i < files.length; i++) {
46 | var text = files[i].name+', '+files[i].size+' bytes';
47 | newTodos.push({text:text, done:false, file: files[i]});
48 | }
49 | } else { // uris
50 | var uri=e.dataTransfer.getData("text/uri-list");
51 | if (/\.png$/.test(uri)) {
52 |
53 | }
54 | newTodos.push({text:uri, done:false, uri: uri});
55 | }
56 |
57 | $scope.$apply(function() {
58 | $scope.dropText = defaultDropText;
59 | $scope.dropClass = '';
60 | for (var i = 0; i < newTodos.length; i++) {
61 | $scope.todos.push(newTodos[i]);
62 | }
63 | $scope.save();
64 | });
65 | }
66 |
67 | document.body.addEventListener("dragover", dragOver, false);
68 | document.body.addEventListener("dragleave", dragLeave, false);
69 | document.body.addEventListener("drop", drop, false);
70 |
71 | // If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
72 | $scope.load = function(value) {
73 | if (value && value.todolist) {
74 | $scope.todos = value.todolist;
75 | } else {
76 | $scope.todos = [
77 | {text:'learn angular', done:true},
78 | {text:'build an angular app', done:false}];
79 | }
80 | }
81 |
82 | $scope.save = function() {
83 | chrome.storage.sync.set({'todolist': $scope.todos});
84 | };
85 |
86 | $scope.addTodo = function() {
87 | var todo = {text:$scope.todoText, done:false};
88 | if(/http:\/\/|https:\/\//.test($scope.todoText)) {
89 | todo.uri = $scope.todoText;
90 | }
91 | $scope.todos.push(todo);
92 | $scope.todoText = '';
93 | };
94 |
95 | $scope.showUri = function(uri) {
96 | var webview=document.querySelector("webview");
97 | webview.src=uri;
98 | };
99 |
100 | $scope.remaining = function() {
101 | var count = 0;
102 | angular.forEach($scope.todos, function(todo) {
103 | count += todo.done ? 0 : 1;
104 | });
105 | return count;
106 | };
107 |
108 | $scope.archive = function() {
109 | var oldTodos = $scope.todos;
110 | $scope.todos = [];
111 | angular.forEach(oldTodos, function(todo) {
112 | if (!todo.done) $scope.todos.push(todo);
113 | });
114 | };
115 | }
116 |
117 |
118 | var newTodoInput = null;
119 |
120 | var clearInitialState = function() {
121 | chrome.storage.local.set({'newtodo': null});
122 | }
123 |
124 | var setInitialState = function() {
125 | chrome.storage.local.get('newtodo', function(data) {
126 | if (newTodoInput && data && data.newtodo) {
127 | newTodoInput.value = data.newtodo;
128 | newTodoInput.focus();
129 | }
130 | });
131 | };
132 |
133 | window.addEventListener('load', function() {
134 | var saveTransientState = function() {
135 | chrome.storage.local.set({'newtodo': newTodoInput.value});
136 | };
137 | newTodoInput = document.querySelector('input[type="text"]');
138 | newTodoInput.addEventListener('keypress' , function() {
139 | saveTransientState();
140 | })
141 | });
142 |
143 |
144 |
--------------------------------------------------------------------------------
/lab8_webresources/angularjs/1_webview/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChrome/chrome-app-codelab/78e1782ffe1fbfc2a25f8bf22b9c140be9891495/lab8_webresources/angularjs/1_webview/icon.png
--------------------------------------------------------------------------------
/lab8_webresources/angularjs/1_webview/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Todo
10 |
11 |
{{remaining()}} of {{todos.length}} remaining
12 | [
archive ]
13 |
20 |
25 |
26 | {{dropText}}
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/lab8_webresources/angularjs/1_webview/main.js:
--------------------------------------------------------------------------------
1 |
2 | chrome.app.runtime.onLaunched.addListener(function() {
3 | // normal launch initiated by the user, let's start clean.
4 | // note that this is not related to the persistent state, which is
5 | // appropriately handled in the window code.
6 | runApp(false);
7 | });
8 |
9 | chrome.app.runtime.onRestarted.addListener(function() {
10 | // if restarted, try to get the transient saved state
11 | runApp(true);
12 | });
13 |
14 | function runApp(readInitialState) {
15 | chrome.app.window.create('index.html',
16 | {id: 'mainwindow', width: 500, height: 500},
17 | // the create callback gets a reference to the AppWindow obj
18 | function(win) {
19 | // when the callback is executed, the DOM is loaded but no script was
20 | // loaded yet. So, let's attach to the load event.
21 | win.contentWindow.addEventListener('load', function() {
22 | if (readInitialState) {
23 | win.contentWindow.setInitialState();
24 | } else {
25 | win.contentWindow.clearInitialState();
26 | }
27 | });
28 | });
29 | }
--------------------------------------------------------------------------------
/lab8_webresources/angularjs/1_webview/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Lab8a Webview and Web resources",
4 | "version": "1",
5 | "app": {
6 | "background": {
7 | "scripts": ["main.js"]
8 | }
9 | },
10 | "permissions": ["storage", "webview"],
11 | "icons": { "128": "icon.png" }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/lab8_webresources/angularjs/1_webview/todo.css:
--------------------------------------------------------------------------------
1 | html {
2 | height: 100%;
3 | overflow: hidden;
4 | }
5 |
6 | body {
7 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
8 | height: 100%;
9 | }
10 |
11 | a {
12 | cursor: pointer;
13 | }
14 |
15 | webview {
16 | width: 100%;
17 | height: 200px;
18 | }
19 |
20 | @-webkit-keyframes switch-green {
21 | from { background-color: white;} to {background-color: rgb(163, 255, 163);}
22 | }
23 | @-webkit-keyframes switch-red {
24 | from { background-color: white;} to {background-color: rgb(255, 203, 203);}
25 | }
26 | .dragging {
27 | -webkit-animation: switch-green 0.5s ease-in-out 0 infinite alternate;
28 | }
29 |
30 | .invalid-dragging {
31 | -webkit-animation: switch-red 0.5s ease-in-out 0 infinite alternate;
32 | }
33 |
34 | ul {
35 | list-style: none;
36 | max-height: 120px;
37 | overflow-y: auto;
38 | }
39 |
40 | button, input[type=submit] {
41 | background-color: #0074CC;
42 | background-image: linear-gradient(top, #08C, #05C);
43 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
44 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
45 | color: white;
46 | }
47 |
48 | .done-true {
49 | text-decoration: line-through;
50 | color: grey;
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/lab8_webresources/angularjs/2_loading_resources/controller.js:
--------------------------------------------------------------------------------
1 | const PLACEHOLDER_IMAGE = "loading.gif";
2 |
3 | function TodoCtrl($scope) {
4 |
5 | // Notice that chrome.storage.sync.get is asynchronous
6 | chrome.storage.sync.get('todolist', function(value) {
7 | // The $apply is only necessary to execute the function inside Angular scope
8 | $scope.$apply(function() {
9 | $scope.load(value);
10 | });
11 | });
12 |
13 | var defaultDropText = "Or drop files here...";
14 | $scope.dropText = defaultDropText;
15 |
16 | // on dragOver, we will change the style and text accordingly, depending on
17 | // the data being transfered
18 | var dragOver = function(e) {
19 | e.stopPropagation();
20 | e.preventDefault();
21 | var valid = e.dataTransfer && e.dataTransfer.types
22 | && ( e.dataTransfer.types.indexOf('Files') >= 0
23 | || e.dataTransfer.types.indexOf('text/uri-list') >=0 )
24 | $scope.$apply(function() {
25 | $scope.dropText = valid ? "Drop files and remote images and they will become Todos"
26 | : "Can only drop files and remote images here";
27 | $scope.dropClass = valid ? "dragging" : "invalid-dragging";
28 | });
29 | }
30 |
31 | // reset style and text to the default
32 | var dragLeave = function(e) {
33 | $scope.$apply(function() {
34 | $scope.dropText = defaultDropText;
35 | $scope.dropClass = '';
36 | });
37 | }
38 |
39 | // on drop, we create the appropriate TODOs using dropped data
40 | var drop = function(e) {
41 | e.preventDefault();
42 | e.stopPropagation();
43 | var hasImage = false;
44 | var newTodos = [];
45 | if (e.dataTransfer.types.indexOf('Files') >= 0) {
46 | var files = e.dataTransfer.files;
47 | for (var i = 0; i < files.length; i++) {
48 | var text = files[i].name+', '+files[i].size+' bytes';
49 | newTodos.push({text:text, done:false, file: files[i]});
50 | }
51 | } else { // uris
52 | var uri=e.dataTransfer.getData("text/uri-list");
53 | var todo = {text:uri, done:false, uri: uri};
54 | if (/\.png$/.test(uri) || /\.jpg$/.test(uri)) {
55 | hasImage = true;
56 | todo.validImage = true;
57 | todo.imageUrl = PLACEHOLDER_IMAGE;
58 | }
59 | newTodos.push(todo);
60 | }
61 |
62 | $scope.$apply(function() {
63 | $scope.dropText = defaultDropText;
64 | $scope.dropClass = '';
65 | for (var i = 0; i < newTodos.length; i++) {
66 | $scope.todos.push(newTodos[i]);
67 | }
68 | if (hasImage) $scope.loadImages();
69 | $scope.save();
70 | });
71 | }
72 |
73 | // for each image with no imageUrl, start a new loader
74 | $scope.loadImages = function() {
75 | for (var i=0; i<$scope.todos.length; i++) {
76 | var todo=$scope.todos[i];
77 | if (todo.validImage && todo.imageUrl===PLACEHOLDER_IMAGE) {
78 | loadImage(todo.uri, function(blob_uri, requested_uri) {
79 | $scope.$apply(function(scope) {
80 | for (var k=0; k
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Todo
11 |
12 |
{{remaining()}} of {{todos.length}} remaining
13 | [
archive ]
14 |
22 |
27 |
28 | {{dropText}}
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/lab8_webresources/angularjs/2_loading_resources/loader.js:
--------------------------------------------------------------------------------
1 | var loadImage = function(uri, callback) {
2 | var xhr = new XMLHttpRequest();
3 | xhr.responseType = 'blob';
4 | xhr.onload = function() {
5 | callback(window.webkitURL.createObjectURL(xhr.response), uri);
6 | }
7 | xhr.open('GET', uri, true);
8 | xhr.send();
9 | }
--------------------------------------------------------------------------------
/lab8_webresources/angularjs/2_loading_resources/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChrome/chrome-app-codelab/78e1782ffe1fbfc2a25f8bf22b9c140be9891495/lab8_webresources/angularjs/2_loading_resources/loading.gif
--------------------------------------------------------------------------------
/lab8_webresources/angularjs/2_loading_resources/main.js:
--------------------------------------------------------------------------------
1 |
2 | chrome.app.runtime.onLaunched.addListener(function() {
3 | // normal launch initiated by the user, let's start clean.
4 | // note that this is not related to the persistent state, which is
5 | // appropriately handled in the window code.
6 | runApp(false);
7 | });
8 |
9 | chrome.app.runtime.onRestarted.addListener(function() {
10 | // if restarted, try to get the transient saved state
11 | runApp(true);
12 | });
13 |
14 | function runApp(readInitialState) {
15 | chrome.app.window.create('index.html',
16 | {id: 'mainwindow', width: 500, height: 500},
17 | // the create callback gets a reference to the AppWindow obj
18 | function(win) {
19 | // when the callback is executed, the DOM is loaded but no script was
20 | // loaded yet. So, let's attach to the load event.
21 | win.contentWindow.addEventListener('load', function() {
22 | if (readInitialState) {
23 | win.contentWindow.setInitialState();
24 | } else {
25 | win.contentWindow.clearInitialState();
26 | }
27 | });
28 | });
29 | }
--------------------------------------------------------------------------------
/lab8_webresources/angularjs/2_loading_resources/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Lab8b Loading external resources",
4 | "version": "1",
5 | "app": {
6 | "background": {
7 | "scripts": ["main.js"]
8 | }
9 | },
10 | "permissions": ["storage", "webview", ""],
11 | "icons": { "128": "icon.png" }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/lab8_webresources/angularjs/2_loading_resources/todo.css:
--------------------------------------------------------------------------------
1 | html {
2 | height: 100%;
3 | overflow: hidden;
4 | }
5 |
6 | body {
7 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
8 | height: 100%;
9 | }
10 |
11 | a {
12 | cursor: pointer;
13 | }
14 |
15 | webview {
16 | width: 100%;
17 | height: 200px;
18 | }
19 |
20 | @-webkit-keyframes switch-green {
21 | from { background-color: white;} to {background-color: rgb(163, 255, 163);}
22 | }
23 | @-webkit-keyframes switch-red {
24 | from { background-color: white;} to {background-color: rgb(255, 203, 203);}
25 | }
26 | .dragging {
27 | -webkit-animation: switch-green 0.5s ease-in-out 0 infinite alternate;
28 | }
29 |
30 | .invalid-dragging {
31 | -webkit-animation: switch-red 0.5s ease-in-out 0 infinite alternate;
32 | }
33 |
34 | ul {
35 | list-style: none;
36 | max-height: 120px;
37 | overflow-y: auto;
38 | }
39 |
40 | button, input[type=submit] {
41 | background-color: #0074CC;
42 | background-image: linear-gradient(top, #08C, #05C);
43 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
44 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
45 | color: white;
46 | }
47 |
48 | .done-true {
49 | text-decoration: line-through;
50 | color: grey;
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/lab8_webresources/javascript/1_webview/controller.js:
--------------------------------------------------------------------------------
1 | /**
2 |
3 | A poor man's controller - a very simple module with basic MVC functionaliies
4 |
5 | **/
6 | (function(exports) {
7 |
8 | var nextId = 1;
9 |
10 | var TodoModel = function() {
11 | this.todos = {};
12 | this.listeners = [];
13 | }
14 |
15 | TodoModel.prototype.setTodos = function(todos) {
16 | this.todos = todos;
17 | var maxId = 0;
18 | for (var id in todos) {
19 | var idInt = parseInt(id);
20 | maxId = idInt > maxId ? idInt : maxId;
21 | }
22 | nextId = maxId + 1;
23 | this.notifyListeners('reset');
24 | }
25 |
26 | TodoModel.prototype.clearTodos = function() {
27 | this.todos = {};
28 | this.notifyListeners('removed');
29 | }
30 |
31 | TodoModel.prototype.archiveDone = function() {
32 | var oldTodos = this.todos;
33 | this.todos={};
34 | for (var id in oldTodos) {
35 | if ( ! oldTodos[id].isDone ) {
36 | this.todos[id] = oldTodos[id];
37 | }
38 | }
39 | this.notifyListeners('archived');
40 | }
41 |
42 | TodoModel.prototype.setTodoState = function(id, isDone) {
43 | if ( this.todos[id].isDone != isDone ) {
44 | this.todos[id].isDone = isDone;
45 | this.notifyListeners('stateChanged', id);
46 | }
47 | }
48 |
49 | TodoModel.prototype.addTodo = function(text, isDone, extras) {
50 | var id = nextId++;
51 | this.todos[id]={'id': id, 'text': text, 'isDone': isDone, 'extras': extras};
52 | this.notifyListeners('added', id);
53 | }
54 |
55 | TodoModel.prototype.addListener = function(listener) {
56 | this.listeners.push(listener);
57 | }
58 |
59 | TodoModel.prototype.notifyListeners = function(change, param) {
60 | var this_ = this;
61 | this.listeners.forEach(function(listener) {
62 | listener(this_, change, param);
63 | });
64 | }
65 |
66 | exports.TodoModel = TodoModel;
67 |
68 | })(window);
69 |
70 |
71 |
72 | (function(exports) {
73 |
74 | var defaultDropText = "Or drop files here...";
75 |
76 | var dropText = document.getElementById('dropText');
77 | dropText.innerText = defaultDropText;
78 |
79 | // on dragOver, we will change the style and text accordingly, depending on
80 | // the data being transfered
81 | var dragOver = function(e) {
82 | e.stopPropagation();
83 | e.preventDefault();
84 | var valid = isValid(e.dataTransfer);
85 | if (valid) {
86 | dropText.innerText="Drop files and remote images and they will become Todos";
87 | document.body.classList.add("dragging");
88 | } else {
89 | dropText.innerText="Can only drop files and remote images here";
90 | document.body.classList.add("invalid-dragging");
91 | }
92 | }
93 |
94 | var isValid = function(dataTransfer) {
95 | return dataTransfer && dataTransfer.types
96 | && ( dataTransfer.types.indexOf('Files') >= 0
97 | || dataTransfer.types.indexOf('text/uri-list') >=0 )
98 | }
99 |
100 | // reset style and text to the default
101 | var dragLeave = function(e) {
102 | dropText.innerText=defaultDropText;
103 | document.body.classList.remove('dragging');
104 | document.body.classList.remove('invalid-dragging');
105 | }
106 |
107 | // on drop, we create the appropriate TODOs using dropped data
108 | var drop = function(e, model) {
109 | e.preventDefault();
110 | e.stopPropagation();
111 | if (isValid(e.dataTransfer)) {
112 | if (e.dataTransfer.types.indexOf('Files') >= 0) {
113 | var files = e.dataTransfer.files;
114 | for (var i = 0; i < files.length; i++) {
115 | var text = files[i].name+', '+files[i].size+' bytes';
116 | model.addTodo(text, false, {file: files[i]});
117 | }
118 | } else { // uris
119 | var uri=e.dataTransfer.getData("text/uri-list");
120 | model.addTodo(uri, false, {uri: uri});
121 | }
122 | }
123 |
124 | dragLeave();
125 | }
126 |
127 | exports.setDragHandlers = function(model) {
128 | document.body.addEventListener("dragover", dragOver, false);
129 | document.body.addEventListener("dragleave", dragLeave, false);
130 | document.body.addEventListener("drop", function(e) {
131 | drop(e, model);
132 | }, false);
133 | }
134 |
135 | })(window);
136 |
137 |
138 | window.addEventListener('DOMContentLoaded', function() {
139 |
140 | var model = new TodoModel();
141 | var form = document.querySelector('form');
142 | var archive = document.getElementById('archive');
143 | var list = document.getElementById('list');
144 | var todoTemplate = document.querySelector('#templates > [data-name="list"]');
145 |
146 | /**
147 | * When the form is submitted, we need to add a new todo and clear the input
148 | **/
149 | form.addEventListener('submit', function(e) {
150 | var textEl = e.target.querySelector('input[type="text"]');
151 | model.addTodo(textEl.value, false);
152 | textEl.value=null;
153 | e.preventDefault();
154 | });
155 |
156 |
157 | /**
158 | * A simple handler for the archive link
159 | **/
160 | archive.addEventListener('click', function(e) {
161 | model.archiveDone();
162 | e.preventDefault();
163 | });
164 |
165 |
166 | /**
167 | * Listen to changes in the model and trigger the appropriate changes in the view
168 | **/
169 | model.addListener(function(model, changeType, param) {
170 | if ( changeType === 'removed' || changeType === 'archived' || changeType === 'reset') {
171 | redrawUI(model);
172 | } else if ( changeType === 'added' ) {
173 | drawTodo(model.todos[param], list);
174 | } else if ( changeType === 'stateChanged') {
175 | updateTodo(model.todos[param]);
176 | }
177 | storageSave();
178 | updateCounters(model);
179 | });
180 |
181 |
182 | // If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
183 | var storageLoad = function() {
184 | chrome.storage.sync.get('todolist', function(value) {
185 | if (value && value.todolist) {
186 | model.setTodos(value.todolist);
187 | } else {
188 | model.addTodo('learn Chrome Apps', true);
189 | model.addTodo('build a Chrome App', false);
190 | }
191 | });
192 | }
193 |
194 | var storageSave = function() {
195 | chrome.storage.sync.set({'todolist': model.todos});
196 | };
197 |
198 | /**
199 | * Clean the current ToDo list and add elements again
200 | **/
201 | var redrawUI = function(model) {
202 | list.innerHTML='';
203 | for (var id in model.todos) {
204 | drawTodo(model.todos[id], list);
205 | }
206 | };
207 |
208 | /**
209 | * Deep clone a template node, set its ID and add it to the DOM container.
210 | * Add a listener to the newly added checkbox, so it can trigger the state flip
211 | * when the checkbox is clicked.
212 | **/
213 | var drawTodo = function(todoObj, container) {
214 | var el = todoTemplate.cloneNode(true);
215 | el.setAttribute('data-id', todoObj.id);
216 | container.appendChild(el);
217 | updateTodo(todoObj);
218 | var checkbox = el.querySelector('input[type="checkbox"]');
219 | checkbox.addEventListener('change', function(e) {
220 | model.setTodoState(todoObj.id, e.target.checked);
221 | });
222 | if(/^http:\/\/|https:\/\//.test(todoObj.text)) {
223 | var showUrl = el.querySelector('a');
224 | showUrl.addEventListener('click', function(e) {
225 | e.preventDefault();
226 | var webview=document.querySelector("webview");
227 | webview.src=todoObj.text;
228 | });
229 | showUrl.style.display = 'inline';
230 | }
231 | }
232 |
233 |
234 | /**
235 | * Find the element corresponding to the given ToDo model object and update its
236 | * state and description from the model.
237 | */
238 | var updateTodo = function(model) {
239 | var todoElement = list.querySelector('li[data-id="'+model.id+'"]');
240 | if (todoElement) {
241 | var checkbox = todoElement.querySelector('input[type="checkbox"]');
242 | var desc = todoElement.querySelector('span');
243 | checkbox.checked = model.isDone;
244 | desc.innerText = model.text;
245 | desc.className = "done-"+model.isDone;
246 | }
247 | }
248 |
249 | /**
250 | * Recalculate total number of ToDos and remaining ToDos and update
251 | * appropriate elements in the DOM.
252 | **/
253 | var updateCounters = function(model) {
254 | var count = 0;
255 | var notDone = 0;
256 | for (var id in model.todos) {
257 | count++;
258 | if ( ! model.todos[id].isDone ) {
259 | notDone ++;
260 | }
261 | }
262 | document.getElementById('remaining').innerText = notDone;
263 | document.getElementById('length').innerText = count;
264 | }
265 |
266 | storageLoad();
267 | setDragHandlers(model);
268 |
269 |
270 |
271 | /**
272 | * On each keypress of text input, save contents to the local
273 | * storage. On browser restart (for browser upgrade, for example)
274 | * the method setInitialState is called and the saved value
275 | * restored.
276 | *
277 | * Added for Codelab 6
278 | **/
279 | var newTodoInput = document.querySelector('input[type="text"]');
280 |
281 | window.clearInitialState = function() {
282 | chrome.storage.local.set({'newtodo': null});
283 | }
284 |
285 | window.setInitialState = function() {
286 | chrome.storage.local.get('newtodo', function(data) {
287 | if (newTodoInput && data && data.newtodo) {
288 | newTodoInput.value = data.newtodo;
289 | newTodoInput.focus();
290 | }
291 | });
292 | };
293 |
294 | var saveTransientState = function() {
295 | chrome.storage.local.set({'newtodo': newTodoInput.value});
296 | };
297 |
298 | newTodoInput.addEventListener('keypress' , function() {
299 | saveTransientState();
300 | })
301 |
302 |
303 | });
304 |
305 |
--------------------------------------------------------------------------------
/lab8_webresources/javascript/1_webview/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChrome/chrome-app-codelab/78e1782ffe1fbfc2a25f8bf22b9c140be9891495/lab8_webresources/javascript/1_webview/icon.png
--------------------------------------------------------------------------------
/lab8_webresources/javascript/1_webview/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Todo
8 |
9 |
of remaining
10 | [
archive ]
11 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/lab8_webresources/javascript/1_webview/main.js:
--------------------------------------------------------------------------------
1 |
2 | chrome.app.runtime.onLaunched.addListener(function() {
3 | // normal launch initiated by the user, let's start clean.
4 | // note that this is not related to the persistent state, which is
5 | // appropriately handled in the window code.
6 | runApp(false);
7 | });
8 |
9 | chrome.app.runtime.onRestarted.addListener(function() {
10 | // if restarted, try to get the transient saved state
11 | runApp(true);
12 | });
13 |
14 | function runApp(readInitialState) {
15 | chrome.app.window.create('index.html',
16 | {id: 'mainwindow', width: 500, height: 500},
17 | // the create callback gets a reference to the AppWindow obj
18 | function(win) {
19 | // when the callback is executed, the DOM is loaded but no script was
20 | // loaded yet. So, let's attach to the load event.
21 | win.contentWindow.addEventListener('load', function() {
22 | if (readInitialState) {
23 | win.contentWindow.setInitialState();
24 | } else {
25 | win.contentWindow.clearInitialState();
26 | }
27 | });
28 | });
29 | }
--------------------------------------------------------------------------------
/lab8_webresources/javascript/1_webview/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Lab8a Webview and Web resources",
4 | "version": "1",
5 | "app": {
6 | "background": {
7 | "scripts": ["main.js"]
8 | }
9 | },
10 | "permissions": ["storage", "webview"],
11 | "icons": { "128": "icon.png" }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/lab8_webresources/javascript/1_webview/todo.css:
--------------------------------------------------------------------------------
1 | html {
2 | height: 100%;
3 | overflow: hidden;
4 | }
5 |
6 | body {
7 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
8 | height: 100%;
9 | }
10 |
11 | a {
12 | cursor: pointer;
13 | }
14 |
15 | webview {
16 | width: 100%;
17 | height: 200px;
18 | }
19 |
20 | @-webkit-keyframes switch-green {
21 | from { background-color: white;} to {background-color: rgb(163, 255, 163);}
22 | }
23 | @-webkit-keyframes switch-red {
24 | from { background-color: white;} to {background-color: rgb(255, 203, 203);}
25 | }
26 | .dragging {
27 | -webkit-animation: switch-green 0.5s ease-in-out 0 infinite alternate;
28 | }
29 |
30 | .invalid-dragging {
31 | -webkit-animation: switch-red 0.5s ease-in-out 0 infinite alternate;
32 | }
33 |
34 | ul {
35 | list-style: none;
36 | max-height: 120px;
37 | overflow-y: auto;
38 | }
39 |
40 | button, input[type=submit] {
41 | background-color: #0074CC;
42 | background-image: linear-gradient(top, #08C, #05C);
43 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
44 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
45 | color: white;
46 | }
47 |
48 | .done-true {
49 | text-decoration: line-through;
50 | color: grey;
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/lab8_webresources/javascript/2_loading_resources/controller.js:
--------------------------------------------------------------------------------
1 | /**
2 |
3 | A poor man's controller - a very simple module with basic MVC functionaliies
4 |
5 | **/
6 | (function(exports) {
7 |
8 | var nextId = 1;
9 |
10 | var TodoModel = function() {
11 | this.todos = {};
12 | this.listeners = [];
13 | }
14 |
15 | TodoModel.prototype.setTodos = function(todos) {
16 | this.todos = todos;
17 | var maxId = 0;
18 | for (var id in todos) {
19 | var idInt = parseInt(id);
20 | maxId = idInt > maxId ? idInt : maxId;
21 | }
22 | nextId = maxId + 1;
23 | this.notifyListeners('reset');
24 | }
25 |
26 | TodoModel.prototype.clearTodos = function() {
27 | this.todos = {};
28 | this.notifyListeners('removed');
29 | }
30 |
31 | TodoModel.prototype.archiveDone = function() {
32 | var oldTodos = this.todos;
33 | this.todos={};
34 | for (var id in oldTodos) {
35 | if ( ! oldTodos[id].isDone ) {
36 | this.todos[id] = oldTodos[id];
37 | }
38 | }
39 | this.notifyListeners('archived');
40 | }
41 |
42 | TodoModel.prototype.setTodoState = function(id, isDone) {
43 | if ( this.todos[id].isDone != isDone ) {
44 | this.todos[id].isDone = isDone;
45 | this.notifyListeners('stateChanged', id);
46 | }
47 | }
48 |
49 | TodoModel.prototype.addTodo = function(text, isDone, extras) {
50 | var id = nextId++;
51 | this.todos[id]={'id': id, 'text': text, 'isDone': isDone, 'extras': extras};
52 | this.notifyListeners('added', id);
53 | }
54 |
55 | TodoModel.prototype.addListener = function(listener) {
56 | this.listeners.push(listener);
57 | }
58 |
59 | TodoModel.prototype.notifyListeners = function(change, param) {
60 | var this_ = this;
61 | this.listeners.forEach(function(listener) {
62 | listener(this_, change, param);
63 | });
64 | }
65 |
66 | exports.TodoModel = TodoModel;
67 |
68 | })(window);
69 |
70 |
71 | const PLACEHOLDER_IMAGE = "loading.gif";
72 |
73 |
74 | (function(exports) {
75 |
76 | var defaultDropText = "Or drop files here...";
77 |
78 | var dropText = document.getElementById('dropText');
79 | dropText.innerText = defaultDropText;
80 |
81 | // on dragOver, we will change the style and text accordingly, depending on
82 | // the data being transfered
83 | var dragOver = function(e) {
84 | e.stopPropagation();
85 | e.preventDefault();
86 | var valid = isValid(e.dataTransfer);
87 | if (valid) {
88 | dropText.innerText="Drop files and remote images and they will become Todos";
89 | document.body.classList.add("dragging");
90 | } else {
91 | dropText.innerText="Can only drop files and remote images here";
92 | document.body.classList.add("invalid-dragging");
93 | }
94 | }
95 |
96 | var isValid = function(dataTransfer) {
97 | return dataTransfer && dataTransfer.types
98 | && ( dataTransfer.types.indexOf('Files') >= 0
99 | || dataTransfer.types.indexOf('text/uri-list') >=0 )
100 | }
101 |
102 | // reset style and text to the default
103 | var dragLeave = function(e) {
104 | dropText.innerText=defaultDropText;
105 | document.body.classList.remove('dragging');
106 | document.body.classList.remove('invalid-dragging');
107 | }
108 |
109 | // on drop, we create the appropriate TODOs using dropped data
110 | var drop = function(e, model) {
111 | e.preventDefault();
112 | e.stopPropagation();
113 | if (isValid(e.dataTransfer)) {
114 | if (e.dataTransfer.types.indexOf('Files') >= 0) {
115 | var files = e.dataTransfer.files;
116 | for (var i = 0; i < files.length; i++) {
117 | var text = files[i].name+', '+files[i].size+' bytes';
118 | model.addTodo(text, false, {file: files[i]});
119 | }
120 | } else { // uris
121 | var uri = e.dataTransfer.getData("text/uri-list");
122 | var extras = { uri: uri };
123 | if (/\.png$/.test(uri) || /\.jpg$/.test(uri)) {
124 | hasImage = true;
125 | extras.validImage = true;
126 | extras.imageUrl = PLACEHOLDER_IMAGE;
127 | }
128 | model.addTodo(uri, false, extras);
129 | }
130 | }
131 |
132 | dragLeave();
133 | }
134 |
135 | exports.setDragHandlers = function(model) {
136 | document.body.addEventListener("dragover", dragOver, false);
137 | document.body.addEventListener("dragleave", dragLeave, false);
138 | document.body.addEventListener("drop", function(e) {
139 | drop(e, model);
140 | }, false);
141 | }
142 |
143 | })(window);
144 |
145 |
146 | window.addEventListener('DOMContentLoaded', function() {
147 |
148 | var model = new TodoModel();
149 | var form = document.querySelector('form');
150 | var archive = document.getElementById('archive');
151 | var list = document.getElementById('list');
152 | var todoTemplate = document.querySelector('#templates > [data-name="list"]');
153 |
154 | /**
155 | * When the form is submitted, we need to add a new todo and clear the input
156 | **/
157 | form.addEventListener('submit', function(e) {
158 | var textEl = e.target.querySelector('input[type="text"]');
159 | model.addTodo(textEl.value, false);
160 | textEl.value=null;
161 | e.preventDefault();
162 | });
163 |
164 |
165 | /**
166 | * A simple handler for the archive link
167 | **/
168 | archive.addEventListener('click', function(e) {
169 | model.archiveDone();
170 | e.preventDefault();
171 | });
172 |
173 |
174 | /**
175 | * Listen to changes in the model and trigger the appropriate changes in the view
176 | **/
177 | model.addListener(function(model, changeType, param) {
178 | if ( changeType === 'reset' ) {
179 | // let's invalidate all Blob URLs, since their lifetime is tied to the document's lifetime
180 | for (var id in model.todos) {
181 | if (model.todos[id].extras && model.todos[id].extras.validImage) {
182 | model.todos[id].extras.imageUrl = PLACEHOLDER_IMAGE;
183 | }
184 | }
185 | }
186 |
187 | if ( changeType === 'removed' || changeType === 'archived' || changeType === 'reset') {
188 | redrawUI(model);
189 | } else if ( changeType === 'added' ) {
190 | drawTodo(model.todos[param], list);
191 | } else if ( changeType === 'stateChanged') {
192 | updateTodo(model.todos[param]);
193 | }
194 | storageSave();
195 | updateCounters(model);
196 | });
197 |
198 |
199 | // If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
200 | var storageLoad = function() {
201 | chrome.storage.sync.get('todolist', function(value) {
202 | if (value && value.todolist) {
203 | model.setTodos(value.todolist);
204 | } else {
205 | model.addTodo('learn Chrome Apps', true);
206 | model.addTodo('build a Chrome App', false);
207 | }
208 | });
209 | }
210 |
211 | var storageSave = function() {
212 | chrome.storage.sync.set({'todolist': model.todos});
213 | };
214 |
215 | /**
216 | * Clean the current ToDo list and add elements again
217 | **/
218 | var redrawUI = function(model) {
219 | list.innerHTML='';
220 | for (var id in model.todos) {
221 | drawTodo(model.todos[id], list);
222 | }
223 | };
224 |
225 | /**
226 | * Deep clone a template node, set its ID and add it to the DOM container.
227 | * Add a listener to the newly added checkbox, so it can trigger the state flip
228 | * when the checkbox is clicked.
229 | **/
230 | var drawTodo = function(todoObj, container) {
231 | var el = todoTemplate.cloneNode(true);
232 | el.setAttribute('data-id', todoObj.id);
233 | container.appendChild(el);
234 | updateTodo(todoObj);
235 | var checkbox = el.querySelector('input[type="checkbox"]');
236 | checkbox.addEventListener('change', function(e) {
237 | model.setTodoState(todoObj.id, e.target.checked);
238 | });
239 | if(/^http:\/\/|https:\/\//.test(todoObj.text)) {
240 | var showUrl = el.querySelector('a');
241 | showUrl.addEventListener('click', function(e) {
242 | e.preventDefault();
243 | var webview=document.querySelector("webview");
244 | webview.src=todoObj.text;
245 | });
246 | showUrl.style.display = 'inline';
247 | }
248 | }
249 |
250 |
251 | /**
252 | * Find the element corresponding to the given ToDo model object and update its
253 | * state and description from the model.
254 | */
255 | var updateTodo = function(model) {
256 | var todoElement = list.querySelector('li[data-id="'+model.id+'"]');
257 | if (todoElement) {
258 | var checkbox = todoElement.querySelector('input[type="checkbox"]');
259 | var desc = todoElement.querySelector('span');
260 | checkbox.checked = model.isDone;
261 | desc.innerText = model.text;
262 | desc.className = "done-"+model.isDone;
263 |
264 | // load image, if this ToDo has image data
265 | maybeStartImageLoader(todoElement, model);
266 | }
267 | }
268 |
269 | /**
270 | * Recalculate total number of ToDos and remaining ToDos and update
271 | * appropriate elements in the DOM.
272 | **/
273 | var updateCounters = function(model) {
274 | var count = 0;
275 | var notDone = 0;
276 | for (var id in model.todos) {
277 | count++;
278 | if ( ! model.todos[id].isDone ) {
279 | notDone ++;
280 | }
281 | }
282 | document.getElementById('remaining').innerText = notDone;
283 | document.getElementById('length').innerText = count;
284 | }
285 |
286 | storageLoad();
287 | setDragHandlers(model);
288 |
289 |
290 |
291 | /**
292 | * On each keypress of text input, save contents to the local
293 | * storage. On browser restart (for browser upgrade, for example)
294 | * the method setInitialState is called and the saved value
295 | * restored.
296 | *
297 | * Added for Codelab 6
298 | **/
299 | var newTodoInput = document.querySelector('input[type="text"]');
300 |
301 | window.clearInitialState = function() {
302 | chrome.storage.local.set({'newtodo': null});
303 | }
304 |
305 | window.setInitialState = function() {
306 | chrome.storage.local.get('newtodo', function(data) {
307 | if (newTodoInput && data && data.newtodo) {
308 | newTodoInput.value = data.newtodo;
309 | newTodoInput.focus();
310 | }
311 | });
312 | };
313 |
314 | var saveTransientState = function() {
315 | chrome.storage.local.set({'newtodo': newTodoInput.value});
316 | };
317 |
318 | newTodoInput.addEventListener('keypress' , function() {
319 | saveTransientState();
320 | })
321 |
322 |
323 | /**
324 | * If the image has no imageUrl, start a new loader
325 | **/
326 | var maybeStartImageLoader = function(el, todo) {
327 | var img = el.querySelector('img');
328 | if (todo['extras'] && todo.extras.validImage && todo.extras.imageUrl) {
329 | if (todo.extras.imageUrl===PLACEHOLDER_IMAGE) {
330 | img.src = PLACEHOLDER_IMAGE;
331 | img.style.display = 'inline';
332 | window.loadImage(todo.extras.uri, function(blob_uri, requested_uri) {
333 | todo.extras.imageUrl = blob_uri;
334 | img.src = blob_uri;
335 | });
336 | } else {
337 | img.src = todo.extras.imageUrl;
338 | img.style.display = 'inline';
339 | }
340 | } else {
341 | img.style.display = 'none';
342 | }
343 | };
344 |
345 |
346 | });
347 |
348 |
--------------------------------------------------------------------------------
/lab8_webresources/javascript/2_loading_resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChrome/chrome-app-codelab/78e1782ffe1fbfc2a25f8bf22b9c140be9891495/lab8_webresources/javascript/2_loading_resources/icon.png
--------------------------------------------------------------------------------
/lab8_webresources/javascript/2_loading_resources/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Todo
8 |
9 |
of remaining
10 | [
archive ]
11 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/lab8_webresources/javascript/2_loading_resources/loader.js:
--------------------------------------------------------------------------------
1 | var loadImage = function(uri, callback) {
2 | var xhr = new XMLHttpRequest();
3 | xhr.responseType = 'blob';
4 | xhr.onload = function() {
5 | callback(window.webkitURL.createObjectURL(xhr.response), uri);
6 | }
7 | xhr.open('GET', uri, true);
8 | xhr.send();
9 | }
--------------------------------------------------------------------------------
/lab8_webresources/javascript/2_loading_resources/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChrome/chrome-app-codelab/78e1782ffe1fbfc2a25f8bf22b9c140be9891495/lab8_webresources/javascript/2_loading_resources/loading.gif
--------------------------------------------------------------------------------
/lab8_webresources/javascript/2_loading_resources/main.js:
--------------------------------------------------------------------------------
1 |
2 | chrome.app.runtime.onLaunched.addListener(function() {
3 | // normal launch initiated by the user, let's start clean.
4 | // note that this is not related to the persistent state, which is
5 | // appropriately handled in the window code.
6 | runApp(false);
7 | });
8 |
9 | chrome.app.runtime.onRestarted.addListener(function() {
10 | // if restarted, try to get the transient saved state
11 | runApp(true);
12 | });
13 |
14 | function runApp(readInitialState) {
15 | chrome.app.window.create('index.html',
16 | {id: 'mainwindow', width: 500, height: 500},
17 | // the create callback gets a reference to the AppWindow obj
18 | function(win) {
19 | // when the callback is executed, the DOM is loaded but no script was
20 | // loaded yet. So, let's attach to the load event.
21 | win.contentWindow.addEventListener('load', function() {
22 | if (readInitialState) {
23 | win.contentWindow.setInitialState();
24 | } else {
25 | win.contentWindow.clearInitialState();
26 | }
27 | });
28 | });
29 | }
--------------------------------------------------------------------------------
/lab8_webresources/javascript/2_loading_resources/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Lab8b Loading external resources",
4 | "version": "1",
5 | "app": {
6 | "background": {
7 | "scripts": ["main.js"]
8 | }
9 | },
10 | "permissions": ["storage", "webview", ""],
11 | "icons": { "128": "icon.png" }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/lab8_webresources/javascript/2_loading_resources/todo.css:
--------------------------------------------------------------------------------
1 | html {
2 | height: 100%;
3 | overflow: hidden;
4 | }
5 |
6 | body {
7 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
8 | height: 100%;
9 | }
10 |
11 | a {
12 | cursor: pointer;
13 | }
14 |
15 | webview {
16 | width: 100%;
17 | height: 200px;
18 | }
19 |
20 | @-webkit-keyframes switch-green {
21 | from { background-color: white;} to {background-color: rgb(163, 255, 163);}
22 | }
23 | @-webkit-keyframes switch-red {
24 | from { background-color: white;} to {background-color: rgb(255, 203, 203);}
25 | }
26 | .dragging {
27 | -webkit-animation: switch-green 0.5s ease-in-out 0 infinite alternate;
28 | }
29 |
30 | .invalid-dragging {
31 | -webkit-animation: switch-red 0.5s ease-in-out 0 infinite alternate;
32 | }
33 |
34 | ul {
35 | list-style: none;
36 | max-height: 120px;
37 | overflow-y: auto;
38 | }
39 |
40 | button, input[type=submit] {
41 | background-color: #0074CC;
42 | background-image: linear-gradient(top, #08C, #05C);
43 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
44 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
45 | color: white;
46 | }
47 |
48 | .done-true {
49 | text-decoration: line-through;
50 | color: grey;
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/lab9_multipleviews/angularjs/controller.js:
--------------------------------------------------------------------------------
1 | function TodoCtrl($scope) {
2 |
3 | // Notice that chrome.storage.sync.get is asynchronous
4 | chrome.storage.sync.get('todolist', function(value) {
5 | // The $apply is only necessary to execute the function inside Angular scope
6 | $scope.$apply(function() {
7 | $scope.load(value);
8 | });
9 | });
10 |
11 |
12 | // If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
13 | $scope.load = function(value) {
14 | if (value && value.todolist) {
15 | $scope.todos = value.todolist;
16 | } else {
17 | $scope.todos = [
18 | {text:'learn angular', done:true},
19 | {text:'build an angular app', done:false}];
20 | }
21 | }
22 |
23 | $scope.save = function() {
24 | chrome.storage.sync.set({'todolist': $scope.todos});
25 | };
26 |
27 | $scope.addTodo = function() {
28 | $scope.todos.push({text:$scope.todoText, done:false});
29 | $scope.todoText = '';
30 | };
31 |
32 | $scope.showUri = function(uri) {
33 | var webview=document.querySelector("webview");
34 | webview.src=uri;
35 | };
36 |
37 | $scope.remaining = function() {
38 | var count = 0;
39 | angular.forEach($scope.todos, function(todo) {
40 | count += todo.done ? 0 : 1;
41 | });
42 | return count;
43 | };
44 |
45 | $scope.archive = function() {
46 | var oldTodos = $scope.todos;
47 | $scope.todos = [];
48 | angular.forEach(oldTodos, function(todo) {
49 | if (!todo.done) $scope.todos.push(todo);
50 | });
51 | };
52 |
53 |
54 | // Once the main window is created create the drop area.
55 | chrome.app.window.create('droparea.html',
56 | {id: 'dropArea', width: 200, height: 200 },
57 | function(dropWin) {
58 | dropWin.contentWindow.$parentScope = $scope;
59 | });
60 | }
61 |
62 |
63 |
64 | var newTodoInput = null;
65 |
66 | var clearInitialState = function() {
67 | chrome.storage.local.set({'newtodo': null});
68 | }
69 |
70 | var setInitialState = function() {
71 | chrome.storage.local.get('newtodo', function(data) {
72 | if (newTodoInput && data && data.newtodo) {
73 | newTodoInput.value = data.newtodo;
74 | newTodoInput.focus();
75 | }
76 | });
77 | };
78 |
79 | window.addEventListener('load', function() {
80 | var saveTransientState = function() {
81 | chrome.storage.local.set({'newtodo': newTodoInput.value});
82 | };
83 | newTodoInput = document.querySelector('input[type="text"]');
84 | newTodoInput.addEventListener('keypress' , function() {
85 | saveTransientState();
86 | })
87 | });
88 |
89 |
90 |
--------------------------------------------------------------------------------
/lab9_multipleviews/angularjs/droparea.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | File Drop
8 |
9 |
10 | Drop Area
11 | {{dropText}}
12 |
13 |
14 |
--------------------------------------------------------------------------------
/lab9_multipleviews/angularjs/droparea.js:
--------------------------------------------------------------------------------
1 | function DropCtrl($scope) {
2 | var defaultDropText = "Or drop files here...";
3 | $scope.dropText = defaultDropText;
4 |
5 | // on dragOver, we will change the style and text accordingly, depending on
6 | // the data being transfered
7 | var dragOver = function(e) {
8 | e.stopPropagation();
9 | e.preventDefault();
10 | var valid = e.dataTransfer && e.dataTransfer.types
11 | && ( e.dataTransfer.types.indexOf('Files') >= 0
12 | || e.dataTransfer.types.indexOf('text/uri-list') >=0 )
13 | $scope.$apply(function() {
14 | $scope.dropText = valid ? "Drop files and remote images and they will become Todos"
15 | : "Can only drop files and remote images here";
16 | $scope.dropClass = valid ? "dragging" : "invalid-dragging";
17 | });
18 | };
19 |
20 | // on drop, we create the appropriate TODOs using dropped data
21 | var drop = function(e) {
22 | e.preventDefault();
23 | e.stopPropagation();
24 |
25 | var newTodos=[];
26 | if (e.dataTransfer.types.indexOf('Files') >= 0) {
27 | var files = e.dataTransfer.files;
28 | for (var i = 0; i < files.length; i++) {
29 | var text = files[i].name+', '+files[i].size+' bytes';
30 | newTodos.push({text:text, done:false, file: files[i]});
31 | }
32 | } else { // uris
33 | var uri=e.dataTransfer.getData("text/uri-list");
34 | if (/\.png$/.test(uri)) {
35 |
36 | }
37 | newTodos.push({text:uri, done:false, uri: uri});
38 | }
39 |
40 | $scope.$apply(function() {
41 | $scope.dropText = defaultDropText;
42 | $scope.dropClass = '';
43 | for (var i = 0; i < newTodos.length; i++) {
44 | // Access the main window
45 | $parentScope.todos.push(newTodos[i]);
46 | }
47 | $parentScope.save();
48 | $parentScope.$apply();
49 | });
50 | }
51 |
52 | var dragLeave = function(e) {
53 | $scope.$apply(function() {
54 | $scope.dropText = defaultDropText;
55 | $scope.dropClass = '';
56 | });
57 | };
58 | document.body.addEventListener("dragover", dragOver, false);
59 | document.body.addEventListener("dragleave", dragLeave, false);
60 | document.body.addEventListener("drop", drop, false);
61 | };
62 |
--------------------------------------------------------------------------------
/lab9_multipleviews/angularjs/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChrome/chrome-app-codelab/78e1782ffe1fbfc2a25f8bf22b9c140be9891495/lab9_multipleviews/angularjs/icon.png
--------------------------------------------------------------------------------
/lab9_multipleviews/angularjs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Todo
10 |
11 |
{{remaining()}} of {{todos.length}} remaining
12 | [
archive ]
13 |
20 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/lab9_multipleviews/angularjs/main.js:
--------------------------------------------------------------------------------
1 |
2 | chrome.app.runtime.onLaunched.addListener(function() {
3 | // normal launch initiated by the user, let's start clean.
4 | // note that this is not related to the persistent state, which is
5 | // appropriately handled in the window code.
6 | runApp(false);
7 | });
8 |
9 | chrome.app.runtime.onRestarted.addListener(function() {
10 | // if restarted, try to get the transient saved state
11 | runApp(true);
12 | });
13 |
14 | function runApp(readInitialState) {
15 | chrome.app.window.create('index.html',
16 | {id: 'mainwindow', width: 500, height: 500},
17 | // the create callback gets a reference to the AppWindow obj
18 | function(win) {
19 | // when the callback is executed, the DOM is loaded but no script was
20 | // loaded yet. So, let's attach to the load event.
21 | win.contentWindow.addEventListener('load', function() {
22 | if (readInitialState) {
23 | win.contentWindow.setInitialState();
24 | } else {
25 | win.contentWindow.clearInitialState();
26 | }
27 | });
28 | });
29 |
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/lab9_multipleviews/angularjs/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Lab9 Multiple views",
4 | "version": "1",
5 | "app": {
6 | "background": {
7 | "scripts": ["main.js"]
8 | }
9 | },
10 | "permissions": ["storage", "webview"],
11 | "icons": { "128": "icon.png" }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/lab9_multipleviews/angularjs/todo.css:
--------------------------------------------------------------------------------
1 | html {
2 | height: 100%;
3 | overflow: hidden;
4 | }
5 |
6 | body {
7 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
8 | height: 100%;
9 | }
10 |
11 | a {
12 | cursor: pointer;
13 | }
14 |
15 | webview {
16 | width: 100%;
17 | height: 200px;
18 | }
19 |
20 | @-webkit-keyframes switch-green {
21 | from { background-color: white;} to {background-color: rgb(163, 255, 163);}
22 | }
23 | @-webkit-keyframes switch-red {
24 | from { background-color: white;} to {background-color: rgb(255, 203, 203);}
25 | }
26 | .dragging {
27 | -webkit-animation: switch-green 0.5s ease-in-out 0 infinite alternate;
28 | }
29 |
30 | .invalid-dragging {
31 | -webkit-animation: switch-red 0.5s ease-in-out 0 infinite alternate;
32 | }
33 |
34 | ul {
35 | list-style: none;
36 | max-height: 120px;
37 | overflow-y: auto;
38 | }
39 |
40 | button, input[type=submit] {
41 | background-color: #0074CC;
42 | background-image: linear-gradient(top, #08C, #05C);
43 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
44 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
45 | color: white;
46 | }
47 |
48 | .done-true {
49 | text-decoration: line-through;
50 | color: grey;
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/lab9_multipleviews/javascript/controller.js:
--------------------------------------------------------------------------------
1 | /**
2 |
3 | A poor man's controller - a very simple module with basic MVC functionaliies
4 |
5 | **/
6 | (function(exports) {
7 |
8 | var nextId = 1;
9 |
10 | var TodoModel = function() {
11 | this.todos = {};
12 | this.listeners = [];
13 | }
14 |
15 | TodoModel.prototype.setTodos = function(todos) {
16 | this.todos = todos;
17 | var maxId = 0;
18 | for (var id in todos) {
19 | var idInt = parseInt(id);
20 | maxId = idInt > maxId ? idInt : maxId;
21 | }
22 | nextId = maxId + 1;
23 | this.notifyListeners('reset');
24 | }
25 |
26 | TodoModel.prototype.clearTodos = function() {
27 | this.todos = {};
28 | this.notifyListeners('removed');
29 | }
30 |
31 | TodoModel.prototype.archiveDone = function() {
32 | var oldTodos = this.todos;
33 | this.todos={};
34 | for (var id in oldTodos) {
35 | if ( ! oldTodos[id].isDone ) {
36 | this.todos[id] = oldTodos[id];
37 | }
38 | }
39 | this.notifyListeners('archived');
40 | }
41 |
42 | TodoModel.prototype.setTodoState = function(id, isDone) {
43 | if ( this.todos[id].isDone != isDone ) {
44 | this.todos[id].isDone = isDone;
45 | this.notifyListeners('stateChanged', id);
46 | }
47 | }
48 |
49 | TodoModel.prototype.addTodo = function(text, isDone, extras) {
50 | var id = nextId++;
51 | this.todos[id]={'id': id, 'text': text, 'isDone': isDone, 'extras': extras};
52 | this.notifyListeners('added', id);
53 | }
54 |
55 | TodoModel.prototype.addListener = function(listener) {
56 | this.listeners.push(listener);
57 | }
58 |
59 | TodoModel.prototype.notifyListeners = function(change, param) {
60 | var this_ = this;
61 | this.listeners.forEach(function(listener) {
62 | listener(this_, change, param);
63 | });
64 | }
65 |
66 | exports.TodoModel = TodoModel;
67 |
68 | })(window);
69 |
70 |
71 | const PLACEHOLDER_IMAGE = "loading.gif";
72 |
73 |
74 | window.addEventListener('DOMContentLoaded', function() {
75 |
76 | var model = new TodoModel();
77 | var form = document.querySelector('form');
78 | var archive = document.getElementById('archive');
79 | var list = document.getElementById('list');
80 | var todoTemplate = document.querySelector('#templates > [data-name="list"]');
81 |
82 | /**
83 | * When the form is submitted, we need to add a new todo and clear the input
84 | **/
85 | form.addEventListener('submit', function(e) {
86 | var textEl = e.target.querySelector('input[type="text"]');
87 | model.addTodo(textEl.value, false);
88 | textEl.value=null;
89 | e.preventDefault();
90 | });
91 |
92 |
93 | /**
94 | * A simple handler for the archive link
95 | **/
96 | archive.addEventListener('click', function(e) {
97 | model.archiveDone();
98 | e.preventDefault();
99 | });
100 |
101 |
102 | /**
103 | * Listen to changes in the model and trigger the appropriate changes in the view
104 | **/
105 | model.addListener(function(model, changeType, param) {
106 | if ( changeType === 'reset' ) {
107 | // let's invalidate all Blob URLs, since their lifetime is tied to the document's lifetime
108 | for (var id in model.todos) {
109 | if (model.todos[id].extras && model.todos[id].extras.validImage) {
110 | model.todos[id].extras.imageUrl = PLACEHOLDER_IMAGE;
111 | }
112 | }
113 | }
114 |
115 | if ( changeType === 'removed' || changeType === 'archived' || changeType === 'reset') {
116 | redrawUI(model);
117 | } else if ( changeType === 'added' ) {
118 | drawTodo(model.todos[param], list);
119 | } else if ( changeType === 'stateChanged') {
120 | updateTodo(model.todos[param]);
121 | }
122 | storageSave();
123 | updateCounters(model);
124 | });
125 |
126 |
127 | // If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
128 | var storageLoad = function() {
129 | chrome.storage.sync.get('todolist', function(value) {
130 | if (value && value.todolist) {
131 | model.setTodos(value.todolist);
132 | } else {
133 | model.addTodo('learn Chrome Apps', true);
134 | model.addTodo('build a Chrome App', false);
135 | }
136 | });
137 | }
138 |
139 | var storageSave = function() {
140 | chrome.storage.sync.set({'todolist': model.todos});
141 | };
142 |
143 | /**
144 | * Clean the current ToDo list and add elements again
145 | **/
146 | var redrawUI = function(model) {
147 | list.innerHTML='';
148 | for (var id in model.todos) {
149 | drawTodo(model.todos[id], list);
150 | }
151 | };
152 |
153 | /**
154 | * Deep clone a template node, set its ID and add it to the DOM container.
155 | * Add a listener to the newly added checkbox, so it can trigger the state flip
156 | * when the checkbox is clicked.
157 | **/
158 | var drawTodo = function(todoObj, container) {
159 | var el = todoTemplate.cloneNode(true);
160 | el.setAttribute('data-id', todoObj.id);
161 | container.appendChild(el);
162 | updateTodo(todoObj);
163 | var checkbox = el.querySelector('input[type="checkbox"]');
164 | checkbox.addEventListener('change', function(e) {
165 | model.setTodoState(todoObj.id, e.target.checked);
166 | });
167 | if(/^http:\/\/|https:\/\//.test(todoObj.text)) {
168 | var showUrl = el.querySelector('a');
169 | showUrl.addEventListener('click', function(e) {
170 | e.preventDefault();
171 | var webview=document.querySelector("webview");
172 | webview.src=todoObj.text;
173 | });
174 | showUrl.style.display = 'inline';
175 | }
176 | }
177 |
178 |
179 | /**
180 | * Find the element corresponding to the given ToDo model object and update its
181 | * state and description from the model.
182 | */
183 | var updateTodo = function(model) {
184 | var todoElement = list.querySelector('li[data-id="'+model.id+'"]');
185 | if (todoElement) {
186 | var checkbox = todoElement.querySelector('input[type="checkbox"]');
187 | var desc = todoElement.querySelector('span');
188 | checkbox.checked = model.isDone;
189 | desc.innerText = model.text;
190 | desc.className = "done-"+model.isDone;
191 |
192 | // load image, if this ToDo has image data
193 | maybeStartImageLoader(todoElement, model);
194 | }
195 | }
196 |
197 | /**
198 | * Recalculate total number of ToDos and remaining ToDos and update
199 | * appropriate elements in the DOM.
200 | **/
201 | var updateCounters = function(model) {
202 | var count = 0;
203 | var notDone = 0;
204 | for (var id in model.todos) {
205 | count++;
206 | if ( ! model.todos[id].isDone ) {
207 | notDone ++;
208 | }
209 | }
210 | document.getElementById('remaining').innerText = notDone;
211 | document.getElementById('length').innerText = count;
212 | }
213 |
214 | storageLoad();
215 |
216 |
217 |
218 | /**
219 | * On each keypress of text input, save contents to the local
220 | * storage. On browser restart (for browser upgrade, for example)
221 | * the method setInitialState is called and the saved value
222 | * restored.
223 | *
224 | * Added for Codelab 6
225 | **/
226 | var newTodoInput = document.querySelector('input[type="text"]');
227 |
228 | window.clearInitialState = function() {
229 | chrome.storage.local.set({'newtodo': null});
230 | }
231 |
232 | window.setInitialState = function() {
233 | chrome.storage.local.get('newtodo', function(data) {
234 | if (newTodoInput && data && data.newtodo) {
235 | newTodoInput.value = data.newtodo;
236 | newTodoInput.focus();
237 | }
238 | });
239 | };
240 |
241 | var saveTransientState = function() {
242 | chrome.storage.local.set({'newtodo': newTodoInput.value});
243 | };
244 |
245 | newTodoInput.addEventListener('keypress' , function() {
246 | saveTransientState();
247 | })
248 |
249 |
250 |
251 | /**
252 | * Using the GoogleTasks API, get the logged user's tasks of his first
253 | * task list and add them to the ToDo list
254 | *
255 | * Added for Codelab 7
256 | **/
257 | document.getElementById('importGTasks').addEventListener('click', function() {
258 | var api = new TasksAPI();
259 | var clientId = "";
260 | api.authenticate(clientId, function() {
261 | api.getLists(function(result) {
262 | console.log(result);
263 | if (!result || !result.items || result.items.length==0) {
264 | throw "No task lists available";
265 | }
266 | var listId=result.items[0].id;
267 | api.getTasks(listId, function(tasks) {
268 | console.log(tasks);
269 | for (var j=0; j
2 |
3 |
4 |
5 |
6 | File Drop
7 |
8 |
9 | Drop Area
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/lab9_multipleviews/javascript/droparea.js:
--------------------------------------------------------------------------------
1 | const PLACEHOLDER_IMAGE = "loading.gif";
2 | const defaultDropText = "Or drop files here...";
3 |
4 | var dropText = null;
5 |
6 | window.addEventListener('DOMContentLoaded', function() {
7 | dropText = document.getElementById('dropText');
8 | dropText.innerText = defaultDropText;
9 | });
10 |
11 | // on dragOver, we will change the style and text accordingly, depending on
12 | // the data being transfered
13 | var dragOver = function(e) {
14 | e.stopPropagation();
15 | e.preventDefault();
16 | var valid = isValid(e.dataTransfer);
17 | if (valid) {
18 | dropText.innerText="Drop files and remote images and they will become Todos";
19 | document.body.classList.add("dragging");
20 | } else {
21 | dropText.innerText="Can only drop files and remote images here";
22 | document.body.classList.add("invalid-dragging");
23 | }
24 | }
25 |
26 | var isValid = function(dataTransfer) {
27 | return dataTransfer && dataTransfer.types
28 | && ( dataTransfer.types.indexOf('Files') >= 0
29 | || dataTransfer.types.indexOf('text/uri-list') >=0 )
30 | }
31 |
32 | // reset style and text to the default
33 | var dragLeave = function(e) {
34 | dropText.innerText=defaultDropText;
35 | document.body.classList.remove('dragging');
36 | document.body.classList.remove('invalid-dragging');
37 | }
38 |
39 | // on drop, we create the appropriate TODOs using dropped data
40 | var drop = function(e, model) {
41 | e.preventDefault();
42 | e.stopPropagation();
43 | if (isValid(e.dataTransfer)) {
44 | if (e.dataTransfer.types.indexOf('Files') >= 0) {
45 | var files = e.dataTransfer.files;
46 | for (var i = 0; i < files.length; i++) {
47 | var text = files[i].name+', '+files[i].size+' bytes';
48 | model.addTodo(text, false, {file: files[i]});
49 | }
50 | } else { // uris
51 | var uri = e.dataTransfer.getData("text/uri-list");
52 | var extras = { uri: uri };
53 | if (/\.png$/.test(uri) || /\.jpg$/.test(uri)) {
54 | hasImage = true;
55 | extras.validImage = true;
56 | extras.imageUrl = PLACEHOLDER_IMAGE;
57 | }
58 | model.addTodo(uri, false, extras);
59 | }
60 | }
61 |
62 | dragLeave();
63 | }
64 |
65 |
66 | window.setDragHandlers = function(model) {
67 | document.body.addEventListener("dragover", dragOver, false);
68 | document.body.addEventListener("dragleave", dragLeave, false);
69 | document.body.addEventListener("drop", function(e) {
70 | drop(e, model);
71 | }, false);
72 | }
73 |
74 |
--------------------------------------------------------------------------------
/lab9_multipleviews/javascript/gapi_tasks.js:
--------------------------------------------------------------------------------
1 | (function(context) {
2 |
3 | TasksAPI = function() {
4 | }
5 |
6 | TasksAPI.prototype.authenticate = function(clientId, success) {
7 | var authUrl="https://accounts.google.com/o/oauth2/auth"+
8 | "?response_type=token"+
9 | "&client_id="+clientId+
10 | "&redirect_uri=https://"+chrome.runtime.id+".chromiumapp.org/"+
11 | "&scope=https://www.googleapis.com/auth/tasks.readonly";
12 |
13 | var identityDetails = {
14 | url: authUrl,
15 | interactive: true
16 | };
17 |
18 | var _this = this;
19 |
20 | chrome.experimental.identity.launchWebAuthFlow(identityDetails, function(responseUrl) {
21 | if (!/access_token=(.*?)&/.test(responseUrl)) {
22 | throw "Invaild response from oauth server: "+responseUrl;
23 | }
24 | _this.accessToken=RegExp.$1;
25 |
26 | if (success) success();
27 |
28 | });
29 |
30 | }
31 |
32 | TasksAPI.prototype.getLists = function(callback) {
33 | this.request("users/@me/lists", callback);
34 | }
35 |
36 | TasksAPI.prototype.getTasks = function(listId, callback) {
37 | this.request("lists/"+listId+"/tasks", callback);
38 | }
39 |
40 | TasksAPI.prototype.request = function(method, callback, args) {
41 | var xhr = new XMLHttpRequest();
42 | xhr.onload = function() {
43 | if (callback) {
44 | callback(JSON.parse(xhr.response));
45 | }
46 | };
47 |
48 | var url="https://www.googleapis.com/tasks/v1/"+method+"?access_token="+this.accessToken;
49 | if (args) for (arg in args) {
50 | // TODO: urlescape arg and args[arg]
51 | url += "&"+arg+"="+args[arg];
52 | }
53 |
54 | xhr.open("GET", url);
55 | xhr.send();
56 | }
57 |
58 | context.TasksAPI = TasksAPI;
59 | })(window);
60 |
--------------------------------------------------------------------------------
/lab9_multipleviews/javascript/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChrome/chrome-app-codelab/78e1782ffe1fbfc2a25f8bf22b9c140be9891495/lab9_multipleviews/javascript/icon.png
--------------------------------------------------------------------------------
/lab9_multipleviews/javascript/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Todo
8 |
9 |
of remaining
10 | [
archive ]
11 |
13 |
18 |
import tasks from GTasks
19 |
20 |
21 |
22 |
23 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/lab9_multipleviews/javascript/loader.js:
--------------------------------------------------------------------------------
1 | var loadImage = function(uri, callback) {
2 | var xhr = new XMLHttpRequest();
3 | xhr.responseType = 'blob';
4 | xhr.onload = function() {
5 | callback(window.webkitURL.createObjectURL(xhr.response), uri);
6 | }
7 | xhr.open('GET', uri, true);
8 | xhr.send();
9 | }
--------------------------------------------------------------------------------
/lab9_multipleviews/javascript/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GoogleChrome/chrome-app-codelab/78e1782ffe1fbfc2a25f8bf22b9c140be9891495/lab9_multipleviews/javascript/loading.gif
--------------------------------------------------------------------------------
/lab9_multipleviews/javascript/main.js:
--------------------------------------------------------------------------------
1 |
2 | chrome.app.runtime.onLaunched.addListener(function() {
3 | // normal launch initiated by the user, let's start clean.
4 | // note that this is not related to the persistent state, which is
5 | // appropriately handled in the window code.
6 | runApp(false);
7 | });
8 |
9 | chrome.app.runtime.onRestarted.addListener(function() {
10 | // if restarted, try to get the transient saved state
11 | runApp(true);
12 | });
13 |
14 | function runApp(readInitialState) {
15 | chrome.app.window.create('index.html',
16 | {id: 'mainwindow', width: 500, height: 500},
17 | // the create callback gets a reference to the AppWindow obj
18 | function(win) {
19 | // when the callback is executed, the DOM is loaded but no script was
20 | // loaded yet. So, let's attach to the load event.
21 | win.contentWindow.addEventListener('load', function() {
22 | if (readInitialState) {
23 | win.contentWindow.setInitialState();
24 | } else {
25 | win.contentWindow.clearInitialState();
26 | }
27 | });
28 | });
29 |
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/lab9_multipleviews/javascript/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "Lab9 Multiple views",
4 | "version": "1",
5 | "app": {
6 | "background": {
7 | "scripts": ["main.js"]
8 | }
9 | },
10 | "permissions": ["storage", "webview", ""],
11 | "icons": { "128": "icon.png" }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/lab9_multipleviews/javascript/todo.css:
--------------------------------------------------------------------------------
1 | html {
2 | height: 100%;
3 | overflow: hidden;
4 | }
5 |
6 | body {
7 | font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
8 | height: 100%;
9 | }
10 |
11 | a {
12 | cursor: pointer;
13 | }
14 |
15 | webview {
16 | width: 100%;
17 | height: 200px;
18 | }
19 |
20 | @-webkit-keyframes switch-green {
21 | from { background-color: white;} to {background-color: rgb(163, 255, 163);}
22 | }
23 | @-webkit-keyframes switch-red {
24 | from { background-color: white;} to {background-color: rgb(255, 203, 203);}
25 | }
26 | .dragging {
27 | -webkit-animation: switch-green 0.5s ease-in-out 0 infinite alternate;
28 | }
29 |
30 | .invalid-dragging {
31 | -webkit-animation: switch-red 0.5s ease-in-out 0 infinite alternate;
32 | }
33 |
34 | ul {
35 | list-style: none;
36 | max-height: 120px;
37 | overflow-y: auto;
38 | }
39 |
40 | button, input[type=submit] {
41 | background-color: #0074CC;
42 | background-image: linear-gradient(top, #08C, #05C);
43 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
44 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
45 | color: white;
46 | }
47 |
48 | .done-true {
49 | text-decoration: line-through;
50 | color: grey;
51 | }
52 |
53 |
--------------------------------------------------------------------------------