In this post I’ll show how I got an edit-in-place feature working with the shoutbox. In case you missed the earlier parts, here they are: part 1, part 2, part 3, part 4.
The first thing I did was create a “shout history” view. This allows you to view the shouts in a full page context. In my views.py file I created this:
def view(request, page=1):
"""This view allows one to view the shoutbox history."""
paginator = DiggPaginator(Shout.objects.all(), SHOUTS_PER_PAGE, body=5, tail=3, margin=3, padding=2)
try:
the_page = paginator.page(int(page))
except InvalidPage:
raise Http404
return render_to_response('shoutbox/view.html', {
'page': the_page,
},
context_instance = RequestContext(request))
Here I am using the DiggPaginator, which is a very nice paginator that gives you “Digg-style” pagination. I then pass the page object to the template which does most of the interesting work.
In order to get the edit-in-place funcitonality to work, I am leveraging the Jeditable jQuery plugin. In order to use this plugin, in my shoutbox/view.html template I include <script> tags to bring in both the jQuery javascript library and the Jeditable plugin code. I then loop over the shouts for the page and display them along with the avatar and links to the user that made the shout. You’ll also see me using the smilify fiilter I discussed in part 4.
{% extends 'base.html' %}
<pre>{% load avatar_tags %}
{% load smiley_tags %}
{% block custom_css %}
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/shoutbox_app.css" />
<link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}css/pagination.css" />
{% endblock %}
{% block custom_js %}
<script type="text/javascript" src="{{ MEDIA_URL }}js/jquery-1.2.6.min.js"></script>
<script type="text/javascript" src="{{ MEDIA_URL }}js/jquery.jeditable.mini.js"></script>
<script type="text/javascript" src="{{ MEDIA_URL }}js/shoutbox_app.js"></script>
{% endblock %}
{% block title %}Shout History{% endblock %}
{% block content %}
<h2>Shout History</h2>
{% if page.object_list %}
{% include 'core/pagination.html' %}
<div class="shoutbox-history">
<table>
{% for shout in page.object_list %}
<tr class="{% cycle 'even' 'odd' %}">
<th>
<a href="{% url bio-view_profile username=shout.user.username %}">{% avatar shout.user %}</a>
<a href="{% url bio-view_profile username=shout.user.username %}">{{ shout.user.username }}</a>
</th>
<td>
<div {% ifequal user shout.user %}class="edit" id="shout-{{ shout.id }}"{% endifequal %}>{{ shout.shout|smilify|urlize }}</div>
</div>
<br />
<span class="date">{{ shout.shout_date|date:"D M d Y H:i:s" }}</span>
</td>
</tr>
{% endfor %}
</table>
</div>
{% include 'core/pagination.html' %}
{% else %}
<p>No shouts at this time.</p>
{% endif %}
{% endblock %}
In order to get the edit-in-place working, I need to enclose the shouts the user can edit in a special div with the class “edit”. I do this inside the loop, adding the class attribute if the current user matches the shout user. I also give each of these divs an id of “shout-xx” where xx is the id of the shout being displayed. I thought about just using the shout id as the id, but thought this might clash with other page elements, so I prefixed it with “shout-”. This adds a tiny bit of work back in the view code, but not much.
Now that I have “tagged” the divs the user can edit, I configure the plugin with the following bit of jQuery magic in the shoutbox_app.js file:
$(document).ready(function() {
$('.edit').editable('/shout/edit/', {
loadurl : '/shout/text/',
indicator : 'Saving...',
tooltip : 'Click to edit your shout...',
submit : 'OK',
cancel : 'Cancel'
});
});
This code instructs jQuery to find all elements with the class “edit” and call the editable() function on them. The first parameter to editable() is the URL to send the edited text to when the user hits enter or clicks OK. In our case, this is /shout/edit. I’ll show this view in a minute. The next parameter to the editable() function is a javascript options object with several interesting options:
Holy crap that was easy. Now all we need to do is provide those 2 view functions. One to receive and save the edited text, and one that is called to retrieve the original shout. First, here is the /shout/view view function that retrieves the original text.
shout_id_re = re.compile(r'shout-(\d+)')
def text(request):
"""This view function retrieves the text of a shout; it is used in the in-place
editing of shouts on the shoutbox history view."""
if request.user.is_authenticated():
m = shout_id_re.match(request.GET.get('id', ''))
if m is None:
return HttpResponse(u'')
try:
shout = Shout.objects.get(pk=m.group(1))
except Shout.DoesNotExist:
return HttpResponse(u'')
return HttpResponse(shout.shout)
return HttpResponse(u'')
Jeditable is expecting that this function return the raw shout, not XHTML markup. It will pass the id of the div the user wants to edit as a GET parameter. First we check to see if the user is authenticated as a minimal check. Note that we don’t use the login_required decorator, because that would redirect the Jeditable request to the login page if the user wasn’t logged in. We then use a regular expression to pull the numeric part out of the div’s id (recall that it comes in the form shout-xx). With the id in hand we simply query the shout object using the Django ORM. We then return the shout text as an HttpResponse.
Finally here is the view function that Jeditable posts the edited data to.
def edit(request):
"""This view accepts a shoutbox edit from the shoutbox history view."""
if request.user.is_authenticated():
m = shout_id_re.match(request.POST.get('id', ''))
if m is None:
return HttpResponse(u'')
try:
shout = Shout.objects.get(pk=m.group(1))
except Shout.DoesNotExist:
return HttpResponse(u'')
if request.user != shout.user:
return HttpResponse(u'')
new_shout = request.POST.get('value', '').strip()
if new_shout == '':
return HttpResponse(u'')
shout.shout = new_shout
shout.save()
return render_to_response('shoutbox/render_shout.html', {
'shout': shout,
},
context_instance = RequestContext(request))
return HttpResponse(u'')
This function does do a lot of error checking, so it looks more complicated than it really is. This time Jeditable is expecting us to return XHTML markup, and it passes 2 things to us as POST parameters: the div id appears as “id” and the new text appears as “value”.
In short, this function checks to make sure the user posting the new shout text is the same user that created the shout in the first place. If this is the case, it simply updates the shout text and saves it back to the database. We then render a template to return the new XHTML markup for the div. Remember that we have to smilify the text, so our template looks like this:
{% load smiley_tags %}
{{ shout.shout|smilify|urlize }}
We load our smiley tags discussed in part 4, and filter the shout through our smilify filter and the Django urlize filter.
One thing that is very useful to add at this point is some CSS for our editable div tags. I added a :hover rule that changed the background color and mouse cursor. This provides additional visual cues to the user that the text can edited when the mouse is hovered over it.
And that is basically it! Here is a screen shot of a user editing a shout in-place on the shout history page.

Edit-in-place in action
I plan on adding one more blog post about the shoutbox. All we have left to do is an in-place delete of a shout on the shout history page. Stay tuned for part 6.
[...] new shoutbox. In case you missed the earlier parts, here they are: part 1, part 2, part 3, part 4, part 5. Here we are going to discuss the final feature: deleting a shout in place off the shout history [...]