How can you make a vote-up-down button like in Stackoverflow?

Problems

  1. how to make an Ajax buttons (upward and downward arrows) such that the number can increase or decrease
  2. how to save the action af an user to an variable NumberOfVotesOfQuestionID

I am not sure whether I should use database or not for the variable. However, I know that there is an easier way too to save the number of votes.

How can you solve those problems?

[edit]

The server-side programming language is Python.


This is a dirty/untested theoretical implementation using jQuery/Django.

We're going to assume the voting up and down is for questions/answers like on this site, but that can obviously be adjusted to your real life use case.

The template

<div id="answer_595" class="answer">
  <img src="vote_up.png" class="vote up">
  <div class="score">0</div>
  <img src="vote_down.png" class="vote down">
  Blah blah blah this is my answer.
</div>

<div id="answer_596" class="answer">
  <img src="vote_up.png" class="vote up">
  <div class="score">0</div>
  <img src="vote_down.png" class="vote down">
  Blah blah blah this is my other answer.
</div>

Javascript

$(function() {
    $('div.answer img.vote').click(function() {
        var id = $(this).parents('div.answer').attr('id').split('_')[1];
        var vote_type = $(this).hasClass('up') ? 'up' : 'down';
        if($(this).hasClass('selected')) {
            $.post('/vote/', {id: id, type: vote_type}, function(json) {
                if(json.success == 'success') {
                    $('#answer_' + id)
                     .find('img.' + vote_type);
                     .attr('src', 'vote_' + vote_type + '_selected.png')
                     .addClass('selected');
                    $('div.score', '#answer_' + id).html(json.score);
                }
            });
        } else {
            $.post('/remove_vote/', {id: id, type: vote_type}, function(json) {
                if(json.success == 'success') {
                    $('#answer_' + id)
                     .find('img.' + vote_type);
                     .attr('src', 'vote_' + vote_type + '.png')
                     .removeClass('selected');
                    $('div.score', '#answer_' + id).html(json.score);
                }
            });                
        }
    });
});

Django views

def vote(request):
    if request.method == 'POST':
        try:
            answer = Answer.objects.get(pk=request.POST['id'])
        except Answer.DoesNotExist:
            return HttpResponse("{'success': 'false'}")

        try:
            vote = Vote.objects.get(answer=answer, user=request.user)
        except Vote.DoesNotExist:
            pass
        else:
            return HttpResponse("{'success': 'false'}")

        if request.POST['type'] == 'up':
            answer.score = answer.score + 1
        else:
            answer.score = answer.score - 1

        answer.save()

        Vote.objects.create(answer=answer,
                            user=request.user,
                            type=request.POST['type'])

        return HttpResponse("{'success':'true', 'score':" + answer.score + "}")
    else:
        raise Http404('What are you doing here?')

def remove_vote(request):
    if request.method == 'POST':
        try:
            answer = Answer.objects.get(pk=request.POST['id'])
        except Answer.DoesNotExist:
            return HttpResponse("{'success': 'false'}")

        try:
            vote = Vote.objects.get(answer=answer, user=request.user)
        except Vote.DoesNotExist:
            return HttpResponse("{'success': 'false'}")
        else:
            vote.delete()

        if request.POST['type'] == 'up':
            answer.score = answer.score - 1
        else:
            answer.score = answer.score + 1

        answer.save()

        return HttpResponse("{'success':'true', 'score':" + answer.score + "}")
    else:
        raise Http404('What are you doing here?')

Yikes. When I started answering this question I didn't mean to write this much but I got carried away a little bit. You're still missing an initial request to get all the votes when the page is first loaded and such, but I'll leave that as an exercise to the reader. Anyhow, if you are in fact using Django and are interested in a more tested/real implemention of the Stackoverflow voting, I suggest you check out the source code for cnprog.com, a Chinese clone of Stackoverflow written in Python/Django. They released their code and it is pretty decent.


A couple of points no one has mentioned:

  • You don't want to use GET when changing the state of your database. Otherwise I could put an image on my site with src="http://stackoverflow.com/question_555/vote/up/answer_3/".
  • You also need csrf (Cross Site Request Forgery) protection
  • You must record who makes each vote to avoid people voting more than once for a particular question. Whether this is by IP address or userid.

You create the buttons, which can be links or images or whatever. Now hook a JavaScript function up to each button's click event. On clicking, the function fires and

  • Sends a request to the server code that says, more or less, +1 or -1.
  • Server code takes over. This will vary wildly depending on what framework you use (or don't) and a bunch of other things.
  • Code connects to the database and runs a query to +1 or -1 the score. How this happens will vary wildly depending on your database design, but it'll be something like UPDATE posts SET score=score+1 WHERE score_id={{insert id here}};.
  • Depending on what the database says, the server returns a success code or a failure code as the AJAX request response.
  • Response gets sent to AJAX, asynchronously.
  • The JS response function updates the score if it's a success code, displays an error if it's a failure.

You can store the code in a variable, but this is complicated and depends on how well you know the semantics of your code's runtime environment. It eventually needs to be pushed to persistent storage anyway, so using the database 100% is a good initial solution. When the time for optimizing performance comes, there are enough software in the world to cache database queries to make you feel woozy so it's not that big a deal.