diff --git a/lab3_mvc/javascript/simpleview/controller.js b/lab3_mvc/javascript/simpleview/controller.js
new file mode 100644
index 0000000..9c5cca4
--- /dev/null
+++ b/lab3_mvc/javascript/simpleview/controller.js
@@ -0,0 +1,70 @@
+
+/**
+
+ A poor man's controller - a very simple module with basic MVC functionaliies
+
+**/
+(function(exports) {
+
+ var TodoModel = function() {
+ this.contents = '';
+ this.listeners = [];
+ }
+
+ TodoModel.prototype.setContents = function(contents) {
+ if (this.contents !== contents) {
+ this.contents = contents;
+ this.notifyListeners();
+ }
+ }
+
+ TodoModel.prototype.bindToInput = function(input) {
+ var _this = this;
+
+ var eventHandler = function(e) {
+ if (typeof(e.target.value) != 'undefined') {
+ _this.setContents(e.target.value);
+ }
+ };
+
+ input.addEventListener('keyup', eventHandler);
+ input.addEventListener('change', eventHandler);
+ }
+
+ TodoModel.prototype.bindToView = function(view) {
+ this.addListener(function(model) {
+ if (view.setValue) {
+ view.setValue(model.contents);
+ } else {
+ view.innerText=model.contents;
+ }
+ });
+ }
+
+ TodoModel.prototype.addListener = function(listener) {
+ this.listeners.push(listener);
+ }
+
+ TodoModel.prototype.notifyListeners = function() {
+ var this_ = this;
+ this.listeners.forEach(function(listener) {
+ listener(this_);
+ });
+ }
+
+ exports.TodoModel = TodoModel;
+
+})(window);
+
+
+window.addEventListener('DOMContentLoaded', function() {
+
+ var model = new TodoModel();
+ var newTodo = document.getElementById('newTodo');
+ var todoText = document.getElementById('todoText');
+
+ model.bindToView(todoText);
+ model.bindToInput(newTodo);
+
+})
+
diff --git a/lab3_mvc/javascript/simpleview/icon.png b/lab3_mvc/javascript/simpleview/icon.png
new file mode 100644
index 0000000..3c5c99a
Binary files /dev/null and b/lab3_mvc/javascript/simpleview/icon.png differ
diff --git a/lab3_mvc/javascript/simpleview/index.html b/lab3_mvc/javascript/simpleview/index.html
new file mode 100644
index 0000000..3270d55
--- /dev/null
+++ b/lab3_mvc/javascript/simpleview/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+ Todo
+
+
+
+
diff --git a/lab3_mvc/javascript/simpleview/main.js b/lab3_mvc/javascript/simpleview/main.js
new file mode 100644
index 0000000..48f4770
--- /dev/null
+++ b/lab3_mvc/javascript/simpleview/main.js
@@ -0,0 +1,5 @@
+chrome.app.runtime.onLaunched.addListener(function() {
+ chrome.app.window.create('index.html',
+ {width: 500, height: 309});
+});
+
diff --git a/lab3_mvc/javascript/simpleview/manifest.json b/lab3_mvc/javascript/simpleview/manifest.json
new file mode 100644
index 0000000..63b3df2
--- /dev/null
+++ b/lab3_mvc/javascript/simpleview/manifest.json
@@ -0,0 +1,12 @@
+{
+ "manifest_version": 2,
+ "name": "Lab3a Simple MVC",
+ "version": "1",
+ "app": {
+ "background": {
+ "scripts": ["main.js"]
+ }
+ },
+ "icons": { "128": "icon.png" }
+}
+
diff --git a/lab3_mvc/javascript/simpleview/todo.css b/lab3_mvc/javascript/simpleview/todo.css
new file mode 100644
index 0000000..b35d3dd
--- /dev/null
+++ b/lab3_mvc/javascript/simpleview/todo.css
@@ -0,0 +1,21 @@
+body {
+ font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
+}
+
+ul {
+ list-style: none;
+}
+
+button, input[type=submit] {
+ background-color: #0074CC;
+ background-image: linear-gradient(top, #08C, #05C);
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ color: white;
+}
+
+.done-true {
+ text-decoration: line-through;
+ color: grey;
+}
+
diff --git a/lab3_mvc/javascript/withcontroller/controller.js b/lab3_mvc/javascript/withcontroller/controller.js
new file mode 100644
index 0000000..1821445
--- /dev/null
+++ b/lab3_mvc/javascript/withcontroller/controller.js
@@ -0,0 +1,164 @@
+/**
+
+ A poor man's controller - a very simple module with basic MVC functionaliies
+
+**/
+(function(exports) {
+
+ var nextId = 1;
+
+ var TodoModel = function() {
+ this.todos = {};
+ this.listeners = [];
+ }
+
+ TodoModel.prototype.clearTodos = function() {
+ this.todos = {};
+ this.notifyListeners('removed');
+ }
+
+ TodoModel.prototype.archiveDone = function() {
+ var oldTodos = this.todos;
+ this.todos={};
+ for (var id in oldTodos) {
+ if ( ! oldTodos[id].isDone ) {
+ this.todos[id] = oldTodos[id];
+ }
+ }
+ this.notifyListeners('archived');
+ }
+
+ TodoModel.prototype.setTodoState = function(id, isDone) {
+ if ( this.todos[id].isDone != isDone ) {
+ this.todos[id].isDone = isDone;
+ this.notifyListeners('stateChanged', id);
+ }
+ }
+
+ TodoModel.prototype.addTodo = function(text, isDone) {
+ var id = nextId++;
+ this.todos[id]={'id': id, 'text': text, 'isDone': isDone};
+ this.notifyListeners('added', id);
+ }
+
+ TodoModel.prototype.addListener = function(listener) {
+ this.listeners.push(listener);
+ }
+
+ TodoModel.prototype.notifyListeners = function(change, param) {
+ var this_ = this;
+ this.listeners.forEach(function(listener) {
+ listener(this_, change, param);
+ });
+ }
+
+ exports.TodoModel = TodoModel;
+
+})(window);
+
+
+window.addEventListener('DOMContentLoaded', function() {
+
+ var model = new TodoModel();
+ var form = document.querySelector('form');
+ var archive = document.getElementById('archive');
+ var list = document.getElementById('list');
+ var todoTemplate = document.querySelector('#templates > [data-name="list"]');
+
+ /**
+ * When the form is submitted, we need to add a new todo and clear the input
+ **/
+ form.addEventListener('submit', function(e) {
+ var textEl = e.target.querySelector('input[type="text"]');
+ model.addTodo(textEl.value, false);
+ textEl.value=null;
+ e.preventDefault();
+ });
+
+
+ /**
+ * A simple handler for the archive link
+ **/
+ archive.addEventListener('click', function(e) {
+ model.archiveDone();
+ e.preventDefault();
+ });
+
+
+ /**
+ * Listen to changes in the model and trigger the appropriate changes in the view
+ **/
+ model.addListener(function(model, changeType, param) {
+ if ( changeType === 'removed' || changeType === 'archived') {
+ redrawUI(model);
+ } else if ( changeType === 'added' ) {
+ drawTodo(model.todos[param], list);
+ } else if ( changeType === 'stateChanged') {
+ updateTodo(model.todos[param]);
+ }
+ updateCounters(model);
+ });
+
+ /**
+ * Clean the current ToDo list and add elements again
+ **/
+ var redrawUI = function(model) {
+ list.innerHTML='';
+ for (var id in model.todos) {
+ drawTodo(model.todos[id], list);
+ }
+ };
+
+ /**
+ * Deep clone a template node, set its ID and add it to the DOM container.
+ * Add a listener to the newly added checkbox, so it can trigger the state flip
+ * when the checkbox is clicked.
+ **/
+ var drawTodo = function(todoObj, container) {
+ var el = todoTemplate.cloneNode(true);
+ el.setAttribute('data-id', todoObj.id);
+ container.appendChild(el);
+ updateTodo(todoObj);
+ var checkbox = el.querySelector('input[type="checkbox"]');
+ checkbox.addEventListener('change', function(e) {
+ model.setTodoState(todoObj.id, e.target.checked);
+ });
+ }
+
+
+ /**
+ * Find the element corresponding to the given ToDo model object and update its
+ * state and description from the model.
+ */
+ var updateTodo = function(model) {
+ var todoElement = list.querySelector('li[data-id="'+model.id+'"]');
+ if (todoElement) {
+ var checkbox = todoElement.querySelector('input[type="checkbox"]');
+ var desc = todoElement.querySelector('span');
+ checkbox.checked = model.isDone;
+ desc.innerText = model.text;
+ desc.className = "done-"+model.isDone;
+ }
+ }
+
+ /**
+ * Recalculate total number of ToDos and remaining ToDos and update
+ * appropriate elements in the DOM.
+ **/
+ var updateCounters = function(model) {
+ var count = 0;
+ var notDone = 0;
+ for (var id in model.todos) {
+ count++;
+ if ( ! model.todos[id].isDone ) {
+ notDone ++;
+ }
+ }
+ document.getElementById('remaining').innerText = notDone;
+ document.getElementById('length').innerText = count;
+ }
+
+ updateCounters(model);
+
+});
+
diff --git a/lab3_mvc/javascript/withcontroller/icon.png b/lab3_mvc/javascript/withcontroller/icon.png
new file mode 100644
index 0000000..3c5c99a
Binary files /dev/null and b/lab3_mvc/javascript/withcontroller/icon.png differ
diff --git a/lab3_mvc/javascript/withcontroller/index.html b/lab3_mvc/javascript/withcontroller/index.html
new file mode 100644
index 0000000..c0810b8
--- /dev/null
+++ b/lab3_mvc/javascript/withcontroller/index.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+ Todo
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lab3_mvc/javascript/withcontroller/main.js b/lab3_mvc/javascript/withcontroller/main.js
new file mode 100644
index 0000000..48f4770
--- /dev/null
+++ b/lab3_mvc/javascript/withcontroller/main.js
@@ -0,0 +1,5 @@
+chrome.app.runtime.onLaunched.addListener(function() {
+ chrome.app.window.create('index.html',
+ {width: 500, height: 309});
+});
+
diff --git a/lab3_mvc/javascript/withcontroller/manifest.json b/lab3_mvc/javascript/withcontroller/manifest.json
new file mode 100644
index 0000000..c3c11ce
--- /dev/null
+++ b/lab3_mvc/javascript/withcontroller/manifest.json
@@ -0,0 +1,12 @@
+{
+ "manifest_version": 2,
+ "name": "Lab3b MVC with controller",
+ "version": "1",
+ "app": {
+ "background": {
+ "scripts": ["main.js"]
+ }
+ },
+ "icons": { "128": "icon.png" }
+}
+
diff --git a/lab3_mvc/javascript/withcontroller/todo.css b/lab3_mvc/javascript/withcontroller/todo.css
new file mode 100644
index 0000000..b35d3dd
--- /dev/null
+++ b/lab3_mvc/javascript/withcontroller/todo.css
@@ -0,0 +1,21 @@
+body {
+ font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
+}
+
+ul {
+ list-style: none;
+}
+
+button, input[type=submit] {
+ background-color: #0074CC;
+ background-image: linear-gradient(top, #08C, #05C);
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ color: white;
+}
+
+.done-true {
+ text-decoration: line-through;
+ color: grey;
+}
+
diff --git a/lab5_data/javascript/1_storage_sync/controller.js b/lab5_data/javascript/1_storage_sync/controller.js
new file mode 100644
index 0000000..11e8f6a
--- /dev/null
+++ b/lab5_data/javascript/1_storage_sync/controller.js
@@ -0,0 +1,193 @@
+/**
+
+ A poor man's controller - a very simple module with basic MVC functionaliies
+
+**/
+(function(exports) {
+
+ var nextId = 1;
+
+ var TodoModel = function() {
+ this.todos = {};
+ this.listeners = [];
+ }
+
+ TodoModel.prototype.setTodos = function(todos) {
+ this.todos = todos;
+ var maxId = 0;
+ for (var id in todos) {
+ var idInt = parseInt(id);
+ maxId = idInt > maxId ? idInt : maxId;
+ }
+ nextId = maxId + 1;
+ this.notifyListeners('reset');
+ }
+
+ TodoModel.prototype.clearTodos = function() {
+ this.todos = {};
+ this.notifyListeners('removed');
+ }
+
+ TodoModel.prototype.archiveDone = function() {
+ var oldTodos = this.todos;
+ this.todos={};
+ for (var id in oldTodos) {
+ if ( ! oldTodos[id].isDone ) {
+ this.todos[id] = oldTodos[id];
+ }
+ }
+ this.notifyListeners('archived');
+ }
+
+ TodoModel.prototype.setTodoState = function(id, isDone) {
+ if ( this.todos[id].isDone != isDone ) {
+ this.todos[id].isDone = isDone;
+ this.notifyListeners('stateChanged', id);
+ }
+ }
+
+ TodoModel.prototype.addTodo = function(text, isDone) {
+ var id = nextId++;
+ this.todos[id]={'id': id, 'text': text, 'isDone': isDone};
+ this.notifyListeners('added', id);
+ }
+
+ TodoModel.prototype.addListener = function(listener) {
+ this.listeners.push(listener);
+ }
+
+ TodoModel.prototype.notifyListeners = function(change, param) {
+ var this_ = this;
+ this.listeners.forEach(function(listener) {
+ listener(this_, change, param);
+ });
+ }
+
+ exports.TodoModel = TodoModel;
+
+})(window);
+
+
+window.addEventListener('DOMContentLoaded', function() {
+
+ var model = new TodoModel();
+ var form = document.querySelector('form');
+ var archive = document.getElementById('archive');
+ var list = document.getElementById('list');
+ var todoTemplate = document.querySelector('#templates > [data-name="list"]');
+
+ /**
+ * When the form is submitted, we need to add a new todo and clear the input
+ **/
+ form.addEventListener('submit', function(e) {
+ var textEl = e.target.querySelector('input[type="text"]');
+ model.addTodo(textEl.value, false);
+ textEl.value=null;
+ e.preventDefault();
+ });
+
+
+ /**
+ * A simple handler for the archive link
+ **/
+ archive.addEventListener('click', function(e) {
+ model.archiveDone();
+ e.preventDefault();
+ });
+
+
+ /**
+ * Listen to changes in the model and trigger the appropriate changes in the view
+ **/
+ model.addListener(function(model, changeType, param) {
+ if ( changeType === 'removed' || changeType === 'archived' || changeType === 'reset') {
+ redrawUI(model);
+ } else if ( changeType === 'added' ) {
+ drawTodo(model.todos[param], list);
+ } else if ( changeType === 'stateChanged') {
+ updateTodo(model.todos[param]);
+ }
+ storageSave();
+ updateCounters(model);
+ });
+
+
+ // If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
+ var storageLoad = function() {
+ chrome.storage.sync.get('todolist', function(value) {
+ if (value && value.todolist) {
+ model.setTodos(value.todolist);
+ } else {
+ model.addTodo('learn Chrome Apps', true);
+ model.addTodo('build a Chrome App', false);
+ }
+ });
+ }
+
+ var storageSave = function() {
+ chrome.storage.sync.set({'todolist': model.todos});
+ };
+
+ /**
+ * Clean the current ToDo list and add elements again
+ **/
+ var redrawUI = function(model) {
+ list.innerHTML='';
+ for (var id in model.todos) {
+ drawTodo(model.todos[id], list);
+ }
+ };
+
+ /**
+ * Deep clone a template node, set its ID and add it to the DOM container.
+ * Add a listener to the newly added checkbox, so it can trigger the state flip
+ * when the checkbox is clicked.
+ **/
+ var drawTodo = function(todoObj, container) {
+ var el = todoTemplate.cloneNode(true);
+ el.setAttribute('data-id', todoObj.id);
+ container.appendChild(el);
+ updateTodo(todoObj);
+ var checkbox = el.querySelector('input[type="checkbox"]');
+ checkbox.addEventListener('change', function(e) {
+ model.setTodoState(todoObj.id, e.target.checked);
+ });
+ }
+
+
+ /**
+ * Find the element corresponding to the given ToDo model object and update its
+ * state and description from the model.
+ */
+ var updateTodo = function(model) {
+ var todoElement = list.querySelector('li[data-id="'+model.id+'"]');
+ if (todoElement) {
+ var checkbox = todoElement.querySelector('input[type="checkbox"]');
+ var desc = todoElement.querySelector('span');
+ checkbox.checked = model.isDone;
+ desc.innerText = model.text;
+ desc.className = "done-"+model.isDone;
+ }
+ }
+
+ /**
+ * Recalculate total number of ToDos and remaining ToDos and update
+ * appropriate elements in the DOM.
+ **/
+ var updateCounters = function(model) {
+ var count = 0;
+ var notDone = 0;
+ for (var id in model.todos) {
+ count++;
+ if ( ! model.todos[id].isDone ) {
+ notDone ++;
+ }
+ }
+ document.getElementById('remaining').innerText = notDone;
+ document.getElementById('length').innerText = count;
+ }
+
+ storageLoad();
+
+});
+
diff --git a/lab5_data/javascript/1_storage_sync/icon.png b/lab5_data/javascript/1_storage_sync/icon.png
new file mode 100644
index 0000000..ffd3426
Binary files /dev/null and b/lab5_data/javascript/1_storage_sync/icon.png differ
diff --git a/lab5_data/javascript/1_storage_sync/index.html b/lab5_data/javascript/1_storage_sync/index.html
new file mode 100644
index 0000000..c0810b8
--- /dev/null
+++ b/lab5_data/javascript/1_storage_sync/index.html
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+ Todo
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lab5_data/javascript/1_storage_sync/main.js b/lab5_data/javascript/1_storage_sync/main.js
new file mode 100644
index 0000000..48f4770
--- /dev/null
+++ b/lab5_data/javascript/1_storage_sync/main.js
@@ -0,0 +1,5 @@
+chrome.app.runtime.onLaunched.addListener(function() {
+ chrome.app.window.create('index.html',
+ {width: 500, height: 309});
+});
+
diff --git a/lab5_data/javascript/1_storage_sync/manifest.json b/lab5_data/javascript/1_storage_sync/manifest.json
new file mode 100644
index 0000000..d183837
--- /dev/null
+++ b/lab5_data/javascript/1_storage_sync/manifest.json
@@ -0,0 +1,13 @@
+{
+ "manifest_version": 2,
+ "name": "Lab5a Storage sync",
+ "version": "1",
+ "app": {
+ "background": {
+ "scripts": ["main.js"]
+ }
+ },
+ "permissions": ["storage"],
+ "icons": { "128": "icon.png" }
+}
+
diff --git a/lab5_data/javascript/1_storage_sync/todo.css b/lab5_data/javascript/1_storage_sync/todo.css
new file mode 100644
index 0000000..43f107e
--- /dev/null
+++ b/lab5_data/javascript/1_storage_sync/todo.css
@@ -0,0 +1,23 @@
+body {
+ font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
+}
+
+ul {
+ list-style: none;
+ max-height: 120px;
+ overflow-y: auto;
+}
+
+button, input[type=submit] {
+ background-color: #0074CC;
+ background-image: linear-gradient(top, #08C, #05C);
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ color: white;
+}
+
+.done-true {
+ text-decoration: line-through;
+ color: grey;
+}
+
diff --git a/lab5_data/javascript/2_drop_files/controller.js b/lab5_data/javascript/2_drop_files/controller.js
new file mode 100644
index 0000000..7f524c7
--- /dev/null
+++ b/lab5_data/javascript/2_drop_files/controller.js
@@ -0,0 +1,261 @@
+/**
+
+ A poor man's controller - a very simple module with basic MVC functionaliies
+
+**/
+(function(exports) {
+
+ var nextId = 1;
+
+ var TodoModel = function() {
+ this.todos = {};
+ this.listeners = [];
+ }
+
+ TodoModel.prototype.setTodos = function(todos) {
+ this.todos = todos;
+ var maxId = 0;
+ for (var id in todos) {
+ var idInt = parseInt(id);
+ maxId = idInt > maxId ? idInt : maxId;
+ }
+ nextId = maxId + 1;
+ this.notifyListeners('reset');
+ }
+
+ TodoModel.prototype.clearTodos = function() {
+ this.todos = {};
+ this.notifyListeners('removed');
+ }
+
+ TodoModel.prototype.archiveDone = function() {
+ var oldTodos = this.todos;
+ this.todos={};
+ for (var id in oldTodos) {
+ if ( ! oldTodos[id].isDone ) {
+ this.todos[id] = oldTodos[id];
+ }
+ }
+ this.notifyListeners('archived');
+ }
+
+ TodoModel.prototype.setTodoState = function(id, isDone) {
+ if ( this.todos[id].isDone != isDone ) {
+ this.todos[id].isDone = isDone;
+ this.notifyListeners('stateChanged', id);
+ }
+ }
+
+ TodoModel.prototype.addTodo = function(text, isDone, extras) {
+ var id = nextId++;
+ this.todos[id]={'id': id, 'text': text, 'isDone': isDone, 'extras': extras};
+ this.notifyListeners('added', id);
+ }
+
+ TodoModel.prototype.addListener = function(listener) {
+ this.listeners.push(listener);
+ }
+
+ TodoModel.prototype.notifyListeners = function(change, param) {
+ var this_ = this;
+ this.listeners.forEach(function(listener) {
+ listener(this_, change, param);
+ });
+ }
+
+ exports.TodoModel = TodoModel;
+
+})(window);
+
+
+
+(function(exports) {
+
+ var defaultDropText = "Or drop files here...";
+
+ var dropText = document.getElementById('dropText');
+ dropText.innerText = defaultDropText;
+
+ // on dragOver, we will change the style and text accordingly, depending on
+ // the data being transfered
+ var dragOver = function(e) {
+ e.stopPropagation();
+ e.preventDefault();
+ var valid = isValid(e.dataTransfer);
+ if (valid) {
+ dropText.innerText="Drop files and remote images and they will become Todos";
+ document.body.classList.add("dragging");
+ } else {
+ dropText.innerText="Can only drop files and remote images here";
+ document.body.classList.add("invalid-dragging");
+ }
+ }
+
+ var isValid = function(dataTransfer) {
+ return dataTransfer && dataTransfer.types
+ && ( dataTransfer.types.indexOf('Files') >= 0
+ || dataTransfer.types.indexOf('text/uri-list') >=0 )
+ }
+
+ // reset style and text to the default
+ var dragLeave = function(e) {
+ dropText.innerText=defaultDropText;
+ document.body.classList.remove('dragging');
+ document.body.classList.remove('invalid-dragging');
+ }
+
+ // on drop, we create the appropriate TODOs using dropped data
+ var drop = function(e, model) {
+ e.preventDefault();
+ e.stopPropagation();
+ if (isValid(e.dataTransfer)) {
+ if (e.dataTransfer.types.indexOf('Files') >= 0) {
+ var files = e.dataTransfer.files;
+ for (var i = 0; i < files.length; i++) {
+ var text = files[i].name+', '+files[i].size+' bytes';
+ model.addTodo(text, false, {file: files[i]});
+ }
+ } else { // uris
+ var uri=e.dataTransfer.getData("text/uri-list");
+ model.addTodo(uri, false, {uri: uri});
+ }
+ }
+
+ dragLeave();
+ }
+
+ exports.setDragHandlers = function(model) {
+ document.body.addEventListener("dragover", dragOver, false);
+ document.body.addEventListener("dragleave", dragLeave, false);
+ document.body.addEventListener("drop", function(e) {
+ drop(e, model);
+ }, false);
+ }
+
+})(window);
+
+
+window.addEventListener('DOMContentLoaded', function() {
+
+ var model = new TodoModel();
+ var form = document.querySelector('form');
+ var archive = document.getElementById('archive');
+ var list = document.getElementById('list');
+ var todoTemplate = document.querySelector('#templates > [data-name="list"]');
+
+ /**
+ * When the form is submitted, we need to add a new todo and clear the input
+ **/
+ form.addEventListener('submit', function(e) {
+ var textEl = e.target.querySelector('input[type="text"]');
+ model.addTodo(textEl.value, false);
+ textEl.value=null;
+ e.preventDefault();
+ });
+
+
+ /**
+ * A simple handler for the archive link
+ **/
+ archive.addEventListener('click', function(e) {
+ model.archiveDone();
+ e.preventDefault();
+ });
+
+
+ /**
+ * Listen to changes in the model and trigger the appropriate changes in the view
+ **/
+ model.addListener(function(model, changeType, param) {
+ if ( changeType === 'removed' || changeType === 'archived' || changeType === 'reset') {
+ redrawUI(model);
+ } else if ( changeType === 'added' ) {
+ drawTodo(model.todos[param], list);
+ } else if ( changeType === 'stateChanged') {
+ updateTodo(model.todos[param]);
+ }
+ storageSave();
+ updateCounters(model);
+ });
+
+
+ // If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
+ var storageLoad = function() {
+ chrome.storage.sync.get('todolist', function(value) {
+ if (value && value.todolist) {
+ model.setTodos(value.todolist);
+ } else {
+ model.addTodo('learn Chrome Apps', true);
+ model.addTodo('build a Chrome App', false);
+ }
+ });
+ }
+
+ var storageSave = function() {
+ chrome.storage.sync.set({'todolist': model.todos});
+ };
+
+ /**
+ * Clean the current ToDo list and add elements again
+ **/
+ var redrawUI = function(model) {
+ list.innerHTML='';
+ for (var id in model.todos) {
+ drawTodo(model.todos[id], list);
+ }
+ };
+
+ /**
+ * Deep clone a template node, set its ID and add it to the DOM container.
+ * Add a listener to the newly added checkbox, so it can trigger the state flip
+ * when the checkbox is clicked.
+ **/
+ var drawTodo = function(todoObj, container) {
+ var el = todoTemplate.cloneNode(true);
+ el.setAttribute('data-id', todoObj.id);
+ container.appendChild(el);
+ updateTodo(todoObj);
+ var checkbox = el.querySelector('input[type="checkbox"]');
+ checkbox.addEventListener('change', function(e) {
+ model.setTodoState(todoObj.id, e.target.checked);
+ });
+ }
+
+
+ /**
+ * Find the element corresponding to the given ToDo model object and update its
+ * state and description from the model.
+ */
+ var updateTodo = function(model) {
+ var todoElement = list.querySelector('li[data-id="'+model.id+'"]');
+ if (todoElement) {
+ var checkbox = todoElement.querySelector('input[type="checkbox"]');
+ var desc = todoElement.querySelector('span');
+ checkbox.checked = model.isDone;
+ desc.innerText = model.text;
+ desc.className = "done-"+model.isDone;
+ }
+ }
+
+ /**
+ * Recalculate total number of ToDos and remaining ToDos and update
+ * appropriate elements in the DOM.
+ **/
+ var updateCounters = function(model) {
+ var count = 0;
+ var notDone = 0;
+ for (var id in model.todos) {
+ count++;
+ if ( ! model.todos[id].isDone ) {
+ notDone ++;
+ }
+ }
+ document.getElementById('remaining').innerText = notDone;
+ document.getElementById('length').innerText = count;
+ }
+
+ storageLoad();
+ setDragHandlers(model);
+
+});
+
diff --git a/lab5_data/javascript/2_drop_files/icon.png b/lab5_data/javascript/2_drop_files/icon.png
new file mode 100644
index 0000000..ffd3426
Binary files /dev/null and b/lab5_data/javascript/2_drop_files/icon.png differ
diff --git a/lab5_data/javascript/2_drop_files/index.html b/lab5_data/javascript/2_drop_files/index.html
new file mode 100644
index 0000000..a09944d
--- /dev/null
+++ b/lab5_data/javascript/2_drop_files/index.html
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+ Todo
+
+
of remaining
+ [
archive ]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lab5_data/javascript/2_drop_files/main.js b/lab5_data/javascript/2_drop_files/main.js
new file mode 100644
index 0000000..48f4770
--- /dev/null
+++ b/lab5_data/javascript/2_drop_files/main.js
@@ -0,0 +1,5 @@
+chrome.app.runtime.onLaunched.addListener(function() {
+ chrome.app.window.create('index.html',
+ {width: 500, height: 309});
+});
+
diff --git a/lab5_data/javascript/2_drop_files/manifest.json b/lab5_data/javascript/2_drop_files/manifest.json
new file mode 100644
index 0000000..fdaabf2
--- /dev/null
+++ b/lab5_data/javascript/2_drop_files/manifest.json
@@ -0,0 +1,13 @@
+{
+ "manifest_version": 2,
+ "name": "Lab5b Drop files",
+ "version": "1",
+ "app": {
+ "background": {
+ "scripts": ["main.js"]
+ }
+ },
+ "permissions": ["storage"],
+ "icons": { "128": "icon.png" }
+}
+
diff --git a/lab5_data/javascript/2_drop_files/todo.css b/lab5_data/javascript/2_drop_files/todo.css
new file mode 100644
index 0000000..5254e90
--- /dev/null
+++ b/lab5_data/javascript/2_drop_files/todo.css
@@ -0,0 +1,43 @@
+html {
+ height: 100%;
+ overflow: hidden;
+}
+
+body {
+ font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
+ height: 100%;
+}
+
+@-webkit-keyframes switch-green {
+ from { background-color: white;} to {background-color: rgb(163, 255, 163);}
+}
+@-webkit-keyframes switch-red {
+ from { background-color: white;} to {background-color: rgb(255, 203, 203);}
+}
+.dragging {
+ -webkit-animation: switch-green 0.5s ease-in-out 0 infinite alternate;
+}
+
+.invalid-dragging {
+ -webkit-animation: switch-red 0.5s ease-in-out 0 infinite alternate;
+}
+
+ul {
+ list-style: none;
+ max-height: 120px;
+ overflow-y: auto;
+}
+
+button, input[type=submit] {
+ background-color: #0074CC;
+ background-image: linear-gradient(top, #08C, #05C);
+ border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ color: white;
+}
+
+.done-true {
+ text-decoration: line-through;
+ color: grey;
+}
+