Code


Version 2 (modified by artdent@…, 8 years ago) (diff)

You can get simplejson from the CheeseShop

Form submission

Inner links

Part 1
Ajax basic form submission, Django server answers Ajax call.
Part 2
Handling the form when JavaScript is deactivated.
Part 3
Fixing the frozen fading when user resend the form without waiting for the first fading to end.

Ajax basic form submission, Django server answers Ajax call.

Ajax form submission using Dojo for the client side javaScript toolkit, and simpleJson for the client/server communication.

This will take you through all the steps required to get started and to develop a handy form submission without reload.

What do you need

What do we want to achieve

I will use the model of my current website, a recipe one. When a registered user sees the details of a recipe, the user can rate (mark) it or update their rating (mark) by selecting the mark with a small drop down list. But when the user clicks ok the whole page reloads. We will use some Ajax to make it transparent and fancy effect to show the change to the user. we can add a fading status message like "Your rating has been updated". The select box proposes a rating from 1 to 5, it is actually a form which is sent to the server via POST, which in django links to a method in the view.py : def details(request) which does the innerwork for the details page generation.

Installing Json

get SimpleJson from svn:

svn co http://svn.red-bean.com/bob/simplejson/trunk/ json
cd json
sudo python setup.py install

or get the latest released version from the CheeseShop:

sudo python easy_install simplejson

Django part

view.py

