added pure javascript version of codelab (in progress)
This commit is contained in:
Родитель
c12d952014
Коммит
3bd0250ac0
|
@ -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);
|
||||
|
||||
})
|
||||
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 6.4 KiB |
|
@ -0,0 +1,18 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="todo.css">
|
||||
</head>
|
||||
<body>
|
||||
<h2>Todo</h2>
|
||||
<div>
|
||||
<ul>
|
||||
<li id="todoText">
|
||||
</li>
|
||||
</ul>
|
||||
<input type="text" id="newTodo" size="30"
|
||||
placeholder="type your todo here">
|
||||
</div>
|
||||
<script src="controller.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,5 @@
|
|||
chrome.app.runtime.onLaunched.addListener(function() {
|
||||
chrome.app.window.create('index.html',
|
||||
{width: 500, height: 309});
|
||||
});
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Lab3a Simple MVC",
|
||||
"version": "1",
|
||||
"app": {
|
||||
"background": {
|
||||
"scripts": ["main.js"]
|
||||
}
|
||||
},
|
||||
"icons": { "128": "icon.png" }
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
});
|
||||
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 6.4 KiB |
|
@ -0,0 +1,30 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="todo.css">
|
||||
</head>
|
||||
<body>
|
||||
<h2>Todo</h2>
|
||||
<div>
|
||||
<span><span id="remaining"></span> of <span id="length"></span> remaining</span>
|
||||
[ <a href="" id="archive">archive</a> ]
|
||||
<ul class="unstyled" id="list">
|
||||
</ul>
|
||||
<form>
|
||||
<input type="text" size="30"
|
||||
placeholder="add new todo here">
|
||||
<input class="btn-primary" type="submit" value="add">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- poor man's template -->
|
||||
<div id="templates" style="display: none;">
|
||||
<li data-name="list">
|
||||
<input type="checkbox">
|
||||
<span></span>
|
||||
</li>
|
||||
</div>
|
||||
|
||||
<script src="controller.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,5 @@
|
|||
chrome.app.runtime.onLaunched.addListener(function() {
|
||||
chrome.app.window.create('index.html',
|
||||
{width: 500, height: 309});
|
||||
});
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Lab3b MVC with controller",
|
||||
"version": "1",
|
||||
"app": {
|
||||
"background": {
|
||||
"scripts": ["main.js"]
|
||||
}
|
||||
},
|
||||
"icons": { "128": "icon.png" }
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
});
|
||||
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 6.1 KiB |
|
@ -0,0 +1,30 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="todo.css">
|
||||
</head>
|
||||
<body>
|
||||
<h2>Todo</h2>
|
||||
<div>
|
||||
<span><span id="remaining"></span> of <span id="length"></span> remaining</span>
|
||||
[ <a href="" id="archive">archive</a> ]
|
||||
<ul class="unstyled" id="list">
|
||||
</ul>
|
||||
<form>
|
||||
<input type="text" size="30"
|
||||
placeholder="add new todo here">
|
||||
<input class="btn-primary" type="submit" value="add">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- poor man's template -->
|
||||
<div id="templates" style="display: none;">
|
||||
<li data-name="list">
|
||||
<input type="checkbox">
|
||||
<span></span>
|
||||
</li>
|
||||
</div>
|
||||
|
||||
<script src="controller.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,5 @@
|
|||
chrome.app.runtime.onLaunched.addListener(function() {
|
||||
chrome.app.window.create('index.html',
|
||||
{width: 500, height: 309});
|
||||
});
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Lab5a Storage sync",
|
||||
"version": "1",
|
||||
"app": {
|
||||
"background": {
|
||||
"scripts": ["main.js"]
|
||||
}
|
||||
},
|
||||
"permissions": ["storage"],
|
||||
"icons": { "128": "icon.png" }
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
});
|
||||
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 6.1 KiB |
|
@ -0,0 +1,32 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="todo.css">
|
||||
</head>
|
||||
<body>
|
||||
<h2>Todo</h2>
|
||||
<div>
|
||||
<span><span id="remaining"></span> of <span id="length"></span> remaining</span>
|
||||
[ <a href="" id="archive">archive</a> ]
|
||||
<ul class="unstyled" id="list">
|
||||
</ul>
|
||||
<form>
|
||||
<input type="text" size="30"
|
||||
placeholder="add new todo here">
|
||||
<input class="btn-primary" type="submit" value="add">
|
||||
</form>
|
||||
<div id="dropText">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- poor man's template -->
|
||||
<div id="templates" style="display: none;">
|
||||
<li data-name="list">
|
||||
<input type="checkbox">
|
||||
<span></span>
|
||||
</li>
|
||||
</div>
|
||||
|
||||
<script src="controller.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,5 @@
|
|||
chrome.app.runtime.onLaunched.addListener(function() {
|
||||
chrome.app.window.create('index.html',
|
||||
{width: 500, height: 309});
|
||||
});
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Lab5b Drop files",
|
||||
"version": "1",
|
||||
"app": {
|
||||
"background": {
|
||||
"scripts": ["main.js"]
|
||||
}
|
||||
},
|
||||
"permissions": ["storage"],
|
||||
"icons": { "128": "icon.png" }
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
Загрузка…
Ссылка в новой задаче