HuckinFappy has asked for the wisdom of the Perl Monks concerning the following question:

Oh wise and patient brethren,

I haven't had to do any HTML/CGI development in many a moon (oh, 10 years or so). I have a new project which gave me an opportunity to try some new things, so I'm playing with Catalyst and Template Toolkit. Very cool stuff I must say.

Let's assume for the sake of argument that there's a form where users can enter lists of books and authors. There's no limit to the number they can enter, and there's no way for me to know how many they will want to enter. So what I want it a way to dynamically add more fields with a button(?)

My head was firmly mired in Catalyst/TT, so my first effort was:

sub form_create :Local { my ( $self, $c ) = @_; $c->stash->{entry_fields} = 2; $c->stash->{template} = 'requests/new_request.tt2'; }
Where in new_request.tt2 I have:
[% printField = 1; WHILE printField <= entry_fields %] <tr> <td>Book:</td> <td><input type="text" name="book"</td> <td>By:</td> <td><input type="text"name="author"></td> </tr> [% printField = printField + 1; END; %]
That worked fine, and allowed me to control the number of fields in my form_create() routine. What I wanted to do then was add a way to the form to increment the entry_fields and regenerate the form. When I got to that point, I realized I didn't have a clue what to do

Some folks on #catalyst suggested I should use javascript, but I know absolutely nothing about JS, and can't fathomn where to start (and the caution "Prepare for Hell" someone offered me didn't help)

So maybe I'm thinking orthogonal to the problem, but I'm not sure how to proceed...any pushes in the right direction are appreciated.


PS: Yes, I'm aware the answer here may have nothing to do with perl, Catalyst, or TT, so try and be gentle please.

