Login

Ajax form with jQuery

Author:
Donn
Posted:
August 18, 2008
Language:
Python
Version:
.96
Score:
0 (after 0 ratings)

I recently got a form working via jQuery and Django. This was not easy for me to do and I thought I'd record my finding here.

The form submits via jQuery and the "form" plugin. Please visit jQuery's home page to find all those links.

This code handles: * urls.py -- passing both normal and 'Ajax' urls to a view.

  • views.py -- Handling both kinds of requests so that both normal and ajax submits will work.

  • The HTML template with the script for submitting and some bling.

Error handling

I like to stay DRY so the idea of checking the form for errors in javascript and checking it in Django irks me. I decided to leave that up to Django, so the form submits and gets validated on the server. The error messages are sent back to the browser and then displayed.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
Mixed code -- Sorry about that.
urls.py
===
#Direct to template: For non-ajax form.
(r'afterform/$', 'django.views.generic.simple.direct_to_template', {"template": 'contact/afterform.html',"extra_content":{"error":False} }),
	
#Handle contact form. Using a named url: Note use in template below.
#This url handles contact/ and contact/xhr : The 'xhr' is a flag to tell the
#view that this is an ajax POST. I can't recall what it stood for :)
url(r'contact/(?P<xhr>.*)$', 'scents.contact.views.contactForm',name='contactform'),

views.py
===
from django.shortcuts import render_to_response
from django.http import HttpResponseRedirect, HttpResponse
from django import forms
from django.template import RequestContext
from django.utils import simplejson

# This describes our "form" - on the server side.
class ContactForm( forms.Form ):
	subject = forms.CharField(label = "Subject",max_length=80)
	text = forms.CharField(label = "Your query",widget=forms.Textarea)
	class Media:
		# Plug in the javascript we will need:
		js = ("/media/js/jquery/jquery-1.2.6.pack.js", "/media/js/jquery/plugins/form/jquery.form.js")

# The main view:
def contactForm( request, xhr="WTF?" ): #The xhr default is being ignored. Weird.
	# Is this a POST?
	if request.method == "POST":
		form = ContactForm(request.POST)
		#Check if the <xhr> var had something passed to it.
		if xhr=="xhr":
			# Yup, this is an Ajax request.
			
			# Validate the form:
			clean = form.is_valid()

			# Make some dicts to get passed back to the browser
			rdict = {'bad':'false'}
			if not clean:
				rdict.update({'bad':'true'})
				d={}
				# This was painful, but I can't find a better way to extract the error messages:
				for e in form.errors.iteritems():
					d.update({e[0]:unicode(e[1])}) # e[0] is the id, unicode(e[1]) is the error HTML.
				# Bung all that into the dict
				rdict.update({'errs': d  })

			# Make a json whatsit to send back.
			json = simplejson.dumps(rdict, ensure_ascii=False)

			# And send it off.
			return HttpResponse( json, mimetype='application/javascript')
		# It's a normal submit - non ajax.
		else:
			if form.is_valid():
				# Move on to an okay page:
				return HttpResponseRedirect("/scents/afterform/")
	else:
		# It's not post so make a new form
		form = ContactForm()#error_class=DivErrorList)
	# Get it rollin:
	return render_to_response(
		'contact/contact.html',
		{
		"form":form,
		},
		context_instance=RequestContext(request)
		)
		

contact.html (The template)
===
{% extends "base.html" %}

{% block J%}{{form.media}}{% endblock %}

{% block CSS %}forms.css{%endblock%}


{% block JQUERY %}
// prepare the form when the DOM is ready 
$(document).ready(function() { 
	// prepare Options Object 
	var options = { 
	url: '{% url contactform "xhr"%}', // Here we pass the xhr flag
        dataType:  'json', 
	success:   processJson, //What to call after a reply from Django
	beforeSubmit: beforeForm
	};
    // bind form using ajaxForm 
    $('#tf').ajaxForm(options); //My form id is 'tf'
});

function beforeForm() { 
	$('#bt').attr("disabled","disabled"); //Disable the submit button - can't click twice
	$('.errorlist').remove(); //Get rid of any old error uls
	$('#emsg').fadeOut('slow'); //Get rid of the main error message
	return true; //Might not need this...
}

#Do stuff with server reply:
function processJson(data) { 
// This is the first time I have touched jQuery, so I'm not sure about my
// approach. It does work, but perhaps not in the best way.
	//Do we have any data at all?
	if (data) {
		// Build a var. NOTE: Make sure your id name (this is a div) is NOT THE SAME
		// as any var in javascript -- ie has a fit and barfs errors.
		e_msg = "We received your form, thank you.";
		// Did we set the 'bad' flag in Django?
		// Use eval() to turn the stuff in the data object into actual vars.
		if (eval(data.bad)) {
			e_msg = "Please check your form.";
			errors = eval(data.errs); //Again with the eval :)
			// This is very nice: Go thru the errors, build an id name and
			// then access that tag and add the HTML from the Django error
			$.each(errors,function(fieldname,errmsg)
			{
				id = "#id_" + fieldname;
				$(id).parent().before( errmsg ); //I want the error above the <p> holding the field
				});
			// re-enable the submit button, coz user has to fix stuff.
			$('#bt').attr("disabled","");
		}
		//Show the message
		$('#emsg').text( e_msg ).fadeIn("slow");
	} else {
		//DON'T PANIC :D
		$('#emsg').text("Ajax error : no data received. ").fadeIn("slow");
	}
}
{% endblock %}

{% block CONTENT %}

<div id="emsg">&nbsp;</div>

<form action="{% url contactform None %}" method="post" id="tf">
	<fieldset>
		{{ form.as_p }} 
		<input class="input" type="submit" value="Send mail" id="bt"/>
	</fieldset>
</form>

{% endblock %}

Conclusion
===
Right, that's it. I hope. If I didn't make any horrible mistakes in writing this small tut then you should be able to reproduce the effect.

HTH
\d

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 2 months ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 2 months, 1 week ago
  3. Serializer factory with Django Rest Framework by julio 9 months, 1 week ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 9 months, 4 weeks ago
  5. Help text hyperlinks by sa2812 10 months, 3 weeks ago

Comments

PaulAik (on February 19, 2011):

When I try this on IE8, it prompts me to download the AJAX response!

Works on FF, Chrome etc - anything I could be doing wrong?

#

mischko (on February 22, 2011):

The mime type is incorrect for JSON responses. It should not be application/javascript, but application/json.

Also, the use of eval for JSON parsing is a Very Bad Thing. Use the JSON Javascript library at https://github.com/douglascrockford/JSON-js

I don't think you need the painful method of getting the error message but can probably use this (untested): if not clean: rdict.update({'bad':'true'}) rdict.update({'errs': dict(form.errors.iteritems()) })

(I don't think you need unicode for this. If you do, then use a list comprehension).

#

ermandoser (on May 25, 2011):

This snippet does not submit the form without ajax if there are no errors, correct me if I am wrong.

#

ermandoser (on June 2, 2011):

"btaylordesign" is right about xhr flag, since you can get the type of the request from HttpRequestHeader, request.is_ajax() does what you need with xhr flag. So the url regex for capturing the "xhr" parameter is also not necessary. For newbies like me, this snippet is pointing out a nice way to deal with default form errors while submitting the form via ajax. The basic idea is returning a json object to the client side then check if any errors exist. If there is not your view should create the object then return a json object with 'bad':false which states your form is submitted successfully and related objects are created on server side, so it is kind of a success message for you to evaluate on client side. You can either show errors if 'bad':true or do whatever you need to do on a successful callback if 'bad':false.

#

Please login first before commenting.