Refactor and extend : improved order tasks by most recently added story
story: show tasks in order of most recently added
part of the refactor and extend stories
as a user I wish to show tasks in most recently added order
learnings featured:
In the first approach we simply ordered the array of tasks before saving. This is pretty inefficient for larger data, but very simple to implement. It also suffers from relying on the display index to be equal to the array index so we cant use pure client side sorting and filtering on the list such as using orderBy as a directive.
In this post, a more flexible approach is used be to make the display order independent of the order of the saved data. This requires storing and retrieving tasks using something like a primary key.
Steps
- create a sequence for the primary key
- set the id on save of a new tasks
- find the index using the id creating
findTaskIndex
method - use
findTaskIndex
instead of using the$index
value in editing, deleting tasks and marking a task done - remove the previously implemented
orderProjectTasks
and its calls–this is no longer needed - use the
orderBy
directive in ‘todo-list.html’ template to use client side ordering - refactor calls to
editTask
,updateTask
,doneClicked
no longer requiring pasing in the index
create a sequence for the primary key
create getNextKeyValue
method in Projects service in ‘services.js’
this method will persist a key sequence that will be used when creating tasks that will be unique for the local storage to be able to find tasks again by this id
getNextKeyValue: function() {
var lastKeyValue = parseInt(window.localStorage['lastKeyValue']) || 0;
window.localStorage['lastKeyValue'] = ++lastKeyValue;
return lastKeyValue;
}
set the unique id on save of a new tasks
Modify createTask
of ‘controllers.js’ to set a ‘id’ field of the ceated task using Projects.getNextKeyValue
// Called when the form is submitted
$scope.createTask = function(task) {
if (!$scope.activeProject || !task) {
return;
}
$scope.activeProject.tasks.push({
id : Projects.getNextKeyValue(),
title: task.title,
isDone: 'NO',
createDate: (new Date()).toISOString()
});
$scope.taskModal.hide();
// Inefficient, but save all the projects
$scope.orderProjectTasks($scope.activeProject);
Projects.save($scope.projects);
task.title = "";
};
Modify editTask
to pass in and preserve id when editing
// Open our new task modal
$scope.editTask = function(i, task) {
$scope.task = {title: task.title, isDone: task.isDone, createDate: task.createDate, id : task.id};
$scope.taskIndex = i;
$scope.editTaskModal.show();
};
You can test to make sure that the id is both added and preserved. The saved json created from adding two tasks to one project should look something like this:
[
{
"title":"new project",
"tasks":[
{
"id":1,
"title":"task one",
"isDone":"NO",
"createDate":"2015-01-03T16:01:06.082Z"
},
{
"id":2,
"title":"task two",
"isDone":"NO",
"createDate":"2015-01-03T16:01:10.913Z"
}
]
}
]
find the index using the id creating findTaskIndex
method
add findTaskIndex
to ‘controllers.js’
this method loops through the given array of tasks and returns the index of the task with the given id, or -1 if no task with that id is found
// return index of the task with given id
$scope.findTaskIndex = function(tasks, id) {
for(var i=0;i<tasks.length;i++) {
if (tasks[i].id == id) return i;
}
return -1;
}
use findTaskIndex
instead of using the $index
value in editing and deleting tasks
modify updateTask
of ‘controllers.js’ to use findTaskIndex
instead of supplied index
// Called when the form is submitted
$scope.updateTask = function(i, task) {
if (!$scope.activeProject || !task) {
return;
}
var taskIndex = $scope.findTaskIndex($scope.activeProject.tasks, task.id);
$scope.activeProject.tasks[taskIndex] = task;
$scope.editTaskModal.hide();
// Inefficient, but save all the projects
$scope.orderProjectTasks($scope.activeProject);
Projects.save($scope.projects);
};
modify deleteTask
of ‘controllers.js’ to use findTaskIndex
instead of supplied index
// delete selected task
$scope.deleteTask = function(i, task) {
if (!$scope.activeProject || !task ) {
return;
}
$scope.showConfirm('Delete Task', 'Are you sure you want to delete this task?', function() {
var taskIndex = $scope.findTaskIndex($scope.activeProject.tasks, task.id);
$scope.activeProject.tasks.splice(taskIndex,1);
Projects.save($scope.projects);
});
}
you can now test to make sure that the edit, toggle done and delete actions still work. but sine we are not sorting any differently then little will be different.
remove the previously implemented orderProjectTasks
and its calls–this is no longer needed
Now we can remove all calls and implementation of previously added orderProjectTasks
that sorted the tasks on save. An dwe will replace this with using the orderBy
directive in the next step
Remove orderProjectTasks
call from createTask
and updateTask
in ‘controllers.js’
// Called when the form is submitted
$scope.createTask = function(task) {
if (!$scope.activeProject || !task) {
return;
}
$scope.activeProject.tasks.push({
id : Projects.getNextKeyValue(),
title: task.title,
isDone: 'NO',
createDate: (new Date()).toISOString()
});
$scope.taskModal.hide();
// Inefficient, but save all the projects
Projects.save($scope.projects);
task.title = "";
};
// Called when the form is submitted
$scope.updateTask = function(i, task) {
if (!$scope.activeProject || !task) {
return;
}
var taskIndex = $scope.findTaskIndex($scope.activeProject.tasks, task.id);
$scope.activeProject.tasks[taskIndex] = task;
$scope.editTaskModal.hide();
// Inefficient, but save all the projects
Projects.save($scope.projects);
};
you can test again and see that things still work but we are not ordering tasks to the most recently added at top anymore
use the orderBy
function in ‘todo-list.html’ template to use client side ordering
modify ‘todo-list.html’ template to use the orderBy
function in the ng-repeat
directive
<ion-item class="item item-icon-right" ng-repeat="task in activeProject.tasks | orderBy : predicate: reverse">
now we should see the order again
refactor calls to editTask
, updateTask
, doneClicked
no longer requiring pasing in the index
a final clean up is to stop passing the $index
value into functions editTask
, updateTask
, toggleDone
and remove this as a parameter.
modify ‘todo-list.html’ template to remove passing $index
to calls to editTask
and doneClicked
...
<ion-list>
<ion-item class="item item-icon-right" ng-repeat="task in activeProject.tasks | orderBy : predicate: reverse">
<ion-checkbox ng-class="{complete: task.isDone == 'YES'}" class="item-text-wrap" style="border:none;" ng-model="task.isDone" ng-model="task.isDone" ng-click="doneClicked(task)" ng-true-value="'YES'" ng-false-value="'NO' "><span>{{task.title}}</span></ion-checkbox>
<i class="icon ion-edit" ng-click="editTask(task)" ></i>
</ion-item>
</ion-list>
...
modify ‘edit-task.html’ template to stop passing in the taskIndex
in the case of calls to updateTask
or deleteTask
<div class="modal">
<!-- Modal header bar -->
<ion-header-bar class="bar-secondary">
<h1 class="title">Edit Task</h1>
<button class="button button-clear button-positive" ng-click="closeEditTask()">Cancel</button>
</ion-header-bar>
<!-- Modal content area -->
<ion-content>
<form ng-submit="updateTask(task)">
<div class="list">
<label class="item item-input">
<input type="text" placeholder="What do you need to do?" ng-model="task.title">
</label>
<ion-checkbox ng-model="task.isDone" ng-model="task.isDone" ng-true-value="'YES'" ng-false-value="'NO'">task is done</ion-checkbox>
</div>
<div class="padding">
<button type="submit" class="button button-block button-positive">Update Task</button>
</div>
<div class="padding">
<button t class="button button-block button-assertive" ng-click="deleteTask(task)">Delete Task</button>
</div>
</form>
</ion-content>
</div>
modify editTask
of ‘controllers.js’ to no longer take the i
parameter and not preserving it as taskIndex
scope variable
// Open our new task modal
$scope.editTask = function(task) {
$scope.task = {title: task.title, isDone: task.isDone, createDate: task.createDate, id : task.id};
$scope.editTaskModal.show();
};
modify doneClicked
of ‘controllers.js’ to remove the i
parameter
// Make sure to persist the change after is done is toggled
$scope.doneClicked = function(task) {
//alert("toggle done task "+task.isDone)
if (!$scope.activeProject || !task) {
return;
}
Projects.save($scope.projects);
}
modify updateTask
of ‘controllers.js’ to remove the i
parameter
// Called when the form is submitted
$scope.updateTask = function(task) {
if (!$scope.activeProject || !task) {
return;
}
var taskIndex = $scope.findTaskIndex($scope.activeProject.tasks, task.id);
$scope.activeProject.tasks[taskIndex] = task;
$scope.editTaskModal.hide();
// Inefficient, but save all the projects
Projects.save($scope.projects);
};
modify deleteTask
of ‘controllers.js’ to remove the i
parameter
// delete selected task
$scope.deleteTask = function(task) {
if (!$scope.activeProject || !task ) {
return;
}
$scope.showConfirm('Delete Task', 'Are you sure you want to delete this task?', function() {
var taskIndex = $scope.findTaskIndex($scope.activeProject.tasks, task.id);
$scope.activeProject.tasks.splice(taskIndex,1);
Projects.save($scope.projects);
});
}
the code for this can be found on my github page