Replies are listed 'Best First'.
Re: Adding fields to a form using Catalyst/TT
by chargrill (Parson) on Oct 13, 2006 at 18:30 UTC


    I've recently been trying to do something extremely similar, only instead of it being a work project, it was purely to learn how to get Catalyst to play nicely with AJAX-y type stuff.

    Against the advice of some of the folks in #catalyst, I decided to use HTML::Prototype. Here are some of the changes I made to dynamically add a single form field using ajax and Catalyst. Note, I set up an observed field (text field) that, on change, would fire off the appropriate xhttpd request, as this was only a proof of concept for me. So, it's not 100% what you're looking to do, but may give you a head start.

    First, in my lib/

    use Catalyst qw/-Debug ConfigLoader Static::Simple Prototype /;

    This loads the Catalyst::Plugin::Prototype plugin. Then, in my file, I had to have the following:

    <head> [% c.prototype.define_javascript_functions %] </head>

    ... which generates the javascript used elsewhere in the HTML page. Then, I created a new .tt file for each form field I wanted to insert:

    <table border="1" style="border: 1px;"> <tr> <td width="100">[% task_name %]</td> <td width="150"><input type="text" size="30" name="[% task_inpu +t_name %]" id="[% task_input_id %]"></td> <td width="250"><div id="[% task_output_name %]"></div></td> </tr> [% url = base _ 'application/newrow/' _ page.title %] [% c.prototype.observe_field( "$task_input_id", { url => url, with => "'input='+value", update => "$task_output_name" } ) %] </table>

    Here, the div id "task_output_name" is what gets updated for this particular ajax call. And then in the .tt file that includes the .tt file above:

    [% task_name = 'Project Name' %] [% task_input_name = 'project' %] [% task_output_name = 'project' %] [% task_input_id = 'project_id' %] [% INCLUDE %]

    Further, I had to make sure I had the "newrow" method defined in my lib/application/Controller/ file:

    sub newrow : Local { my( $self, $c ) = @_; my $newrow = buildrow() || 'some default text'; $c->res->output( $newrow ); } sub buildrow : Private { my $html = ''; my $template = Template->new({ CATALYST_VAR => 'c', INCLUDE_PATH => [ workest->path_to( 'root', 'src' ), ], OUTPUT => \$html, }); $template->process( '', { task_name => 'Brand New Task', task_input_name => 'newtask', task_output_name => 'newtask', task_input_id => 'newtask_id', } ); $html = $html || $template->error() || 'template broke'; return $html; }

    This is example is taken from my work-in-progess (and as such probably isn't the best way to do things), is greatly simplified, and doesn't do much, but does handle creating the ajax request, using TT to generate some HTML based on a template I already had setup, and inserting the HTML into the <div> tag defined. It also probably won't work for you without some major tweaking, since I only extracted bits of what I've done (hopefully the right ones ;) ) and gone through it without completely checking to make sure the bits I've shown here are 100% what you need :)

    If you get stuck after this, try going to this url, which is where I got started with it.

    Happy catalysting,

    s**lil*; $*=join'',sort split q**; s;.*;grr; &&s+(.(.)).+$2$1+; $; = qq-$_-;s,.*,ahc,;$,.=chop for split q,,,reverse;print for($,,$;,$*,$/)
      Against the advice of some of the folks in #catalyst, I decided to use HTML::Prototype.
      do you recall the reasons given against HTML::Prototype? it has been a while (over a year?) since i visited #catalyst and folks were still recommending prototype at the time!

        It's funny, when I explained what I wanted to do to jrockway, he commented that HTML::Protoype was fine. I peeked in the other day while HuckinFappy was asking around, and it seems dojo and mochikit are in favor by the #catalyst folk.

        I believe the specific quotes I saw were something like "prototype == matt's script archive" and "Prototype does the Javascript equivalent of stuffing UNIVERSAL with methods". Note, these are not my thoughts, and I couldn't find any substantiation for these assertions.

        But, it seems to work OK for the things that I have so far found that I want to do, so naturally YMMV.

        Updated: Fixed links (Thanks, Hue-Bond)

        s**lil*; $*=join'',sort split q**; s;.*;grr; &&s+(.(.)).+$2$1+; $; = qq-$_-;s,.*,ahc,;$,.=chop for split q,,,reverse;print for($,,$;,$*,$/)
      Just a followup for closure sake.

      After much struggling and gnashing of teeth, I'm getting the hang of this! Thanks again chargrill for the pointers in the right direction. I also found much help here, and here.

      To infinity and beyond!


Re: Adding fields to a form using Catalyst/TT
by philcrow (Priest) on Oct 13, 2006 at 18:40 UTC
    Javascript is a very nice language which does not deserve its even worse reputation than our favorite language. I was in your position about 6 months ago, so I read a book on the language (Java Script the Complete Reference 2ed., I heard a new O'Reilly title was coming soon, but it wasn't soon enough) and discovered that javascript is very flexible in surprisingly perlish ways (although the syntax looks different than ours at first glance).

    A previous poster mentioned libraries, which are great. But, understanding comes to me from rolling a few things myself first. Otherwise, I can't conceive of what the library is up to.

    Here is a little sample pulled from recent code (Bigtop's tentmaker editor to be specific). I've pruned it a little bit to make it clearer and more general.

    function add_to_div( div_to_change, new_div_text ) { var div_area = document.getElementById( div_to_change ); var new_node = document.createElement( 'div' ); new_node.innerHTML = new_div_text; div_area.appendChild( new_node ); }
    Now this is not fancy. Library code would be nicer in a number of ways (notably in cross browser support). But this shows how easy it is to augment an existing document object model (DOM).

    Other than javascript, you can only make round trips to the server, which is <pun_warning /> anything but refreshing.


Re: Adding fields to a form using Catalyst/TT
by jhourcle (Prior) on Oct 13, 2006 at 17:54 UTC

    I'd agree -- use JavaScript. Anything else is going to require a round-trip to the webserver.

    There are plenty of javascript toolkits these days to make things easier on you. (dojo, prototype, Google Web Toolkit, etc.)

    If you want an example, see any of the insertion routines for Prototype