Code


Version 6 (modified by WillNorris, 4 years ago) (diff)

grammar

Ajax submiting and processing form errors with newforms and prototype

The task

  • Show a form;
  • Submit the form and show validation errors;
  • Handle both js enabled and js disabled (ajax and standard form submiting).

I want to submit only the form and get validation errors from django newforms without submiting the whole page. Django Newforms library is really exciting and easy to use. Django is also great that doesn't restrict me to use any specific ajax/js library so I can use whatever I want in this case prototype.

So I'm using

  • django.newforms
  • django.utils.simplejson which converts python data into JSON data so I can exchange it with the browser.
  • And I'm using the great prototype.js library that makes javascript and ajax quite easy.

The Form

The form is a simple contact form with all four fields required. Just to have something to validate. :)

from django import newforms as forms
from django.newforms.widgets import *

# A simple contact form with four fields.
class ContactForm(forms.Form):
    name = forms.CharField()
    email = forms.EmailField()
    topic = forms.CharField()
    message = forms.CharField(widget=Textarea())

The View

The view returns either JSON with the errors from the form or a whole page if javascript is disabled in the browser.

I have a hidden input element in the form. I set it to None just before sending the form with javascript so the view knows what should it return, the whole page or just the errors with JSON.

The rest is just a generic newforms code. I check if the form is valid and redirect the user if it is or return the errors if it's not. When the form is valid and using AJAX I use a special field {'OK': 'java script for the browser to execute'}. The client-side javascript "knows" that it should run the javascript when it encounters 'OK'.

def contactview(request):
    if request.method == 'POST':
        theform = ContactForm(request.POST)

        # if "js" got here together with the form it means that the 
        # browser doesn't have javascript so handle the form without js
        js = request.POST.get('js', None) 

        if theform.is_valid():
            if not js:
                # simplejson makes a json format so we can do an 
                # eval() and run the javascript the view returns
                jsaction = 'window.location.pathname="/"'
                data = simplejson.dumps({'OK':jsaction})
            else:
                return HttpResponseRedirect('/')
        else:
            if not js:
                data = simplejson.dumps(theform.errors)
            else:
                return render_to_response('contactform.html', {'form': theform})
        return HttpResponse(data, mimetype="text/javascript")
    else:
        # show a blank form
        return render_to_response('contactform.html', {'form': ContactForm()})

The Javascript

<script type="text/javascript">
// when the page gets loaded, call the init function to hijack all the form
Event.observe(window, 'load', init, false);

function init(){
    // hijack the form submit
    Event.observe($('theform'), 'submit', processform, false);
}

function processform(e) {

    // get the form that got submited ( the first parrent element of the element that trigger the event)
    var aform = Event.findElement(e, 'form');
    // where to submit the form
    var ajaxurl = aform.action;
    var formparameters = aform.serialize(true); // take the form field values before you disable it

    //disable the form elements while beeing processed
   aform.disable();

    // get the submit button of the form and show an ajax-loader instead
    var submitbutton = aform.getInputs('submit')[0];
    new Insertion.Before(submitbutton, '<img src="/site_media/ajax-loader.gif" id="ajaxloader" />');
    submitbutton.hide()

    // make the js field empty so that django knows how to handle the request
    // if js is submited with the form django handles it as a normal form
    // if js is None django returns AJAX variables
    document.getElementById('js').value = ''

        var myAjax = new Ajax.Request(
        ajaxurl,
        { method: 'post', 
          parameters: formparameters, 
          onSuccess: handlereq, 
          onFailure: function() {alert('Refresh the Page, something happend to the server');} // what to do if the server return something else than 2xx status.
        });

    function handlereq(req) {
        // evaulate the response (get the errors or run the javascript)
        var errors = eval('(' + req.responseText + ')');

        // clean the errors from the previous run (elements with class="errorlist"
        var old = aform.getElementsByClassName('errorlist');
        for (i=0; i<old.length; i++) { old[i].remove(); }

        // if there is an OK attribute in the response, run the javascript
        // if not show the errors in the for_fieldname element
        if ( errors.OK ){
            eval(errors.OK)
        } else {
            $H(errors).each(function(pair) {
                $('for_'+pair.key).innerHTML = '<ul class="errorlist"><li>'+pair.value+'</li></ul>';
            });

        aform.enable(); // enable the form
        $('ajaxloader').remove(); // remove the ajax-loader
        submitbutton.show(); // return the submit button
        }
    }

    // don't trigger the actual html submit form
    Event.stop(e);
}
</script>

The Template

The template for the form has a span element with id="for_fieldname" where the javascript puts the errors and {{ forms.fieldname.errors }} where django puts the errors if javascript is disabled.

<html>
<head>
<title>Contact form</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<script type="text/javascript" src="/site_media/prototype.js"></script>
</head>
<body>
<form method="post" action="." id="theform" name="theform">
    <fieldset>
    <legend>Contact form</legend>

    <label for="id_name">Your name:</label>
   {# in the for_fieldname element I put the errors with js #}
    <span id="for_name"></span> 
   {# this are the errors when javascript is disabled #}
    {{ form.errors.name }}
    <br />
   {{ form.name }}<br /><br />
    <label for="id_email">Your email:</label>
    <span id="for_email"></span>
    {{ form.errors.email }}
    <br />
   {{ form.email }}<br /><br />

    <label for="id_topic">Topic:</label>
    <span id="for_topic"></span>
    {{ form.errors.topic }}
    <br />
   {{ form.topic }}<br /><br />

    <label for="id_message">Message:</label>
    <span id="for_message"></span>
    {{ form.errors.message }}
    <br />
   {{ form.message }}<br /><br />

    <br />
    <input type="hidden" name="js" id="js" value="disablewithjs">
    <input type="submit" id="submitbutton" value="Send">
    </fieldset>
</form>
</body>
</head>