Ajax form with jQuery

  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. Complex Formsets by smagala 5 years, 2 months ago
  2. View and StatefulView classes by Digitalxero 5 years, 6 months ago
  3. RecaptchaForm by oggy 5 years, 10 months ago
  4. Complex Form Preview by smagala 5 years ago
  5. Jquery ajax csrf framework for Django by chriszweber 2 years, 2 months 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.

#

(Forgotten your password?)