Giterdone app view

Git'er Done!


Project Date: August 2017

DESCRIPTION

A web application that uses Firebase API and AngularJS to create tasks and automatically expire them. The project write up below does not detail every aspect of implementation. Rather, it summarizes or skips common practice code, but highlights the relatively more individualistic components. It also assumes basic familiarity with the technology on the part of the reader. Environment setup is outlined in README in the GitHub repository, and is not covered here.
UPCOMING FEATURE
Authenticated user accounts will be added in the near future.
CODER COMMENT
Working with AngularFire was great experience, but the design of the task display and making choices about ease of viewing and editing were among the most stimulating parts of this project. Also, I liked using theme throughout color scheme, images, and font to achieve a cohesive aesthetic.

LINKS

Live App  please read testing below before trying app
GitHub Repo

TESTING

CONFIGURATION
  • Tasks expire after 90 seconds so testers can see the task expiration functionality within a reasonable testing time frame.
  • The Firebase database does not require users to be logged in to the application, and is set to allow anyone to read/write.
FEATURES
  • Create tasks with title and priority
  • Edit tasks (including multiple at once)
  • Task expiration after 90 seconds - it is removed from home view to history view
  • Delete task - it is removed from home view to history view
  • Purge tasks from history individually
  • Purge entire task history

VIEWS

The two views in the application consist of a default view that displays active tasks and a separate view that presents expired and completed tasks. index.html acts as a global file and contains a uiview viewport. The templates are home.html and history.html. The UI-Router module is used with $locationProvider and $stateProvider to disable Hashbang mode for application paths and configure states for home and history. The state changes are triggered using ui-sref on each of the views.

DATABASE

Firebase was used to save and sync data on the backend. Structurally, the database has a ‘tasks’ key, and task objects have four properties – date, status, title, and pThread. Task-related API queries are handled in a Task factory in Angular. Within the Task factory, the orderByChild() method is called three times on a Reference for the ‘tasks’ location. These Query objects are ordered by the ‘status’ key, and equalTo() is used to match all three possible values for status – ‘incomplete’, ‘complete’, and ‘expired’. The $firebaseArray service is used to return this data as an array. How this data is used will be explained in further detail in the Angular section.
app/scripts/services/Task.js
var ref = firebase.database().ref().child("tasks");

Task.tasksIncomplete = $firebaseArray(ref.orderByChild("status").equalTo("incomplete"));
Task.tasksComplete = $firebaseArray(ref.orderByChild("status").equalTo("complete"));
Task.tasksExpired = $firebaseArray(ref.orderByChild("status").equalTo("expired"));
 

ANGULAR

Basic Architecture
To briefly summarize the Angular application architecture, the root Angular module is named ‘giterdone’ and is bootstrapped in the <html> element of index.html. Two controllers, HomeCtrl and HistoryCtrl are used, and each state has one designated for it in $stateProvider. Finally, a Task factory handles Firebase queries and is injected into both controllers.
Directives and Forms
Tasks are displayed in the home and history views using the ng-repeat directive on the aforementioned three sets of ‘status’ ordered array data. The history view displays ‘expired’ and ‘completed’ tasks and home shows ‘incomplete’ objects. In home, <li> is the repeated HTML element. <li> contains an ng-class directive to add background color to the list item based on its priority. The collection is also ordered by priority.
app/templates/home.html
<li class="list-group-item list-style-do" ng-class="{'h-fade': task.pThread.split(',')[1] == '1', 'm-fade': task.pThread.split(',')[1] == '2', 'l-fade': task.pThread.split(',')[1] == '3'}">
 
In the home template, this list element contains two sets of items. Each set of items is shown or hidden using ng-show and ng-hide. The items that are shown by default are the task title, a button to update tasks as completed, and an edit button to update the task. The edit button has an ng-click directive that shows the textbox and radio buttons to update the task title and priority, and the submit button which calls the updateTask method. ng-click in the edit button also hides the default display items, including itself. The button to submit the task update form elements, also has an ng-click that correspondingly, hides the form elements, and shows the default display items.
app/templates/home.html
<button ng-show="newP" class="submit-with-icon" style="float: right !important;" ng-click="home.updateTask(task, newTitle, newPriority); newP=false; editP=false; oldT=false; newT=false;"><span class="glyphicon glyphicon-ok" style="vertical-align: middle;"></span></button>
<button ng-hide="editP" type="button" class="submit-with-icon" style="float: right !important;"><span class="glyphicon glyphicon-pencil"  ng-click="newP=true; subP=true; editP=true; oldT=true; newT=true"></span></button>
 

TASK MANAGEMENT WITH FIREBASE

Completion and Expiration
The status of task objects is updated via two methods: first, the user marking the task as complete; second, a setInterval method in Angular’s Task factory which expires tasks based on their date property. In the home template, ‘incomplete’ tasks are displayed with a button that has an ng-click directive. Clicking it executes the completeTask function in HomeCtrl and passes the respective task object as an argument. Within this method, the status property of the object is updated to ‘complete’.
In the Task factory, JavaScript's setInterval() method is used to update the object’s status to ‘expired’ based on its date property. Within the method, orderByChild() generates a query object ordered by the ‘status’ key, and equalTo() limits the query to children with a status of 'incomplete'. on() is called on the query object and the tasks are looped through with forEach(). A conditional statement then checks if the date of the snapshot is 90 seconds behind the current time. Then update() is used on the Reference for that location to change status to ‘expired’.
app/scripts/services/Task.js
setInterval(function(){
    var query = ref.orderByChild("status").equalTo('incomplete');
    query.on("value", function(snapshot) {
        snapshot.forEach(function(snapshot) {
            if (snapshot.val().date < (Math.round(Date.now() / 1000) - 89)) {
                snapshot.ref.update({status: 'expired'})
             }
        });
    });
}, 1000)
 
Taking a step back, ‘date’ is assigned when a task is added. The addTask method is located in HomeCtrl. Within it, Date.now() is called and the result is divided by 1000 to get a value in seconds vs milliseconds. Then Math.round() is used to get the result in whole seconds.
app/scripts/controllers/HomeCtrl.js
...
this.newTask.date = Math.round(Date.now() / 1000);
...
 
In the history view, users can delete tasks individually or clear the entire history. off() is used to detach the callbacks on the references.
app/scripts/controllers/HistoryCtrl.js
this.deleteAll = function() {
    ref.orderByChild("status").equalTo("complete").on("child_added", removeSnap);
    ref.orderByChild("status").equalTo("expired").on("child_added", removeSnap);
    ref.off("child_added", removeSnap);
}