A reader on another post asked me about using jQuery to dynamically add form fields to an existing form. I whipped up a quick demo that I'd like to get folks opinions on. Here is what I came up with:
1 <cfif not structIsEmpty(form)>
2 <cfdump var="#form#">
3 </cfif>
4
5 <html>
6
7 <head>
8 <script src="/jquery/jquery.js"></script>
9 <script>
10 var current = 1;
11
12 function addPerson() {
13 console.log('running addPerson')
14 //current keeps track of how many people we have.
15 current++;
16 var strToAdd = '<p><label for="firstname"'+current+'">Name</label> <em>*</em><input id="firstname'+current+'" name="firstname'+current+'" size="25" /> <input id="lastname'+current+'" name="lastname'+current+'" size="25" />'
17 strToAdd += '<p><label for="email'+current+'">Email</label> <em>*</em><input id="email'+current+'" name="email'+current+'" size="25" /></p>'
18 console.log(strToAdd)
19 $('#mainField').append(strToAdd)
20 }
21
22 $(document).ready(function(){
23 $('#addPerson').click(addPerson)
24 });
25 </script>
26 </head>
27
28 <body>
29
30 <form id="someform" method="post">
31 <fieldset id="mainField">
32 <p>
33 <label for="firstname1">Name</label>
34 <em>*</em><input id="firstname1" name="firstname1" size="25" /> <input id="lastname1" name="lastname1" size="25" />
35 </p>
36 <p>
37 <label for="email1">Email</label>
38 <em>*</em><input id="email1" name="email1" size="25" />
39 </p>
40 </fieldset>
41
42 <p>
43 <input type="button" id="addPerson" value="Add Another Person">
44 </p>
45
46 <input type="submit" value="Save">
47 </form>
48
49 </body>
50 </html>
I want to talk about this from the bottom up, so please read 'up' with me. The form contains one block of detail for a person - a firstname, lastname, and email address. There are two buttons - one to add another person and one to submit. This is what I started off with as the base form. I wanted it so that when you clicked Add Another Person, it would essentially duplicate the 3 fields.
I began by telling jQuery to monitor the click event for the button (inobtrusive JS FTW):
2 <cfdump var="#form#">
3 </cfif>
4
5 <html>
6
7 <head>
8 <script src="/jquery/jquery.js"></script>
9 <script>
10 var current = 1;
11
12 function addPerson() {
13 console.log('running addPerson')
14 //current keeps track of how many people we have.
15 current++;
16 var strToAdd = '<p><label for="firstname"'+current+'">Name</label> <em>*</em><input id="firstname'+current+'" name="firstname'+current+'" size="25" /> <input id="lastname'+current+'" name="lastname'+current+'" size="25" />'
17 strToAdd += '<p><label for="email'+current+'">Email</label> <em>*</em><input id="email'+current+'" name="email'+current+'" size="25" /></p>'
18 console.log(strToAdd)
19 $('#mainField').append(strToAdd)
20 }
21
22 $(document).ready(function(){
23 $('#addPerson').click(addPerson)
24 });
25 </script>
26 </head>
27
28 <body>
29
30 <form id="someform" method="post">
31 <fieldset id="mainField">
32 <p>
33 <label for="firstname1">Name</label>
34 <em>*</em><input id="firstname1" name="firstname1" size="25" /> <input id="lastname1" name="lastname1" size="25" />
35 </p>
36 <p>
37 <label for="email1">Email</label>
38 <em>*</em><input id="email1" name="email1" size="25" />
39 </p>
40 </fieldset>
41
42 <p>
43 <input type="button" id="addPerson" value="Add Another Person">
44 </p>
45
46 <input type="submit" value="Save">
47 </form>
48
49 </body>
50 </html>
1 $(document).ready(function(){
2 $('#addPerson').click(addPerson)
3 });
addPerson then had to do two things. First, it needed to know how many people existed on the page already. I had created a JavaScript variable, 'current', with a hard coded value of 1. This doesn't need to be hard coded, but it certainly seemed to be the simplest way to handle it. Inside of addPerson, I immediately increase the value by one. I generate the HTML I want to use which was basically a cut and paste of the HTML below. The only difference is that I make the number part dynamic. I could have probably used a special character like $N and then used Regex to replace it. (On second though, that would have been a lot cleaner I think.) Then I just append the new string to the HTML.
You can view this here.
So a few open questions/things to discuss:
1) jQuery provides a clone() function for the DOM. In theory, I could have just cloned my fieldset. The problem with that (as far as I know) is that I'd end up with form fields that have the same name. That "works" in CF, but the values would be a list. So for example, form.firstname would be "Ray,Jay". That works well until someone has a comma in their name. Not very likely, but still. I believe in PHP it actually gives you the values in an array, but in CF we have no control over that. Maybe I could have done a clone, gotten the new node, and did the regex on the HTML?
2) I didn't demonstrate the CF side to parse this because I've done so many times before, but in case folks are curious - you would simply introspect the Form struct to figure out how many people you have to process.
3) Of course, the next step is to add validation. I'm willing to bet I can use the kick butt jQuery Validation plugin with dynamic forms. I'll check that next!
2 $('#addPerson').click(addPerson)
3 });
Comment 1 written by Kyle Hayes on 19 February 2009, at 12:10 PM
Comment 2 written by Brian Swartzfager on 19 February 2009, at 12:14 PM
Comment 3 written by Neil Bailey on 19 February 2009, at 12:15 PM
That being said, I have checked out jQuery based solely on the exuberance you have displayed about it in your blog posts.
I am amazed at its versatility! While I can't say its exactly _easy_ to use, the jQuery mailing list is EXTREMELY responsive and helpful, and I can see us using this in our site in the very near future.
We are still using CF7 (it works for us, and mgmt feels if it ain't broke....), and are using AjaxCFC for our AJAX calls, and jQuery makes using Rob's package SO much easier.
So, thanks for pointing us in the right direction. Again.
And fix your test page :)
Comment 4 written by Raymond Camden on 19 February 2009, at 12:31 PM
@Kyle: It seems like for my use here, adding about 3 form fields, labels, etc, that adding by createElement would be somewhat slow (not slow to run, slow to write)
@Brian - do you know if clone will return the 'package'? Ie, I clone the fieldSet and get back X, X being the copy of the fieldSet so I could then dig into it and make the changes. Does that make sense?
Comment 5 written by Jonathan on 19 February 2009, at 12:38 PM
Comment 6 written by Josh Nathanson on 19 February 2009, at 12:46 PM
This hidden fieldset can be outside the form tags so you don't have to worry about it when you post.
Comment 7 written by Raymond Camden on 19 February 2009, at 12:55 PM
Comment 8 written by Josh Nathanson on 19 February 2009, at 1:00 PM
var newset = $("#hiddenFieldset").clone();
$("input[name=firstname]", newset).attr("name", "firstname"+current);
etc.
Comment 9 written by Jon Hartmann on 19 February 2009, at 1:12 PM
var template = new Template('<input type="text" name="field#{number}" value="" /><br /');
And then to add it you would call:
new Insertion(
'bottom': template.evaluate({number: currentCount++});
);
Comment 10 written by Brian Swartzfager on 19 February 2009, at 1:26 PM
But Josh gave you pretty much the same answers I would have. I also tend to use a blank, hidden "template" element to use with the clone() function, then alter the newly cloned element as needed.
Comment 11 written by Emilie on 19 February 2009, at 1:45 PM
Joern's Validation plugin includes a template utility that will replace {n} placeholders with arguments. This demo makes use of it: http://jquery.bassistance.de/validate/demo/dynamic...
Comment 12 written by Raymond Camden on 19 February 2009, at 1:54 PM
@Emilie: Interesting. I used that in one of my demos. I'm going to avoid that for now though - I want to do this stuff in baby steps. So my first step is to improve the process w/ clone. Then I'll add the validation. Of course, if I clone, I want need the templating stuff anyway since I'll just use DOM manipulation on the new node.
Of course... now that I think about it - I will have to do about 6 DOM calls (changing IDs, NAMEs), so it may make sense to NOT use DOM manipulation. Templating may be quicker. But that has the side effect of me needing to update the string if I change the main 'package' I have to clone. Then again, that would also apply to DOM as well.
Confusing! Ok, I'll take it easy at first and try clone next.
Comment 13 written by Jon Hartmann on 19 February 2009, at 2:04 PM
http://www.jonhartmann.com/index.cfm/2009/2/19/Exa...
Comment 14 written by Raymond Camden on 19 February 2009, at 2:35 PM
Comment 15 written by Hatem Jaber on 19 February 2009, at 4:30 PM
http://bassistance.de/jquery-plugins/
Comment 16 written by Raymond Camden on 19 February 2009, at 4:49 PM
Comment 17 written by Brian Swartzfager on 19 February 2009, at 4:50 PM
I wrote it such that you don't have to update each "for", "name", and "id" attribute individually. You can see it/view the code for it here:
http://www.swartzfager.org/blog/demoFiles/clonedFi...
Comment 18 written by Jon Hartmann on 19 February 2009, at 5:53 PM
That might help dispel some of the confusion about my example.
Comment 19 written by Raymond Camden on 19 February 2009, at 8:03 PM
Comment 20 written by Brian Swartzfager on 19 February 2009, at 8:48 PM
If the form submission was a regular submission and not an AJAX submission, I suppose you could also use the jQuery remove() function on the template after the client-side validation took place and just before the form was submitted, thereby eliminating it from the form submission.
Comment 21 written by Misty on 10 May 2009, at 6:39 AM
2. how we Limit the textboxes to 10. mean they should not exced more than 10. a Popup alert message should be displayed that no more than 10.
Comment 22 written by Raymond Camden on 10 May 2009, at 9:36 PM
#1 You would modify the HTML added to include the link. That link would run JS code to remove the item. It would be a bit more complex, a bit too much for a blog comment.
Comment 23 written by Claude Petitpas on 13 July 2009, at 2:21 PM
Comment 24 written by Raymond Camden on 17 July 2009, at 10:35 PM
Comment 25 written by Rob on 25 July 2009, at 9:49 PM
Comment 26 written by Raymond Camden on 25 July 2009, at 9:56 PM
Comment 27 written by Kris on 29 July 2009, at 7:18 PM
I was tweaking around with the code a bit, trying to get it to work with something Im working on and for some reason I can't get it to work in IE. Can anyone point out why?
http://pastebin.com/m5c9478a1
Comment 28 written by Kris on 29 July 2009, at 7:19 PM
Comment 29 written by Michael Appenzellar on 11 September 2009, at 9:30 PM
Comment 30 written by Raymond Camden on 12 September 2009, at 8:32 AM
As for the button - not sure. I'd have to see it to help.
Comment 31 written by Michael Appenzellar on 12 September 2009, at 10:23 PM
Comment 32 written by Michael Appenzellar on 12 September 2009, at 10:23 PM
Comment 33 written by Raymond Camden on 12 September 2009, at 10:24 PM
Comment 34 written by Shaun W. on 15 September 2009, at 10:37 AM
Comment 35 written by Shaun W. on 15 September 2009, at 11:23 PM
Comment 36 written by Raymond Camden on 16 September 2009, at 6:55 AM
Comment 37 written by Shaun W. on 16 September 2009, at 8:45 PM
Comment 38 written by Michael Appenzellar on 17 September 2009, at 2:49 PM
Comment 39 written by Raymond Camden on 17 September 2009, at 2:51 PM
http://docs.jquery.com/Events/live
[Add Comment] [Subscribe to Comments]