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
findTaskIndexmethod - use
findTaskIndexinstead of using the$indexvalue in editing, deleting tasks and marking a task done - remove the previously implemented
orderProjectTasksand its calls–this is no longer needed - use the
orderBydirective in ‘todo-list.html’ template to use client side ordering - refactor calls to
editTask,updateTask,doneClickedno 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