# Todo app
In this application you design a todo app for your smartphone.
All tasks are bundled in an array and saved locally. For this we use HTML5 local storage (is a part of Web Storage).
- Create a new project:
$ cordova create todo be.yourName.todo Todo
$ cd todo
$ cordova platform add browser android
2
3
4
# What is local storage?
- Local storage is part of Web storage.
- Local storage stores a key/value pair on the client.
- The value is always a string.
- In the value you can store any type of data (a string, a number, an array, a JSON object, ...), as long as it can be converted to a string.
See: https://itf-web-advanced.netlify.app/es6/localstorage.html
The main functions are:
- Store a value:
localStorage.setItem('key', 'value')
- Retrieve a value:
localStorage.getItem('key')
- Delete a value:
localStorage.removeItem('key')
- Delete local storage entirely:
localStorage.clear()
REMARK In this application, we use an array containing JSON objects (JSON array). To save and retrieve this
data, we will always need to transform the data.
- Store in local storage:
convert a JSON array to a string withlocalStorage.setItem('key', JSON.stringify(object))
. - Retrieve from local storage:
convert a string to a JSON array withobject = JSON.parse(localStorage.getItem('key'))
# Content of the website
- Delete the entire contents of the www folder and replace it with the files from this zip file.
- Examine the files.
# index.html
<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css" />
<title>Todo</title>
<style>
.deleteTask {
cursor: pointer;
}
</style>
</head>
<body>
<!-- Fixed navbar: https://materializecss.com/navbar.html -->
<div class="navbar-fixed">
<nav class="orange">
<div class="nav-wrapper">
<a href="#!" class="brand-logo">Todo App</a>
</div>
</nav>
</div>
<!-- Grid: https://materializecss.com/grid.html -->
<div class="container">
<div class="row">
<div class="col s12">
<!-- Collection (avatar content): https://materializecss.com/collections.html -->
<ul class="collection">
<!-- Dummy task as an example -->
<li class="collection-item avatar">
<i class="material-icons circle red deleteTask" data-task="0">delete_forever</i>
<div class="title" data-task="0" contenteditable>Dummy task</div>
</li>
</ul>
</div>
</div>
</div>
<!-- Fixed action button: https://materializecss.com/floating-action-button.html -->
<div class="fixed-action-btn">
<a class="btn-floating btn-large green" id="addTask">
<i class="large material-icons">note_add</i>
</a>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<script src="cordova.js"></script>
<script src="js/todo.js"></script>
<script defer src="js/app.js"></script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
- The top of the page contains a fixed navbar.
- Followed by a collection (
ul
tag) with one dummy task (li
tag).- On the delete icon belonging to a task, an additional class
.deleteTask
has been added.
Through this class we can later delete the task. - Both the icon and the text are given a data attribute
data-task="0"
.
We will use this number later to access the correct element within the array.
data-task="0"
refers to the array element_todos[0]
. - The
contenteditable
attribute on the div tag allows us to modify the text directly (inline).
- On the delete icon belonging to a task, an additional class
- At the end of the page comes another fixed action button.
With the id#addTask
to add a new task.
# js/app.js
This is the basic script for our application.
$(function(){
document.addEventListener("deviceready", onDeviceReady, false);
$('#addTask').click(function(){
console.log('add a new task');
});
$('ul').on('blur', '.title', function(){
console.log('update a task');
});
$('ul').on('click', '.deleteTask', function(){
console.log('delete a task');
});
});
function onDeviceReady() {
console.log('Device is ready');
ToDo.init();
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- Once the device is active (
onDeviceReady()
) the todo list is initialized. - This is followed by the different actions we can perform. The different classes and ids refer to the elements on the html page. For now, we just write some comments to the console.
REMARK
- Note that we execute the last two events via
on()
! - Most jQuery events like
click()
,blur()
,change()
, etc... work exclusively on a selector that is present ** when the page opens**! - Only the jQuery event
on()
can work with dynamic generated content. - Since we are going to add li-tags dynamically, we cannot use
$('.title').click(function(){})
and we are forced to work viaon()
. - The code then becomes
$('ul').on('click', '.title', function(){})
. - The selector is now the ul tag (it does exist when the page loads) and the click event is delegated to the
class
.title
.
# js/todo.js
const ToDo = function () {
let _todos = ['Dummy task'];
const _setLocalStorage = function() {
console.log('save all tasks as "todo" key in local storage');
console.log('_todos[]', _todos);
};
const _taskList = function() {
console.log('add all tasks to the ul tag');
};
const init = function(){
console.log('initialize the Todo app');
};
const addTask = function(){
console.log('add a new task');
};
const deleteTask = function(id){
console.log(`delete task with id = ${id}`);
};
const editTask = function(id, task){
console.log(`edit/update a task: _dotos[${id}] = ${task}`);
};
return {
init: init,
addTask: addTask,
deleteTask : deleteTask,
editTask : editTask
};
}();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
The public available methods in this module are:
init()
: initialize the array_todos[]
with all tasks stored in local storage.addTask()
: add a new task to the array_todos[]
and store it in local storage.deleteTask(id)
: delete the selected task from the array_todos[id]
and save the modified list in local storage.id
is the value ofdata-task="x"
.
editTask = function(id, task)
: modify the content of the task.id
is the value ofdata-task="x"
.task
is the text.
Open the application in Chrome http://localhost:3000.
$ phonegap serve
- Open the Chrome DevTools (F12) and click on the console tab.
- When the app starts, two messages appear in the console. The first notification is from app.js, the second from ** todo.js**.
- Click on the various icons on the page. For now, only the logs are visible in the console.
# Update a task
Modifying (updating) a task is done by clicking on the text itself. You do not need to write any additional code for
this.
The attribute contenteditable
makes the text editable.
If you click outside the text field, you activate the blur
event. This is when two values are retrieved ( the id
and
the task
). Each adjustment refers to the appropriate element in the array _todos[]
. For now, the array contains only
one item: _todos[0]
. In the console, you see the modified text.
Modify the code on js/app.js:
$('ul').on('blur', '.title', function(){ console.log('update a task'); const id = $(this).data('task'); // id = the value of x from data-task="x" const task = $(this).html(); // task = the HTML code in the text field ToDo.editTask(id, task); });
1
2
3
4
5
6Modify the code on js/todo.js:
const editTask = function(id, task){ console.log(`edit/update a task: _dotos[${id}] = ${task}`); _todos[id] = task; _setLocalStorage(); };
1
2
3
4
5
# Local storage
The three methods editTask()
, addTask()
and deleteTask()
each refer to the method _setLocalStorage()
.
# Save tasks to local storage
- The
_setLocalStorage()
method stores the array locally.
Note that we first convert the array to a JSON string usingJSON.stringify(_todos)
before storing it. Without this prior operation, you cannot process the data correctly! - The last action refers to
_taskList()
. This is where the task list on the screen will be rebuilt in a moment. - Modify the code on js/todo.js:
const _setLocalStorage = function() {
console.log('save all tasks as "todo" key in local storage');
console.log('_todos[]', _todos);
localStorage.setItem('todo', JSON.stringify(_todos)); // localStorage.setItem('key', 'value')
_taskList();
}
2
3
4
5
6
- Change the text Dummy task several times and see what happens in local storage (Application tab)
# Retrieve tasks when starting the app
- When initializing (starting up) the app, you are going to fetch the data from local storage and display the task list on the screen.
- First, empty the array
_todos[]
and then retrieve the value of todo from local storage.
Check that the string is not empty and then convert the string to an array usingJSON.parse()
. Then build the todo list back up. - Modify the code on js/todo.js:
const init = function(){
console.log('initialize the Todo app');
const todos_str = localStorage.getItem('todo');
if (todos_str !== null) {
_todos = []; // empty the array
_todos = JSON.parse(todos_str);
}
_taskList();
}
2
3
4
5
6
7
8
9
REMARK
- For now, the array only contains one element and even if you can change the content, you won't see it appear in the app yet!
- Any changes will only show up in the app after the next step.
# Rebuild tasks
- The last line of the method
_setLocalStorage()
points to the method_taskList()
. This is where the task list is built. - First we are going to delete all the
li
tags that are already present. - Then, from a
forEach
loop, we are going to rebuild allli
tags.
The structure is identical to the structure of the dummyli
tag that is now hardcoded on the page.
The text anddata-task
number are generated dynamically. - Modify the code on js/todo.js:
const _taskList = function() {
console.log('add all tasks to the ul tag');
$('ul').empty(); // remove all li tags
_todos.forEach(function (value, key) {
const item = `<li class="collection-item avatar">
<i class="material-icons circle red deleteTask" data-task="${key}">delete_forever</i>
<div class="title" data-task="${key}" contenteditable>${value}</div>
</li>`;
$('ul.collection').append(item);
});
};
2
3
4
5
6
7
8
9
10
11
12
13
TIP
- Edit the first task from the app and refresh the page.
- For now, you can add no new task from the app. This will only be possible in the next step.
- However, you can add additional tasks in the console:
- DoubleClick on the Value.
- Change the array to e.g.
["Dummy task 1", "Task 2", "Task 3"]
(note the commas and the quotes!). - Refresh the page.
# Add a task
Click on the add icon, to add a new task at the end of the array.
- Modify the code on js/app.js:
$('#addTask').click(function(){
console.log('add a new task');
ToDo.addTask();
});
2
3
4
- Modify the code on js/todo.js:
const addTask = function(){
console.log('add a new task');
_todos.push(`Task ${_todos.length + 1}`); // add the text "Task x" at the end (push) or in front (unshift) of the array
_setLocalStorage();
};
2
3
4
5
In the text, you also include the length of the array. The new task appears on the screen and in the console.
- From the console, delete the todo array (right-click the line).
- Refresh the page.
- Click the add icon several times and check in the console how the array is growing.
# Delete a task
Click on the trash icon, to remove the task from the array.
- Modify the code on js/app.js:
$('ul').on('click', '.deleteTask', function(){
console.log('delete a task');
const id = $(this).data('task'); // id = value x from data-task="x"
ToDo.deleteTask(id);
});
2
3
4
5
- Modify the code on js/todo.js:
const deleteTask = function(id){
console.log(`delete task with id = ${id}`);
if(confirm('Delete this task?')) {
_todos.splice(id, 1); // remove the x-th element from the array
_setLocalStorage();
}
};
2
3
4
5
6
7
- Click the
add
icon several times and then thetrash
icon several times.
Check in the console what happens to the array.
# Install the updated todo app
- Delete all unnecessary logs (
console.log(...)
). - Create an icon for the app and place it in the resources folder.
$ cordova-res android --type icon
1 - Install the updated todo app on your smartphone and test the result.
$ cordova run android
1