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

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

added invalid field class CSS hook
moved descirption from full tempalte to form template

File size: 10.2 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    foreach (@{ $self->{form_spec}{fields} }) {
90        $$_{ulist} = 1 if defined $$_{options} and @{ $$_{options} } >= 3;
91    }
92   
93    $self->{form} = CGI::FormBuilder->new(
94        method => 'GET',
95        javascript => 0,
96        keepextras => 1,
97        title => $self->{form_spec}{title},
98        fields => [ map { $$_{name} } @{ $self->{form_spec}{fields} } ],
99        template => {
100            type => 'Text',
101            engine => {
102                TYPE       => 'STRING',
103                SOURCE     => $form_only ? $self->_form_template : $self->_template,
104                DELIMITERS => [ qw(<% %>) ],
105            },
106            data => {
107                headings    => $self->{form_spec}{headings},
108                author      => $self->{form_spec}{author},
109                description => $self->{form_spec}{description},
110            },
111        },
112        %options,
113    );
114    $self->{form}->field(%{ $_ }) foreach @{ $self->{form_spec}{fields} };
115   
116    return $self;
117}
118
119sub write {
120    my ($self, $outfile) = @_;
121    if ($outfile) {
122        open FORM, "> $outfile";
123        print FORM $self->form->render;
124        close FORM;
125    } else {
126        print $self->form->render;
127    }
128}
129
130sub write_module {
131    my ($self, $package) = @_;
132
133    use Data::Dumper;
134    # don't dump $VARn names
135    $Data::Dumper::Terse = 1;
136   
137    my $title = $self->{form_spec}{title} || '';
138    my $author = $self->{form_spec}{author} || '';
139    my $description = $self->{form_spec}{description} || '';
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 get_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                description => q[$description],
179            },
180        },
181        $options
182    );
183   
184    $field_setup
185   
186    return \$cgi_form;
187}
188
189# module return
1901;
191END
192    my $outfile = (split(/::/, $package))[-1] . '.pm';
193   
194    if ($outfile) {
195        open FORM, "> $outfile";
196        print FORM $module;
197        close FORM;
198    } else {
199        print $module;
200    }
201}
202
203sub form { shift->{form} }
204
205sub _form_template {
206q[<% $description ? qq[<p id="description">$description</p>] : '' %>
207<% (grep { $_->{required} } @fields) ? qq[<p id="instructions">(Required fields are marked in <strong>bold</strong>.)</p>] : '' %>
208<% $start %>
209<table>
210<% my $i; foreach(@fields) {
211    $OUT .= qq[  <tr><th class="sectionhead" colspan="2"><h2>$headings[$i]</h2></th></tr>\n] if $headings[$i];
212    $OUT .= $$_{invalid} ? qq[  <tr class="invalid">] : qq[  <tr>];
213    $OUT .= '<th class="label">' . ($$_{required} ? qq[<strong class="required">$$_{label}:</strong>] : "$$_{label}:") . '</th>';
214    if ($$_{invalid}) {
215        $OUT .= qq[<td>$$_{field} $$_{comment} Missing or invalid value.</td></tr>\n];
216    } else {
217        $OUT .= qq[<td>$$_{field} $$_{comment}</td></tr>\n];
218    }
219    $i++;
220} %>
221  <tr><th></th><td style="padding-top: 1em;"><% $submit %></td></tr>
222</table>
223<% $end %>
224];
225}
226
227sub _template {
228    my $self = shift;
229q[<html>
230<head>
231  <title><% $title %><% $author ? ' - ' . ucfirst $author : '' %></title>
232  <style type="text/css">
233    #author, #footer { font-style: italic; }
234    th { text-align: left; }
235    th h2 { padding: .125em .5em; background: #eee; }
236    th.label { font-weight: normal; text-align: right; vertical-align: top; }
237    td ul { list-style: none; padding-left: 0; margin-left: 0; }
238  </style>
239</head>
240<body>
241
242<h1><% $title %></h1>
243<% $author ? qq[<p id="author">Created by $author</p>] : '' %>
244] . $self->_form_template . q[
245<hr />
246<div id="footer">
247  <p id="creator">Made with <a href="http://formbuilder.org/">CGI::FormBuilder</a> version <% $CGI::FormBuilder::VERSION %>.</p>
248</div>
249</body>
250</html>
251];
252}
253
254sub dump { 
255    eval "use YAML;";
256    unless ($@) {
257        print YAML::Dump(shift->{form_spec});
258    } else {
259        warn "Can't dump form spec structure: $@";
260    }
261}
262
263
264# module return
2651;
266
267=head1 NAME
268
269Text::FormBuilder - Parser for a minilanguage describing web forms
270
271=head1 SYNOPSIS
272
273    my $parser = Text::FormBuilder->new;
274    $parser->parse($src_file);
275   
276    # returns a new CGI::FormBuilder object with the fields
277    # from the input form spec
278    my $form = $parser->form;
279
280=head1 DESCRIPTION
281
282=head2 new
283
284=head2 parse
285
286    $parser->parse($src_file);
287   
288    # or as a class method
289    my $parser = Txt::FormBuilder->parse($src_file);
290
291=head2 parse_text
292
293=head2 build
294
295    $parser->build(%options);
296
297Builds the CGI::FormBuilder object. Options directly used by C<build> are:
298
299=over
300
301=item form_only
302
303Only uses the form portion of the template, and omits the surrounding html,
304title, author, and the standard footer.
305
306=back
307
308All other options given to C<build> are passed on verbatim to the
309L<CGI::FormBuilder> constructor. Any options given here override the
310defaults that this module uses.
311
312=head2 form
313
314    my $form = $parser->form;
315
316Returns the L<CGI::FormBuilder> object.
317
318=head2 write
319
320    $parser->write($out_file);
321    # or just print to STDOUT
322    $parser->write;
323
324Calls C<render> on the FormBuilder form, and either writes the resulting HTML
325to a file, or to STDOUT if no filename is given.
326
327=head2 write_module
328
329    $parser->write_module($package);
330
331Takes a package name, and writes out a new module that can be used by your
332CGI script to render the form. This way, you only need CGI::FormBuilder on
333your server, and you don't have to parse the form spec each time you want
334to display your form.
335
336    #!/usr/bin/perl -w
337    use strict;
338   
339    # your CGI script
340   
341    use CGI;
342    use MyForm;
343   
344    my $q = CGI->new;
345    my $form = MyForm::form($q);
346   
347    # do the standard CGI::FormBuilder stuff
348    if ($form->submitted && $form->validate) {
349        # process results
350    } else {
351        print $q->header;
352        print $form->render;
353    }
354   
355
356=head2 dump
357
358Uses L<YAML> to print out a human-readable representaiton of the parsed
359form spec.
360
361=head1 LANGUAGE
362
363    field_name[size]|descriptive label[hint]:type=default{option1[display string],option2[display string],...}//validate
364   
365    !title ...
366   
367    !author ...
368   
369    !pattern name /regular expression/
370    !list name {
371        option1[display string],
372        option2[display string],
373        ...
374    }
375   
376    !list name &{ CODE }
377   
378    !head ...
379
380=head2 Directives
381
382=over
383
384=item C<!pattern>
385
386Defines a validation pattern.
387
388=item C<!list>
389
390Defines a list for use in a C<radio>, C<checkbox>, or C<select> field.
391
392=item C<!title>
393
394=item C<!author>
395
396=item C<!head>
397
398Inserts a heading between two fields. There can only be one heading between
399any two fields; the parser will warn you if you try to put two headings right
400next to each other.
401
402=back
403
404=head2 Fields
405
406Form fields are each described on a single line.
407
408If you have a list of options that is too long to fit comfortably on one line,
409consider using the C<!list> directive.
410
411=head2 Comments
412
413    # comment ...
414
415Any line beginning with a C<#> is considered a comment.
416
417=head1 TODO
418
419=head2 Langauge
420
421Directive for a descriptive or explanatory paragraph about the form
422
423=head1 SEE ALSO
424
425L<CGI::FormBuilder>
426
427=cut
Note: See TracBrowser for help on using the repository browser.