⏲️ Est. time to complete: 25 min. ⏲️
As a user, I want to be able to mark a task as complete from the main list as well see the due date of a task if one is defined
-
A checkbox should be added to the task list to allow users to mark a task as complete right from the main panel.
-
The task should be visually differentiated when marked as complete. i.e., by using strikethrough text on the task name.
-
The due date of the task should be displayed in a friendly format underneath the name of the task.
- If a due date is past due, the due date should be displayed inside a red badge .
- If a due date is in the future, the due date should be displayed in light blue badge.
- If there is no due date set for the task, then nothing should display for the due date.
-
The interface should look something like this:
no resources at this time
In order to complete this user story you will need to complete the following tasks:
Open Visual Studio Code (either locally in the project directory that you setup or through your Codespace). Visual Studio Code should have your completed solution from the end of Sprint 1 or if you prefer you can use the starting reference application from here by copying it over into your local directory or Codespace.
In order to support the ability to mark a task as complete when the user checks the checkbox, we will need to add a new route to the backend that will update the completed status of a task. Open the app.py
file and add the following code to the bottom of the file before the if __name__ == '__main__':
line:
@app.route('/completed/<int:id>/<complete>', methods=['GET'])
def completed(id, complete):
g.selectedTab = Tab.NONE
g.todo = Todo.query.filter_by(id=id).first()
if (g.todo != None and complete == "true"):
g.todo.completed = True
elif (g.todo != None and complete == "false"):
g.todo.completed = False
#update todo in the database
db.session.add(g.todo)
db.session.commit()
#
return redirect(url_for('index'))
This code will create a new route called /completed
that will take in the id and complete status of the task. It will then get the task from the database, update the completed status of the task, and then redirect the user back to the main task list.
Per the acceptance criteria above there is a requirement to display the due date of the task in a friendly format underneath the name of the task as well as understand if the due date is past due or in the future. To support this requirement, we will need to update the app to pass the current date to the user interface. We could continue to do this through the flask global module g
in a similar way that we pass the other variables, but for the sake of this exercise we wanted to show you another way to pass variables to the user interface. We will do this through context processors. To enable this we first need to create a new file called context_processors.py
in the app
folder. Add the following code to the file:
from datetime import datetime
def inject_current_date():
return {'current_date': datetime.now().strftime('%Y-%m-%d')}
This code will create a function called inject_current_date
that will return the current date in the format YYYY-MM-DD
.
We will then need to add this functionality to our flask application by making two changes to the app.py
file. First, we need to import the context processor into the application. Add the following code to the top of the file:
from context_processors import inject_current_date
Next we will need to register the context processor with the application. Add the following code to the app.py
file after the db.init_app(app) with app.app_context(): db.create_all()
lines:
@app.context_processor
def inject_common_variables():
return inject_current_date()
This code will register the inject_current_date
function as a context processor with the application. This will allow the current_date
variable to be passed to the user interface on every request, which will be used in a later step to decide how we display the due date of the task (i.e., past due or in the future).
There are several changes that we need to make to the user interface to support the ability to mark a task as complete and display the due date of the task.
First, we need to add a checkbox to the task list and the due date details. Open the index.html
file and update the code between the {% for todo in g.todos %}...{% endfor %}
tags as seen below:
Replace the code below:
{% for todo in g.todos %}
<li id="task-{{ todo.id }}" data-id="{{ todo.id }}" class="list-group-item d-flex justify-content-between">
<div class="task">
<div class="title" id="title-{{ todo.id }}">{{ todo.name }}</div>
</div>
<span>
<button type="submit" class="btn btn-success" formaction="{{ url_for('details', id=todo.id) }}" formmethod="GET">Details</button>
<button type="submit" class="btn btn-success" formaction="{{ url_for('edit', id=todo.id) }}" formmethod="GET">Edit</button>
<button type="submit" class="btn btn-success" formaction="{{ url_for('recommend', id=todo.id) }}" formmethod="GET">Recommendations</button>
<button type="submit" class="btn btn-danger" formaction="{{ url_for('remove_todo', id=todo.id) }}" formmethod="POST">Remove</button>
</span>
</li>
{% endfor %}
with the following code:
{% for todo in g.todos %}
<li id="task-{{ todo.id }}" data-id="{{ todo.id }}" class="list-group-item d-flex justify-content-between">
<div class="task">
<div class="form-check">
{% if todo.completed %}
<input class="form-check-input" type="checkbox" id="{{ todo.id }}" checked onclick="handleClick(event, this)">
{% else %}
<input class="form-check-input" type="checkbox" id="{{ todo.id }}" onclick="handleClick(event, this)">
{% endif %}
<div class="title" id="title-{{ todo.id }}">{{ todo.name }}</div>
<div class="subtitle" id="duedate-{{ todo.id }}">
{% if todo.completed %}
<small class="badge bg-success">Completed</small>
{% elif todo.due_date %}
{% if todo.due_date < current_date %}
<small class="badge bg-danger">Past Due: {{ todo.due_date }}</small>
{% else %}
<small class="badge bg-info">Due Date: {{ todo.due_date }}</small>
{% endif %}
{% endif %}
</div>
</div>
</div>
<span>
<button type="submit" class="btn btn-success" formaction="{{ url_for('details', id=todo.id) }}" formmethod="GET">Details</button>
<button type="submit" class="btn btn-success" formaction="{{ url_for('edit', id=todo.id) }}" formmethod="GET">Edit</button>
<button type="submit" class="btn btn-success" formaction="{{ url_for('recommend', id=todo.id) }}" formmethod="GET">Recommendations</button>
<button type="submit" class="btn btn-danger" formaction="{{ url_for('remove_todo', id=todo.id) }}" formmethod="POST">Remove</button>
</span>
</li>
{% endfor %}
This code will adds the following functionality:
- adds a checkbox to each task in the list. Upon loading of the web page, if the task is marked as completed, the checkbox will be checked. If the task is not completed, the checkbox will be unchecked. This is handled through the
checked
attribute in the<input>
tag. There is also anonclick
event that will call thehandleClick
function when the checkbox is clicked. This function will be created in the next step. - due date - If there is a due date assigned, the due date will be displayed in a friendly format underneath the name of the task. If the task is marked as completed, a "Completed" badge will be displayed. If the task is past due, a "Past Due" badge will be displayed in red text. If the task is not past due, a "Due Date" badge will be displayed in blue text.
Next, we need to add the handleClick
function to the static/js/app.js
file. Open the app.js
file and add the following code within the DOMContentLoaded event listener:
window.handleClick = function(event, cb) {
event.stopPropagation();
const rootUrl = window.location.origin;
const cbId = cb.id;
const cbChecked = cb.checked;
window.location.href = `${rootUrl}/completed/${cbId}/${cbChecked}`;
};
This code will create a function called handleClick
that will be called when the checkbox is clicked. This function will stop the event from propagating, get the id and checked status of the checkbox, and then redirect the user to the /completed
route with the id and checked status as parameters to update the state of the to-do item's status.
Finally, we will need to update the style.css
file to add a strikethrough effect to the task name when it is marked as completed. Add the following code to the bottom of the file:
.form-check input[type="checkbox"]:checked + .title {
text-decoration: line-through;
}
.title {
display: inline-flex;
font-weight: bold;
color: #333;
}
.subtitle {
font-size: 14px;
color: #666;
padding-left: 20px;
}
This code will add a strikethrough effect to the task name when the task is marked as completed. It will also update the styling of the task name and due date to make it more visually appealing.
Now let's run the application to see the checkbox in action. Open the terminal and navigate to the folder where your app.py
file is located. Run the application by typing python app.py
and pressing the enter key or simply click the play button in the top right corner of the Visual Studio Code window. For Codespaces, the easiest path is to just click the play button. This will launch a browser and show the home page (or you can browse to http://localhost:5000).
You should now see a checkbox next to each task in the task list. When you click on the checkbox, the task should be marked as completed and visually differentiated with a strikethrough effect. The web page should look something like this:
Note
To stop the server simply hit CTRL-C
in the terminal window where the web server is running.
🎉 Congratulations! You have now updated the user interface to include a checkbox to mark tasks as completed and visually differentiate completed tasks.
Note
📄For the full source code for this exercise please see here.
🔼 Home | ◀ Previous user story (in previous sprint) | Next user story ▶