In part II we’ll look at enhancing our App’s functionality with AJAX using jQuery library. Most of the code we’ll deal with is not specific to Django. My intent is to give a few examples of using asynchronous communication with the Django server to make the interface a little more appealing.
If you are not familiar with Javascript, the following code may appear complicated and intimidating; if you don’t need AJAX functionality, feel free to skip this part completely and go on to other tutorials — nothing here will be essential to this App’s operation.
Let’s make our OnHold and Done links a little spiffier: in this iteration, they will update the task status behind the scenes without page reload. Add the following Javascript code to the head section of change_list.html template:
<script type="text/javascript" src="http://code.jquery.com/jquery-1.4.2.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
{% for obj in cl.result_list %}
// Done toggle
$('#done_{{ obj.pk }}').click(function() {
if ($(this).html().indexOf("icon-on") != -1) { $action = "off"; }
else { $action = "on"; }
$.ajax({
type: "POST",
url: "/dbe/onhold_done/done/" + $action + "/{{ obj.pk }}/",
success: function(action) {
$('#done_{{ obj.pk }}').html("<img class='btn' src='/media/img/admin/icon-"
+$action+".gif' />");
}
})
});
});
{% endfor %}
});
</script>
<style type="text/css">
.btn { cursor: pointer; }
</style>
I won’t go into what happens here — jQuery deserves a dedicated tutorial;— if you are interested you should read their excellent documentation site.
From the point of view of Django the only thing we do here is a POST request to /dbe/onhold_done/done/on/ (or off). Add the following code to your urlconf file and views.py:
(r"^onhold_done/(onhold|done)/(on|off)/(\d*)/$", "onhold_done"),
@staff_member_required
def onhold_done(request, mode, action, pk):
"""Toggle Done / Onhold on/off."""
item = Item.objects.get(pk=pk)
if action == "on":
if mode == "done": item.done = True
elif mode == "onhold": item.onhold = True
elif action == "off":
if mode == "done": item.done = False
elif mode == "onhold": item.onhold = False
item.save()
return HttpResponse('')
This function will handle both onhold and done toggles. Let’s change toggle_done() in models.py (you should also update list_display in the same file):
def done_(self):
if self.done:
btn = "<div id='done_%s'><img class='btn' src='%simg/admin/icon-on.gif' /></div>"
else:
btn = "<div id='done_%s'><img class='btn' src='%simg/admin/icon-off.gif' /></div>"
return btn % (self.pk, MEDIA_URL)
done_.allow_tags = True
done_.admin_order_field = "done"
Notice that we use admin_order_field to tell Django to sort the display column by done column in the database. The icons I’m using are not included in Django — you’ll have to make them yourself or find free ones from the Web. The interface looks a lot less cluttered now, doesn’t it?:
The code for OnHold toggle is exactly the same.
Now let’s do something a little tougher: an interactive progress bar that will update the database when you click on it.
We have to go back to change_list.html and add the following javascript code (in the same for loop we used before):
// hover
$('#progress_btns_{{ obj.pk }} li').hover(function() {
$progress = $(this).text();
$('#progress_on_{{ obj.pk }}').css('width', barWidth($progress));
});
// mouseout
$('#progress_btns_{{ obj.pk }} li').mouseout(function() {
$progress = $('#progress_{{ obj.pk }}').text();
$('#progress_on_{{ obj.pk }}').css('width', barWidth($progress));
});
//click
$('#progress_btns_{{ obj.pk }} li').click(function() {
$progress = $(this).text();
$('#progress_on_{{ obj.pk }}').css('width', barWidth($progress));
$('#progress_{{ obj.pk }}').text($progress);
$.ajax({
type: "POST",
url: "{% url dbe.todo.views.progress obj.pk %}",
data: ({progress: $progress }),
})
});
$('#progress_on_{{ obj.pk }}').css('width', barWidth({{ obj.progress }}));
$('#progress_{{ obj.pk }}').text({{ obj.progress }});
Add the barWidth() function after the for loop:
function barWidth($progress) {
$progress = parseFloat($progress);
switch ($progress){
case 10: $width = "14px"; break;
case 20: $width = "28px"; break;
case 30: $width = "42px"; break;
case 40: $width = "56px"; break;
case 50: $width = "70px"; break;
case 60: $width = "84px"; break;
case 70: $width = "98px"; break;
case 80: $width = "112px"; break;
case 90: $width = "126px"; break;
case 100: $width = "140px"; break;
default: $width = "84px";
}
return $width;
}
Finally, add the styling in CSS block in the same file:
<style type="text/css">
.progress_cont { background: #ccc; border: 1px solid #ccc; width: 140px;
height: 10px; text-align: left; margin-left: 2px; margin-top: 0px; }
.progress_on { background: #333; width: 0px; height: 10px; position: relative;
z-index: 50; top: -10px; }
.progress { font-size: 11px; font-family: Arial, Helvetica, sans-serif;
color: #333; padding-left: 3px; width: 22px; float: left; margin-top: -2px; }
.progress_btns { position: relative; z-index: 100; width: 140px; height: 10px;}
.progress_btns ul, #progress_btns li { padding: 0; margin: 0; }
.progress_btns li { float: left; width: 13px; height: 10px; display: block;
font-size: 1px; cursor: pointer; color: #1E1D1C;
}
</style>
Switch to models.py and add the following function to display the bar:
def progress_(self):
return """
<div id="progress_cont_%s" class="progress_cont">
<div id="progress_btns_%s" class="progress_btns">
<ul>
<li>10</li>
<li>20</li>
<li>30</li>
<li>40</li>
<li>50</li>
<li>60</li>
<li>70</li>
<li>80</li>
<li>90</li>
<li>100</li>
</ul>
</div>
<div id="progress_on_%s" class="progress_on"> </div>
<div id="progress_%s" style="visibility: hidden"></div>
</div>
""" % (self.pk, self.pk, self.pk, self.pk)
progress_.allow_tags = True
Almost done! Type in the urlconf line and add the view:
(r"^progress/(\d*)/$", "progress"),
def progress(request, pk):
"""Set task progress."""
p = request.POST
if "progress" in p:
item = Item.objects.get(pk=pk)
item.progress = int(p["progress"])
item.save()
return HttpResponse('')
Now we can click on the bar to set progress:
I have used eligeske’s star comment code as inspiration for the progress bar widget.
If something’s not working for you, here are the full sources you can check against: todosrc.tar.gz.