source: text-formbuilder/trunk/lib/Text/FormBuilder.pm @ 12

Last change on this file since 12 was 12, checked in by peter, 20 years ago

added a method to write a "pre-compiled" module that encapsulates setting up a form
updated docs
updated version number

File size: 9.8 KB
Line 
1package Text::FormBuilder;
2
3use strict;
4use warnings;
5
6our $VERSION = '0.03';
7
8use Text::FormBuilder::Parser;
9use CGI::FormBuilder;
10
11sub new {
12    my $invocant = shift;
13    my $class = ref $invocant || $invocant;
14    my $self = {
15        parser => Text::FormBuilder::Parser->new,
16    };
17    return bless $self, $class;
18}
19
20sub parse {
21    my ($self, $filename) = @_;
22   
23    # so it can be called as a class method
24    $self = $self->new unless ref $self;
25   
26    local $/ = undef;
27    open SRC, "< $filename";
28    my $src = <SRC>;
29    close SRC;
30   
31    return $self->parse_text($src);
32}
33
34sub parse_text {
35    my ($self, $src) = @_;
36    # so it can be called as a class method
37    $self = $self->new unless ref $self;
38    $self->{form_spec} = $self->{parser}->form_spec($src);
39    return $self;
40}
41
42sub build {
43    my ($self, %options) = @_;
44   
45    $self->{build_options} = { %options };
46   
47    # our custom %options:
48    # form_only: use only the form part of the template
49    my $form_only = $options{form_only};
50    delete $options{form_only};
51   
52    # substitute in custom pattern definitions for field validation
53    if (my %patterns = %{ $self->{form_spec}{patterns} }) {
54        foreach (@{ $self->{form_spec}{fields} }) {
55            if ($$_{validate} and exists $patterns{$$_{validate}}) {
56                $$_{validate} = $patterns{$$_{validate}};
57            }
58        }
59    }
60   
61    # remove extraneous undefined values
62    for my $field (@{ $self->{form_spec}{fields} }) {
63        defined $$field{$_} or delete $$field{$_} foreach keys %{ $field };
64    }
65   
66    # so we don't get all fields required
67    foreach (@{ $self->{form_spec}{fields} }) {
68        delete $$_{validate} unless $$_{validate};
69    }
70   
71    # substitute in list names
72    if (my %lists = %{ $self->{form_spec}{lists} }) {
73        foreach (@{ $self->{form_spec}{fields} }) {
74            next unless $$_{list};
75           
76            $$_{list} =~ s/^\@//;   # strip leading @ from list var name
77           
78            # a hack so we don't get screwy reference errors
79            if (exists $lists{$$_{list}}) {
80                my @list;
81                push @list, { %$_ } foreach @{ $lists{$$_{list}} };
82                $$_{options} = \@list;
83            }
84        } continue {
85            delete $$_{list};
86        }
87    }
88
89##     #TODO: option switch for this
90##     #TODO: goes with CGI::FormBuilder 2.13
91##     foreach (@{ $self->{form_spec}{fields} }) {
92##         $$_{ulist} = 1 if $$_{type} and $$_{type} =~ /checkbox|radio/ and @{ $$_{options} } >= 3;
93##     }
94   
95    $self->{form} = CGI::FormBuilder->new(
96        method => 'GET',
97        javascript => 0,
98        keepextras => 1,
99        title => $self->{form_spec}{title},
100        fields => [ map { $$_{name} } @{ $self->{form_spec}{fields} } ],
101        template => {
102            type => 'Text',
103            engine => {
104                TYPE       => 'STRING',
105                SOURCE     => $form_only ? $self->_form_template : $self->_template,
106                DELIMITERS => [ qw(<% %>) ],
107            },
108            data => {
109                headings => $self->{form_spec}{headings},
110                author   => $self->{form_spec}{author},
111            },
112        },
113        %options,
114    );
115    $self->{form}->field(%{ $_ }) foreach @{ $self->{form_spec}{fields} };
116   
117    return $self;
118}
119
120sub write {
121    my ($self, $outfile) = @_;
122    if ($outfile) {
123        open FORM, "> $outfile";
124        print FORM $self->form->render;
125        close FORM;
126    } else {
127        print $self->form->render;
128    }
129}
130
131sub write_module {
132    my ($self, $package) = @_;
133
134    use Data::Dumper;
135    # don't dump $VARn names
136    $Data::Dumper::Terse = 1;
137   
138    my $title = $self->{form_spec}{title} || '';
139    my $author = $self->{form_spec}{author} || '';
140    my $headings = Data::Dumper->Dump([$self->{form_spec}{headings}],['headings']);
141    my $fields = Data::Dumper->Dump([ [ map { $$_{name} } @{ $self->{form_spec}{fields} } ] ],['fields']);
142   
143    my $source = $self->{build_options}{form_only} ? $self->_form_template : $self->_template;
144   
145    my $options = keys %{ $self->{build_options} } > 0 ? Data::Dumper->Dump([$self->{build_options}],['*options']) : '';
146   
147    my $field_setup = join(
148        "\n", 
149        map { '$cgi_form->field' . Data::Dumper->Dump([$_],['*field']) . ';' } @{ $self->{form_spec}{fields} }
150    );
151   
152    my $module = <<END;
153package $package;
154use strict;
155use warnings;
156
157use CGI::FormBuilder;
158
159sub form {
160    my \$cgi = shift;
161    my \$cgi_form = CGI::FormBuilder->new(
162        method => 'GET',
163        params => \$cgi,
164        javascript => 0,
165        keepextras => 1,
166        title => q[$title],
167        fields => $fields,
168        template => {
169            type => 'Text',
170            engine => {
171                TYPE       => 'STRING',
172                SOURCE     => q[$source],
173                DELIMITERS => [ qw(<% %>) ],
174            },
175            data => {
176                headings => $headings,
177                author   => q[$author],
178            },
179        },
180        $options
181    );
182   
183    $field_setup
184   
185    return \$cgi_form;
186}
187
188# module return
1891;
190END
191    my $outfile = (split(/::/, $package))[-1] . '.pm';
192   
193    if ($outfile) {
194        open FORM, "> $outfile";
195        print FORM $module;
196        close FORM;
197    } else {
198        print $module;
199    }
200}
201
202sub form { shift->{form} }
203
204sub _form_template {
205q[<p id="instructions">(Required fields are marked in <strong>bold</strong>.)</p>
206<% $start %>
207<table>
208<% my $i; foreach(@fields) {
209    $OUT .= qq[  <tr><th class="sectionhead" colspan="2"><h2>$headings[$i]</h2></th></tr>\n] if $headings[$i];
210    $OUT .= qq[  <tr>];
211    $OUT .= '<th class="label">' . ($$_{required} ? qq[<strong class="required">$$_{label}:</strong>] : "$$_{label}:") . '</th>';
212    $OUT .= qq[<td>$$_{field} $$_{comment}</td></tr>\n];
213    $i++;
214} %>
215  <tr><th></th><td style="padding-top: 1em;"><% $submit %></td></tr>
216</table>
217<% $end %>
218];
219}
220
221sub _template {
222    my $self = shift;
223q[<html>
224<head>
225  <title><% $title %><% $author ? ' - ' . ucfirst $author : '' %></title>
226  <style type="text/css">
227    #author, #footer { font-style: italic; }
228    th { text-align: left; }
229    th h2 { padding: .125em .5em; background: #eee; }
230    th.label { font-weight: normal; text-align: right; vertical-align: top; }
231  </style>
232</head>
233<body>
234
235<h1><% $title %></h1>
236<% $author ? qq[<p id="author">Created by $author</p>] : '' %>
237] . $self->_form_template . q[
238<hr />
239<div id="footer">
240  <p id="creator">Made with <a href="http://formbuilder.org/">CGI::FormBuilder</a> version <% $CGI::FormBuilder::VERSION %>.</p>
241</div>
242</body>
243</html>
244];
245}
246
247sub dump { 
248    eval "use YAML;";
249    unless ($@) {
250        print YAML::Dump(shift->{form_spec});
251    } else {
252        warn "Can't dump form spec structure: $@";
253    }
254}
255
256
257# module return
2581;
259
260=head1 NAME
261
262Text::FormBuilder - Parser for a minilanguage describing web forms
263
264=head1 SYNOPSIS
265
266    my $parser = Text::FormBuilder->new;
267    $parser->parse($src_file);
268   
269    # returns a new CGI::FormBuilder object with the fields
270    # from the input form spec
271    my $form = $parser->form;
272
273=head1 DESCRIPTION
274
275=head2 new
276
277=head2 parse
278
279    $parser->parse($src_file);
280   
281    # or as a class method
282    my $parser = Txt::FormBuilder->parse($src_file);
283
284=head2 parse_text
285
286=head2 build
287
288    $parser->build(%options);
289
290Builds the CGI::FormBuilder object. Options directly used by C<build> are:
291
292=over
293
294=item form_only
295
296Only uses the form portion of the template, and omits the surrounding html,
297title, author, and the standard footer.
298
299=back
300
301All other options given to C<build> are passed on verbatim to the
302L<CGI::FormBuilder> constructor. Any options given here override the
303defaults that this module uses.
304
305=head2 form
306
307    my $form = $parser->form;
308
309Returns the L<CGI::FormBuilder> object.
310
311=head2 write
312
313    $parser->write($out_file);
314    # or just print to STDOUT
315    $parser->write;
316
317Calls C<render> on the FormBuilder form, and either writes the resulting HTML
318to a file, or to STDOUT if no filename is given.
319
320=head2 write_module
321
322    $parser->write_module($package);
323
324Takes a package name, and writes out a new module that can be used by your
325CGI script to render the form. This way, you only need CGI::FormBuilder on
326your server, and you don't have to parse the form spec each time you want
327to display your form.
328
329    #!/usr/bin/perl -w
330    use strict;
331   
332    # your CGI script
333   
334    use CGI;
335    use MyForm;
336   
337    my $q = CGI->new;
338    my $form = MyForm::form($q);
339   
340    # do the standard CGI::FormBuilder stuff
341    if ($form->submitted && $form->validate) {
342        # process results
343    } else {
344        print $q->header;
345        print $form->render;
346    }
347   
348
349=head2 dump
350
351Uses L<YAML> to print out a human-readable representaiton of the parsed
352form spec.
353
354=head1 LANGUAGE
355
356    field_name[size]|descriptive label[hint]:type=default{option1[display string],option2[display string],...}//validate
357   
358    !title ...
359   
360    !author ...
361   
362    !pattern name /regular expression/
363    !list name {
364        option1[display string],
365        option2[display string],
366        ...
367    }
368   
369    !list name &{ CODE }
370   
371    !head ...
372
373=head2 Directives
374
375=over
376
377=item C<!pattern>
378
379Defines a validation pattern.
380
381=item C<!list>
382
383Defines a list for use in a C<radio>, C<checkbox>, or C<select> field.
384
385=item C<!title>
386
387=item C<!author>
388
389=item C<!head>
390
391Inserts a heading between two fields. There can only be one heading between
392any two fields; the parser will warn you if you try to put two headings right
393next to each other.
394
395=back
396
397=head2 Fields
398
399Form fields are each described on a single line.
400
401If you have a list of options that is too long to fit comfortably on one line,
402consider using the C<!list> directive.
403
404=head2 Comments
405
406    # comment ...
407
408Any line beginning with a C<#> is considered a comment.
409
410=head1 TODO
411
412=head2 Langauge
413
414Directive for a descriptive or explanatory paragraph about the form
415
416=head1 SEE ALSO
417
418L<CGI::FormBuilder>
419
420=cut
Note: See TracBrowser for help on using the repository browser.