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

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

added a !description directive
enabled showing checkboxes/radio groups as ulists when there are 3 or more options

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