def details(request):
        [more stuff]
        if request.POST:
                # Get all the mark for this recipe
                # its not the best way i sould hava an oter entry or table, with total and nbr of marks for
                # each recipe.
                list_mark = Mark.objects.values('mark').filter(recipe__pk=r.id)
                # loop to get the total
                total = 0
                for element in list_mark:
                        total+= element['mark']
                # round it
                total = round((float(total) /  len(list_mark)),1)
                # update the total
                r.total_mark= total
                # save the user mark
                r.save()

                # Now the intersting part for this tut
                import simple_json
                # it was a french string, if we dont't use unicode
                # the result at the output of json in Dojo is wrong.
                message = unicode( message, "utf-8" )
                #
                jsonList = simple_json.dumps((my_mark, total, form_message ,message))
                return HttpResponse(jsonList)
        [more stuff, if not POST return render_to_response('recettes/details.html' ...]

url.py

Just normal url.py, remember the path which will point to the wanted method.

from django.conf.urls.defaults import *

urlpatterns = patterns('',
        [...more...]
        (r'^recettes/(?P<r_cat>[-\w]+)/(?P<r_slug>[-\w]+)/$', 'cefinban.recettes.views.details'),
        [...more...]

Html template with Dojo javascript

Dojo use

{% load i18n %}
{% extends "base.html" %}
{% block script %}
 <script type="text/javascript" src="/media/js/dojo/dojo.js"></script>
 <script type="text/javascript">
 
    dojo.require("dojo.widget.Tooltip");
    dojo.require("dojo.fx.html");
    dojo.require("dojo.event.*");
    dojo.require("dojo.json");

    // point to the same url details.html	 
    function sendForm()
    {
     dojo.io.bind({
                    url: '.',
                    handler: sendFormCallback,
                    formNode: dojo.byId('myForm')
                });
    }

  function sendFormCallback(type, data, evt)
    {
    if (type == 'error')
        alert('Error when retrieving data from the server!');
    else
        // de code the simpleJson answer from django ''details'' method.
        // it populates a Js array ! straigth so cool !
    	arrayData = dojo.json.evalJson(data);
        // now we update the html using the css id as a pointer to the part 
        // we want to update
        dojo.byId("mark_total").innerHTML = arrayData[1];
        dojo.byId("mark_message").innerHTML = arrayData[2];
        dojo.byId("mark_status").innerHTML = arrayData[3];
        // and the fancy fading effect
        dojo.lfx.html.highlight("mark_status", [255, 151, 58], 700).play(300);
    }
    
    function init()
    {
        var sendFormButton = dojo.byId('sendFormButton');
        dojo.event.connect(sendFormButton, 'onclick', 'sendForm')
    }
 
    dojo.addOnLoad(init);
	
</script>

the following HTML code just comes after the upper code snipset.

[... total mark get updated, lets put css id="mark_total" ...]
{% if r.total_mark  %}<li><b>Score</b>: <span id="mark_total">{{ r.total_mark }}</span>/5</li>{% endif %}
[....]
{% if not user.is_anonymous %}
    {% ifnotequal user.id r.owner_id %}
        <form enctype="multipart/form-data" id="myForm" method="post">
            <span id="mark_message">{{mark_message}}</span>
	       <select name="select_mark">
	  	    <option value ="1" {% ifequal my_mark 1 %} selected {% endifequal %}>1</option>
	            <option value ="2" {% ifequal my_mark 2 %} selected {% endifequal %}>2</option>
	 	    <option value ="3" {% ifequal my_mark 3 %} selected {% endifequal %}>3</option>
	 	    <option value ="4" {% ifequal my_mark 4 %} selected {% endifequal %}>4</option>
	 	    <option value ="5" {% ifequal my_mark 5 %} selected {% endifequal %}>5</option>
	       </select>
       </form>
      <button id="sendFormButton">Notez</button>
      <br/>
      <span id="mark_status">{{ mark_status }}</span>
     {% endifnotequal %}
{% endif %}

And, voila.

To have a demo use guest as login, guest as password here http://ozserver.no-ip.com:345 or if not working here http://www.cefinban.net. Go to index and pick a recipe, update the rating.

You can also have a look at the screenshot here : http://ozserver.no-ip.com/~greg/images/ajaxdjango.png

Dreamhost and Simplejson

If you are using dreamhost for hosting please be aware that simplejson is not installed. Instead you will have to install the source of simplejson in a folder in your home directory eg /proz/json/simple_json The simple_json directory contains the required init.py for it to be loaded as a python module.

Then in your ~/.bash_profile add the directory to your python path like below.

export PYTHONPATH=$PYTHONPATH:$HOME/django/django_src:$HOME/django/django_projects:$HOME/progz/json

That will allow yout to use simpl_json in python shell. But dont forget to change django.fcgi ! Add

sys.path +=['/home/coulix/progz/json']

log out/in and try import simple_json (or simplejson depends on what source you picked)

Handling the form when JavaScript is deactivated.

If a user has deactivated his browser's javascript support, or is using a text mode browser, we need a way of making the previous rating button submit the rating to the server which should this time return an html template instead of data to the Ajax call.

Updating the form HTML (details.html template)

This time we put a submit type input inside the form instead of the button type in part 1. type="submit" as indicates its name, submits the form to the server, we will need a way of stopping this behavior using javaScript.

<form  enctype="multipart/form-data" id="myForm" method="post">
	<span id="mark_message">{{mark_message}}</span>
	<select name="select_mark">
	  	[...]
	</select>
	<input id="sendFormButton" type="submit" value="Notez" />
</form>

Now, how can we tell our details method in view.py to know if it comes from a normal submit request or an Ajax request ? Two solutions,

The first uses a hidden variable in form.html and an added content element in the JS part.

    function sendForm()
    {
     dojo.byId("mark_status").innerHTML = "Loading ...";
     dojo.io.bind({
                    url: '.',
                    handler: sendFormCallback,
                    content: {"js", "true"},
                    formNode: dojo.byId('myForm')
                });
    }


[...]
<form  enctype="multipart/form-data" id="myForm" method="post">
[...]
<input type="hidden" name="js" value="false">
</form>

With this, in our django method in view.py we can test for requestjs?=="true" it would means that Js is activatd and we return the appropriate answer to the Ajax request.

The second uses the url to pass a new variable ajax_or_not to the detail method.

def details(request, r_slug, r_cat, ajax_or_not=None):
[...]

We modify the url.py to accept this new parameter.

(r'^recettes/(?P<r_cat>[-\w]+)/(?P<r_slug>[-\w]+)/(?P<ajax_or_not>.*)$', 'cefinban.recettes.views.details'),

The dojo binding needs to append a variable to the original document url, to make ajax_or_not not None.

    function sendForm()
    {
     dojo.byId("mark_status").innerHTML = "Loading ...";
     dojo.io.bind({
                    url: './ajax/',
                    handler: sendFormCallback,
                    formNode: dojo.byId('myForm')
                });
    }

New details method in view.py

We just need to test for the existence of ajax_or_not

def details(request, r_slug, r_cat, ajax_or_not=None):
[...]
        if request.POST:
                [...] same as part 1 
                # except here we check ajax_or_not
                if  ajax_or_not:
                        # use json for python js exchange
                        # it was a french string, if we dont't use unicode
                        # the result at the output of json in Dojo is wrong.
                        message = unicode( message, "utf-8" )
                        jsonList = simple_json.dumps((my_mark, total, form_message ,message))
                        return HttpResponse(jsonList)

        return render_to_response('recettes/details.html', {'r': r, 'section':'index',
                 'mark_status':message , 'mark_message':form_message, 'my_mark': my_mark},
                  context_instance=RequestContext(request),)

Fixing the frozen fading when user resend the form without waiting for the first fading to end.

If you haven't realised yet, if two or more calls are sent to the javascript function sendForm in a short time, the fading effect of the current sendForm Callback method might get stuck / froze / bugged. We need a way of avoiding this by desactivating the connection between the submit button and the sendForm method while the fading animation is active. Thanks Dojo there is such things ! in two lines of code its done.

function sendFormCallback(type, data, evt)
    {
       [...as before ...]
        // and the fancy fading effect
        
        // first disconnect the listener !
        dojo.event.disconnect(sendFormButton, 'onclick', 'sendForm');
        // assign our fading effect to an anim variable.
        var anim = dojo.lfx.html.highlight("mark_status", [255, 151, 58], 700).play(300);
        // When this anim is finish, reconnect
        dojo.event.connect(anim, "onEnd", function() {  dojo.event.connect(sendFormButton, 'onclick', 'sendForm'); });
    }

how nice is this ! Careful, while talking about how to fix the problem using onEnd in Dojo IRC chanel, they realised play() method didnt behave properly and updated it to react to onEnd and such. su you need at least revision 4286. Update your dojo source

svn co http://svn.dojotoolkit.org/dojo/trunk dojo

Note It might be completely wrong. More questions / complaints: coulix@…