So I found a nice piece of Jquery - Jquery-Dynamic-Form ( http://code.google.com/p/jquery-dynamic-form/ ), and thought it would be just the thing to spice up the static forms on my latest venture (a django website, of course). So I downloaded the jquery file and tested it out, and I thought, to myself that it would be no problem to integrate - well, it took longer than I thought, but I finally got it to work nicely.
Here’s how I was able to integrate dynamic addition of form fields on my django form using jquery- dynamic-form:
0. First, fix the JQUERY. This particular jquery library used a ‘php’ style naming scheme for its variables - namely, something like:
<input type="text" name="myfield[]" id="0">
<input type="text" name="myfield[]" id="1">
<input type ="text" name="myfield[]" id="2" >
As you can see, it used the php array as the name - instead, i make a quick fix to the jquery to make it instead just do this naming scheme:
<input type="text" name="myfield0" id="0">
<input type="text" name="myfield1" id="1">
<input type ="text" name="myfield2" id="2" >
To do that was rather easy, simply change a couple lines in the jquery; to look like this:
/* Normalize field name attributes */
if (!nameAttr) {
jQuery(this).attr("name", "field" + fieldId);
}
if (!/\[\]$/.exec(nameAttr)) {
jQuery(this).attr("name", nameAttr + fieldId);
}
As you can see, we only changed 2 lines of code, so that it appends the ‘fieldId’ to the end of the name attribute.
1. Set-up the form template properly with jquery-dynamic-form (easy) - lets you clone whatever field you want, in my case, just a + and - buttons to click on to add / remove text fields on the fly (this template needs a dynamic element to it, but we’ll get to that in step 3)
The JQuery Stuff:
<script type="text/javascript" src="/media/js/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="/media/js/jquery-ui-1.7.2.highlight.min.js"></script>
<script type="text/javascript" src="/media/js/jquery-dynamic-form.js"></script>
<script type="text/javascript">
$(document).ready (function() {
$("#duplicate").dynamicForm("#plus", "#minus", {limit:6, createColor:"yellow", removeColor:"red"});
});
</script>
The template form stuff:
<form method="post" action=".">
<fieldset>
<h3>My dynamic text fields</h3>
<div id="fields">
<div class="field">
{{ dynamic_html }}
<p><span><div id="plus">Add Another <a href="">[+]</a></div>
<div id="minus">Remove <a href="">[-]</a></div></span></p>
</div>
</div>
<p><input type="submit" value="Build it!" /><br/></p>
</fieldset>
</form>
As you can see, we dynamically create the guts of this template with a variable called {{ dynamic_html }} - I’ll show you what happens with this in a second.
But in general, its just some code that we generate that shows our bound form fields, but it works just like you think it would - clicking the plus adds a field, clicking the minus removes it, intuitive! Here is a quick preview:
2. When the form is submitted, the VIEW dynamically creates a form to match the submitted amount of fields. I used what i call the ‘type’ method to dynamically create the form- we’ll show you this in step three. When the form gets submitted, here is the view that handles it:
Here is the view code:
def myview(request, template_name='myapp/mytemplate.html'):
if request.method == 'POST':
#create the dynamic form class here on the fly
form_class = make_dynamic_form(post=request.POST)
form = form_class(data=request.POST)
#this is the template that we insert into our general template
dynamic_fields_template = make_dynamic_fields_template(form.data)
if form.is_valid():
#hooray! A Valid form - process the form fields as necessary!
return HttpResponseRedirect('/')
else:
initial_vals = {'empty_textbox':'',}
form_class = make_dynamic_form(post=initial_vals)
form = form_class()
dynamic_fields_template = make_dynamic_fields_template({'blog_url':''})
dynamic_html = dynamic_fields_template.render(Context({'form':form}))
return render_to_response(template_name,
{'form':form, 'dynamic_html':dynamic_html},
context_instance=RequestContext(request))
#Input: dictionary of fields into this method
#Output: Html (with our field variables not yet bound to context)
def make_dynamic_fields_template(fields):
template_html = ""
for f in fields:
template_html += """
<p id="duplicate">
My Form Label {{ form.%s }}
{{form.%s.errors}}
</p>
""" % (f, f)
return Template(template_html)
Here you can see that we dynamically create our form on the fly from the POST information using a custom method that we show in the next step. If the form is valid, perfect, and you can process it like any other form. If it was the first time we accessed the page (and didn’t POST), then you can see that we simply create a form with one field on the fly, render the template for this one field, and then pass this dynamic_template inside the other template; this variable gets rendered it like a regular variable using {{ dynamic_html }} inside the template.
Why do we have to render the template on the fly? Well, if the template (with a bunch of new, say, text fields you added dynamically) doesn’t validate - well, then you need to return that form you just used (with its corresponding errors) - but you still want it to be a dynamic form, but the original template you used doesn’t know how many fields you added or removed dynamically (using jquery), or what their names are! Thus, we need to dynamically generate a template again from our new form, which we do by created the html and passing it into our template as a variable (and rendering it there)
2.5 To create the form on the fly, we use this method:
http://www.b-list.org/weblog/2008/nov/09/dynamic-forms/ to create the form on the fly. You can see we override the ‘clean’ method; We validate all the dynamic fields, and if there are errors, assign the errors back to the individual fields (otherwise, errors would go in non_field_errors) - this is done using this technique: http://docs.djangoproject.com/en/dev/ref/forms/validation/#cleaning-and-validating-fields-that-depend-on-each-other
And here is how the form is created on the fly:
def make_dynamic_form(myfields={}):
fields = {}
#create the fields dynamically!
for p in myfields.keys():
fields[p] = forms.CharField(max_length=200, required=False, widget=forms.TextInput(attrs={'class':'text' }))
#this creates our new form with the given fields
newform = type('MyDynamicForm', (forms.BaseForm,), { 'base_fields': fields })
#now lets define our clean method
def clean(self):
cleaned_data = self.cleaned_data
#iterate through each value
for val in cleaned_data.keys():
if not val:
continue;
if (self.cleaned_data[val] is None) or (self.cleaned_data[val] == ''):
continue;
# clean the individual value below
...run your own cleaning tests here....
...say it doesn't validate.....
#we defined assign_error function to assign the error to the correct field
self.assign_error(val,u'This is an error we want to attach to that specific dynamic field!')
#this error goes in non_field_errors - we call this method anyway
raise forms.ValidationError(u'Error!')
#attaches an error to a specific field instead of non_field_errors
def assign_error(self,val,msg):
if not self._errors.has_key(val):
self._errors[val]= ErrorList([msg])
del self.cleaned_data[val]
#create the methods for the form
newform.clean = clean
newform.assign_error = assign_error
return newform
What we do here is create a fields dictionary, pass this dictionary into the ‘type’ function along with some other stuff, and we create our form. Now, we define the methods we want, and set these as attributes for the form, and finally return the new form. In this case, we defined the clean method, and also a helper method to assign the errors to their respective fields - otherwise, django would put all our errors in non_field_errors, because thats what happens when you raise form validation errors inside of the ‘clean’ method.
Thanks! Any questions / comments are welcome!
