What Todo
Last week I made a quick To Do List kind-of app, based on AngularJS. I did that so that I could pratice some unit testing with jasmine, filtering, and some css styling. I also had to have some persistence system for the data, so I tried using the browser’s localStorage for that. The main HTML structure I imagined (besides containers for styling purposes) was:
- an
<input>
of text type for the insertion - an
<ul>
to list each ‘to do’, represented as an<li>
element - each element would have an
<input>
of type checkbox to check if the ‘to do’ was completed - a footer with a ‘cleaner’ button, some filters and a counter of todos left
So, the main steps were:
‘To Do’ model and Collection
Each to do was defined as an object, with the the following structure :
newTodo= { id: // number: todo-key, text : // string: todo-description, done : // boolean: either completed or not };
And the ‘to do’ collection was represented as an array, assigned to the todos variable in the controller’s scope.
‘To Do’ Insertion
As expected, an insertTodo function was defined in the controller’s scope so that the user could insert new items, being abstracted from its’ ID or state:
$scope.insertTodo = function (todotext) {
var newTodo;
if (todotext.length) {
newTodo= {
id: $scope.todos.length ? $scope.todos[$scope.todos.length - 1].id + 1 : 1,
text : todotext,
done : false
};
// insert into collection
$scope.todos.push(newTodo);
// reset input text
$scope.newTodoText = '';
$scope.$apply();
saveData();
}
};
State update and visual feedback
Like any AngularJS app, updating the model state is as easy as binding each item’s done atribute with the <input>
element, with the ng-model
directive. That would sync the todo’s state, but for this use case the ‘tick’ of the checkbox isn’t enough to indicate that the item is completed, a line-through text-decoration would be beter. To do that, I assigned that style to a CSS class done
, and for that class to be conditionally added to each item I had do use the nice ng-class directive:
ng-class="{done: utils.isTodoDone(item)}
This directive adds the class based on the value returned by the isTodoDone
function, and was used in more places, like on selecting the filter type.
Checkbox Customization
For the layout I predicted, the browser’s standard checkbox was too small and not adequate to the design, so I did some customizing. The technique I used is quite well known, basically given that a <label>
element for the input is present (and properly connected):
- a container is needed so that a
position: relative
can be used to position the new checkbox - the
<input>
element will havedisplay: none
- the
<label>
element will behave as the new checkbox - a pseudo-element
:after
is added to the<label>
, and it will represent the ‘tick’
A nice note on this is that the ‘tick’ is simply a rectangle, rotated 45 degrees and with no right and top borders :) . The result is the following pen:
See the Pen Custom checkbox example by António Capelo (@capelo) on CodePen.
‘To Do’ Cleaning
With the dual binding AngularJS gives us, this task is as easy as setting up a cleaner function and assign it to a button with the ng-click directive:
$scope.cleanSelected = function() {
for (var i = 0; i < $scope.todos.length; i++) {
var todo = $scope.todos[i];
if (todo.done === true) {
$scope.todos.splice(i, 1);
i--;
}
}
saveData();
};
‘To Do’ Filtering
To filter the items based on their state, I decided to make clicking on each filter to change a scope variable that defines the filter type, like ng-click="filterType=\'all\'
. In order to only represent the correct items on the ng-repeat
directive, I had to filter the results based on a function scope I defined:
And
$scope.filterElements = function(el) {
if ($scope.filterType === 'all' ) {
return true;
} else {
return ((el.done === true && $scope.filterType === 'done') || (el.done === false && $scope.filterType === 'active'));
}
};
Using Local Storage
Using the browser’s local storage is pretty easy. I created 2 functions, saveData (which executes on each update) and recoverData (which runs on start up). These functions basically use the getItem and setItem of the localStorage API, parsing from/to JSON/String.
recoverData = function() {
if (localStorage) {
$scope.todos = JSON.parse(localStorage.getItem('jstodos')) || [];
}
};
saveData = function() {
if (localStorage) {
localStorage.setItem('jstodos',JSON.stringify($scope.todos));
}
};
I put this simple app on the folowing pen, so you can see it at work:
See the Pen ToDo by António Capelo (@capelo) on CodePen.
This time I used the cool [Yeoman}(http://yeoman.io/), which helped on bootstraping the app, provides a nice directory structure and eases the setup and running of the Jasmine test on Karma. I’ll leave some thoughts on that for a future post.
See you soon,
A. Capelo
Tweet