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"> </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
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
#
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?
#
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).
#
This snippet does not submit the form without ajax if there are no errors, correct me if I am wrong.
#
"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.
#