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

Comments

btaylordesign (on November 17, 2008):

Hi Donn,

Excellent work. I did run into a couple of issues that I thought I would share...

The form submission is done with the jQuery form plugin: http://jqueryjs.googlecode.com/svn/trunk/plugins/form/jquery.form.js, but you don't mention that in the setup, which might leave people scratching their heads as to why .ajaxForm isn't a function.

It's not necessary to pass in the extra "xhr" variable to your view. Django has a function you can call on the request object to check for an XmlHttpRequest: request.is_ajax()

I'm using jQuery 1.2.6, and had issues with iterating over the error object using $.each. It seems jQuery doesn't like values to be quoted in json for keys (or I need more coffee), but obviously that presents a problem since some of those values are HTML.

So, $.each wouldn't work for me, but...

for(field in errors) $('#id_' + field).after(errors[field]);

...works as expected, and it's slightly less code than $.each

This is great stuff. Thank you for taking the time to work through this, and for sharing!

Kindest regards, Brandon

#

(Forgotten your password?)

You may use Markdown syntax here, but raw HTML will be removed.