File Coverage

blib/lib/HTML/Formulate.pm
Criterion Covered Total %
statement 397 413 96.1
branch 216 264 81.8
condition 138 191 72.2
subroutine 30 30 100.0
pod 3 24 12.5
total 784 922 85.0


line stmt bran cond sub pod time code
1             package HTML::Formulate;
2              
3 16     16   542034 use 5.005;
  16         62  
  16         726  
4 16     16   21117 use HTML::Tabulate 0.39;
  16         319764  
  16         1126  
5 16     16   206 use Carp;
  16         38  
  16         1270  
6 16     16   93 use strict;
  16         31  
  16         873  
7              
8             require Exporter;
9 16     16   87 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  16         30  
  16         133987  
10             @ISA = qw(HTML::Tabulate Exporter);
11             @EXPORT = qw(&render);
12             @EXPORT_OK = qw(&render);
13             %EXPORT_TAGS = ();
14              
15             $VERSION = '0.19';
16              
17             # Additional valid arguments, fields, and field attributes to those of
18             # HTML::Tabulate
19             my %VALID_ARG = (
20             # form: form tag attribute/value hash, or boolean scalar
21             form => 'HASH/SCALAR',
22             # formtype: form/table
23             formtype => 'SCALAR',
24             # primkey: primary key field, or list of primary key fields (for composites)
25             # primkey => 'SCALAR/ARRAY',
26             # submit: list of submit/button/reset elements for form
27             submit => 'SCALAR/ARRAY',
28             # submit_location: location of submit elements - top/bottom/both, default: bottom
29             submit_location => 'SCALAR',
30             # hidden: list of fields to render as hiddens, or hashref of field/value
31             # pairs; default: none
32             hidden => 'ARRAY/HASH',
33             # required: list of required/mandatory fields, or tokens 'ALL' or 'NONE'
34             required => 'ARRAY/SCALAR',
35             # use_name_as_id: add 'name' as 'id' field to input-type fields if none set
36             use_name_as_id => 'SCALAR',
37             # errors: hashref of field => (scalar/array of) validation-error-messages
38             errors => 'HASH',
39             # errors_where: where to display validation error messages:
40             # top: above form table (default)
41             # column: within form table, in a third table column
42             errors_where => 'SCALAR',
43             # errors_format: subroutine to format/render 'top' style error messages
44             errors_format => 'SCALAR/CODE',
45             );
46             my %VALID_FIELDS = (
47             # primary key defaults (deprecated?)
48             # -primkey => 'HASH',
49             # select defaults
50             '-select' => 'HASH',
51             # submit button defaults
52             -submit => 'HASH',
53             # required fields defaults
54             -required => 'HASH',
55             # error field defaults
56             -errors => 'HASH',
57             );
58             my %FIELD_ATTR = (
59             # type: how this field is rendered on the form (roughly an type)
60             # type => [ qw(text textarea password select hidden display static omit)],
61             type => [ qw(text textarea password file image button select checkbox radio hidden display static omit)],
62             # datatype: the validation datatype for this field (deprecated?)
63             # datatype => 'SCALAR/ARRAY',
64             # required: boolean
65             required => 'SCALAR',
66             # values: a list of possible values (scalars) for selects or radio buttons
67             'values' => 'ARRAY/CODE',
68             # vlabels: a list (or hashref keyed by values entries) of labels for use
69             # with selects or radio buttons
70             vlabels => 'ARRAY/HASH/CODE',
71             );
72             # Attributes applicable to the various input-type fields
73             my %TEXT_ATTR = map { $_ => 1 } qw(accesskey disabled id maxlength name notab onblur onchange onclick onfocus onselect readonly selected size tabindex taborder value vlabel);
74             my %INPUT_ATTR = map { $_ => 1 } qw(accesskey checked disabled id name notab onblur onchange onclick onfocus onselect readonly selected size tabindex taborder value vlabel);
75             my %SELECT_ATTR = map { $_ => 1 } qw(disabled id multiple name onblur onchange onfocus size tabindex vlabel);
76             my %TEXTAREA_ATTR = map { $_ => 1 } qw(accesskey cols disabled id name onblur onchange onfocus onselect readonly rows tabindex vlabel wrap);
77             my %TABLE_ATTR = map { $_ => 1 } qw(tr th td);
78             my %EMPTY_TAGS = map { $_ => 1 } qw(input br);
79              
80             sub get_valid_arg
81             {
82 119     119 0 3983 my $self = shift;
83 119         513 my %arg = $self->SUPER::get_valid_arg();
84 119 50       6730 return wantarray ? ( %arg, %VALID_ARG ) : { %arg, %VALID_ARG };
85             }
86             sub get_valid_fields
87             {
88 117     117 0 7520 my $self = shift;
89 117         698 my %arg = $self->SUPER::get_valid_fields();
90 117 50       2515 return wantarray ? ( %arg, %VALID_FIELDS ) : { %arg, %VALID_FIELDS };
91             }
92             sub get_field_attributes
93             {
94 25     25 0 751 my $self = shift;
95 25         172 my %attr = $self->SUPER::get_field_attributes();
96 25         1173 @attr{ keys %FIELD_ATTR } = values %FIELD_ATTR;
97 25 50       235 return wantarray ? %attr : \%attr;
98             }
99              
100              
101             # -------------------------------------------------------------------------
102             # Merge in form base defaults
103             #
104             sub init
105             {
106 25     25 0 2447709 my $self = shift;
107 25         61 my $defn = shift;
108              
109             # Munge form => 1 to form => {} for cleaner merging
110 25 50 33     165 $defn->{form} = {} if $defn->{form} && ! ref $defn->{form};
111              
112             $defn = $self->merge({
113             form => { method => 'post' },
114             formtype => 'form',
115             table => { cellpadding => '2' },
116             style => 'across',
117             labels => 1,
118             hidden => {},
119             xhtml => 1,
120             use_name_as_id => 0,
121             null => ' ',
122             errors_where => 'top',
123             errors_format => sub {
124 4     4   30 return qq(

\n) .

125             join(qq(
\n), @_) .
126             qq(\n

\n);
127             },
128             # errors_format => sub {
129             # return qq(

\n) .

130             # join(qq(
\n), @_) .
131             # qq(\n

\n);
132             # },
133 25 100 66     1076 field_attr => {
134             -select => { size => undef },
135             -submit => { maxlength => undef, size => undef },
136             -required => {
137             th => { style => 'color:blue' },
138             label_format => '%s'
139             },
140             # -required => { label_format => '%s [*]' },
141             -errors => {
142             th => { style => 'color:red' },
143             label_format => '%s',
144             # td_error => { style => 'color:red;font-weight:bold' },
145             td_error => { class => 'error' },
146             },
147             },
148             }, $defn) unless $defn->{formtype} && $defn->{formtype} eq 'table';
149              
150 25         11391 return $self->SUPER::init($defn);
151             }
152              
153             #
154             # Further split tx_attr into tx_attr and input_attr
155             sub cell_split_out_tx_attr
156             {
157 397     397 0 35894 my $self = shift;
158 397         953 my ($field) = @_;
159              
160 397         1501 $self->SUPER::cell_split_out_tx_attr(@_);
161              
162 397         23880 for my $attr (qw(label_attr tfoot_attr data_attr)) {
163 1191   100     6160 my $type = $self->{defn_t}->{$attr}->{$field}->{type} || '';
164 1191         3739 $self->{defn_t}->{$attr}->{$field}->{input_attr} = {};
165              
166 1191         1470 for (keys %{ $self->{defn_t}->{$attr}->{$field}->{tx_attr} }) {
  1191         4897  
167             # Attributes like input_class will be mapped as input.class
168 457 100 100     2808 if (m/^input_/) {
    100 66        
169 1         4 my $val = delete $self->{defn_t}->{$attr}->{$field}->{tx_attr}->{$_};
170 1         3 s/^input_//;
171 1         4 $self->{defn_t}->{$attr}->{$field}->{input_attr}->{$_} = $val;
172             }
173             elsif ($TEXT_ATTR{$_} || $TEXTAREA_ATTR{$_} || $SELECT_ATTR{$_}) {
174 361         1109 my $val = delete $self->{defn_t}->{$attr}->{$field}->{tx_attr}->{$_};
175              
176 361 100 66     1871 if ($type eq 'select') {
    100 66        
    100          
177 20 100       153 $self->{defn_t}->{$attr}->{$field}->{input_attr}->{$_} = $val
178             if $SELECT_ATTR{$_};
179             }
180             elsif ($type eq 'textarea') {
181 3 50       16 $self->{defn_t}->{$attr}->{$field}->{input_attr}->{$_} = $val
182             if $TEXTAREA_ATTR{$_};
183             }
184             elsif (! $type ||
185             $type eq 'text' ||
186             $type eq 'password') {
187 266 50       2294 $self->{defn_t}->{$attr}->{$field}->{input_attr}->{$_} = $val
188             if $TEXT_ATTR{$_};
189             }
190             else {
191 72 100       463 $self->{defn_t}->{$attr}->{$field}->{input_attr}->{$_} = $val
192             if $INPUT_ATTR{$_};
193             }
194             }
195             }
196             }
197             }
198              
199             # One-off or dataset-specific presentation definition munging
200             sub prerender_munge
201             {
202 88     88 0 33884 my $self = shift;
203              
204             # Call SUPER version first
205 88         1295 $self->SUPER::prerender_munge(@_);
206              
207 88         5001 my $defn_t = $self->{defn_t};
208 88 100       478 if ($defn_t->{formtype} eq 'table') {
209 7         13 delete $defn_t->{form};
210 7         19 return;
211             }
212              
213             # Map top-level 'hidden' arrayref/hashref into fields
214 81 100       244 if (ref $defn_t->{hidden} eq 'HASH') {
    50          
215 80         117 for my $hidden (keys %{$defn_t->{hidden}}) {
  80         305  
216 5   50     20 $defn_t->{field_attr}->{$hidden} ||= {};
217 5         10 $defn_t->{field_attr}->{$hidden}->{type} = 'hidden';
218 4         10 push @{$defn_t->{fields}}, $hidden
  5         66  
219 5 100       6 unless grep /^$hidden$/, @{$defn_t->{fields}};
220             }
221             }
222             elsif (ref $defn_t->{hidden} eq 'ARRAY') {
223 1         1 for my $hidden (@{$defn_t->{hidden}}) {
  1         3  
224 4   100     17 $defn_t->{field_attr}->{$hidden} ||= {};
225 4         10 $defn_t->{field_attr}->{$hidden}->{type} = 'hidden';
226 3         7 push @{$defn_t->{fields}}, $hidden
  4         55  
227 4 100       4 unless grep /^$hidden$/, @{$defn_t->{fields}};
228             }
229             # Reset to hashref
230 1         3 $defn_t->{hidden} = {};
231             }
232             # Map top-level 'required' array into fields
233 81         192 my $required = $defn_t->{required};
234 81 100 100     630 if ($required && ! ref $required && $required =~ m/^(ALL|NONE)$/) {
    100 100        
      100        
235 7 100 33     53 if ($required eq 'NONE') {
    50          
236 1         3 $defn_t->{required} = $required = [];
237             }
238             elsif ($defn_t->{fields} && ref $defn_t->{fields} eq 'ARRAY') {
239 6         10 $defn_t->{required} = $required = [ @{$defn_t->{fields}} ];
  6         24  
240             }
241             }
242             elsif ($required && ! ref $required) {
243 1         3 $defn_t->{required} = $required = [ $required ];
244             }
245 81 100 66     298 if ($required && ref $required eq 'ARRAY') {
246 14         37 for (@$required) {
247 34   100     257 $defn_t->{field_attr}->{$_} ||= {};
248 34         153 $defn_t->{field_attr}->{$_}->{required} = 1;
249             }
250             }
251              
252             # Add default submit if fields
253 81 100 100     655 $defn_t->{submit} = [ 'submit' ]
254             if exists $defn_t->{fields} && ! exists $defn_t->{submit};
255              
256             # Reset errors_where unless we have error_messages
257 81         140 my $error_messages = 0;
258 81 100       619 if ($self->{defn_t}->{errors}) {
259 12         20 for (keys %{$self->{defn_t}->{errors}}) {
  12         44  
260 11 50       54 $error_messages = 1, last
261             if $self->{defn_t}->{errors}->{$_} ne '';
262             }
263 12 50 66     115 $self->{defn_t}->{errors_where} = 'column'
264             if $error_messages &&
265             $self->{defn_t}->{errors_where} !~ m/^(column|top)$/;
266             }
267 81 100       1097 $self->{defn_t}->{errors_where} = '' unless $error_messages;
268              
269             # Default primkey to first field if not set
270             # $defn->{primkey} = $defn->{fields}->[0]
271             # if ! $defn->{primkey} &&
272             # $defn->{fields} && ref $defn->{fields} eq 'ARRAY';
273              
274             # Default primkey type to 'static' if not set
275             # my $primkey = $defn->{primkey};
276             # if ($primkey) {
277             # $defn->{field_attr}->{$primkey} ||= {};
278             # $defn->{field_attr}->{$primkey}->{type} = 'static'
279             # if $primkey && ! $defn->{field_attr}->{$primkey}->{type};
280             # }
281             }
282              
283              
284             # -------------------------------------------------------------------------
285             # Override start_tag to add explicit 'id' fields if use_name_as_id is set
286             #
287             sub start_tag
288             {
289 1899     1899 0 9147 my $self = shift;
290 1899         2305 my $tag = shift;
291 1899         2439 my $attr = shift;
292 1899 100 100     6395 if ($self->{defn_t}->{use_name_as_id} &&
      66        
293             $tag =~ qr/^(input|select|textarea)$/ &&
294             exists $attr->{name}) {
295 31   66     126 $attr->{id} ||= $attr->{name};
296             }
297 1899         6183 return $self->SUPER::start_tag($tag, $attr, @_);
298             }
299              
300             # -------------------------------------------------------------------------
301             # Render cells as appropriate input type etc.
302             #
303             sub cell_content
304             {
305 1007     1007 0 36076 my $self = shift;
306 1007         1727 my ($row, $field, $fattr) = @_;
307 1007   0     2149 $fattr ||= $self->{defn_t}->{field}->{$field} || {};
      33        
308 1007 100 100     2931 $fattr->{type} ||= 'text' if $row;
309              
310             # No special handling required for labels or 'table' forms or composites
311 1007 100 100     6156 if (! defined $row or
      66        
312             $self->{defn_t}->{formtype} eq 'table' or
313             $fattr->{composite}) {
314 523         2010 my ($fvalue, $value) = $self->SUPER::cell_content(@_);
315             # Cache label values for later e.g. error_messages
316 523 100       9295 $self->{defn_t}->{_labels}->{$field} = $value if ! defined $row;
317 523 50       2157 return wantarray ? ($fvalue, $value) : $fvalue;
318             }
319              
320             # Call the parent cell_value to get the data value to use
321 484         1621 my $value = $self->SUPER::cell_value(@_);
322 484 50 33     10208 undef $value
323             if defined $self->{defn_t}->{null} && $value eq $self->{defn_t}->{null};
324 484 100 66     2094 undef $value if defined $value && $value eq '';
325              
326             # Create (etc.) fields
327 484         1030 my $out = '';
328 484 100       1130 my $selected_value = $self->{defn_t}->{xhtml} ? 'selected' : '';
329 484 50 66     2399 delete $fattr->{value}
      66        
330             if defined $self->{defn_t}->{null} && defined $fattr->{value} &&
331             $fattr->{value} eq $self->{defn_t}->{null};
332 484 100 100     3237 if ($fattr->{type} eq 'static' || $fattr->{type} eq 'display') {
    100          
    100          
    50          
    100          
333 106 100       275 if ($fattr->{vlabel}) {
334 2 100       8 if (ref $fattr->{vlabel}) {
335 1 50       6 if (ref $fattr->{vlabel} eq 'CODE') {
336 1         12 $out .= $fattr->{vlabel}->($value, $row, $field);
337             }
338             }
339             else {
340 1         8 $out .= sprintf $fattr->{vlabel}, $value;
341             }
342             }
343             else {
344 104         203 $out .= $value;
345             }
346 106         168 delete $fattr->{vlabel};
347 106 100       628 $out .= $self->start_tag('input',
348             { type => 'hidden', name => $field, value => $value }, 'close')
349             if $fattr->{type} eq 'static';
350             }
351             # Select fields
352             elsif ($fattr->{type} eq 'select') {
353 22         41 my $values = $fattr->{values};
354             # Allow code on values
355 22 100       60 if (ref $values eq 'CODE') {
356 8         24 my @values = $values->($field, $row);
357 8 100 66     78 $values = @values == 1 && ref $values[0] ? $values[0] : \@values;
358             }
359 22 50 33     140 if (ref $values eq 'ARRAY' && @$values) {
360 22         723 $out .= $self->start_tag('select',
361 22         89 { %{$fattr->{input_attr}}, name => $field });
362 22   50     1217 my $vlabels = $fattr->{vlabels} || {};
363             # Iterate over values, creating options
364 22         73 for (my $i = 0; $i <= $#$values; $i++) {
365 120         916 my $v = $values->[$i];
366 120         182 my $oattr = {};
367 120 50       832 $oattr->{value} = $v if defined $v;
368 120 100       209 if (defined $value) {
369             # Multi-values make sense in select contexts
370 20 50       36 if (ref $value eq 'ARRAY') {
371 0 0       0 $oattr->{selected} = $selected_value if grep { $v eq $_ } @$value;
  0         0  
372             } else {
373 20 100       44 $oattr->{selected} = $selected_value if $v eq $value;
374             }
375             }
376 120         245 $out .= $self->start_tag('option', $oattr);
377 120         3443 my $vlabel = '';
378 120 100       314 if (ref $vlabels eq 'CODE') {
379             # Two styles are supported for vlabel subroutines - the sub
380             # can either just return a single label based on the given
381             # value, or the first invocation can return an arrayref or
382             # hashref containing the whole set of labels
383 16         40 my @vlabels = $vlabels->($v, $field, $row);
384 16 100       94 $vlabel = @vlabels == 1 ? $vlabels[0] : \@vlabels;
385             # Replace vlabels if arrayref or hashref returned
386 16 100       42 if (ref $vlabel) {
387 6         7 $vlabels = $vlabel;
388 6         11 $vlabel = '';
389             }
390             }
391 120 100       305 if (ref $vlabels eq 'HASH') {
    100          
392 36         65 $vlabel = $vlabels->{$v};
393             }
394             elsif (ref $vlabels eq 'ARRAY') {
395 74         104 $vlabel = $vlabels->[$i];
396             }
397 120 100       258 $vlabel = $v if ! defined $vlabel;
398              
399 120         143 $out .= $vlabel;
400 120         306 $out .= $self->end_tag('option');
401             }
402 22         181 $out .= $self->end_tag('select');
403             }
404             }
405             # Radio fields
406             elsif ($fattr->{type} eq 'radio') {
407 14         23 my $values = $fattr->{values};
408             # Allow code on values
409 14 100       37 if (ref $values eq 'CODE') {
410 10         33 my @values = $values->($field, $row);
411 10 100 66     101 $values = @values == 1 && ref $values[0] ? $values[0] : \@values;
412             }
413 14 50 33     73 if (ref $values eq 'ARRAY' && @$values) {
414             # $out .= $self->start_tag('select',
415             # { %{$fattr->{input_attr}}, name => $field });
416 14   50     47 my $vlabels = $fattr->{vlabels} || {};
417             # Iterate over values
418 14         24 my @out = ();
419 14         43 for (my $i = 0; $i <= $#$values; $i++) {
420 66         137 my $v = $values->[$i];
421 66         92 my $oattr = {};
422 66 50       199 $oattr->{value} = $v if defined $v;
423 66 50       140 if (defined $value) {
424             # Multi-values make sense in select contexts
425 0 0       0 if (ref $value eq 'ARRAY') {
426 0 0       0 $oattr->{selected} = $selected_value if grep { $v eq $_ } @$value;
  0         0  
427             } else {
428 0 0       0 $oattr->{selected} = $selected_value if $v eq $value;
429             }
430             }
431 66         593 my $input = $self->start_tag('input', {
432 66 100 0     82 %{$fattr->{input_attr}}, name => $field, type => 'radio',
    50          
    50          
433             ($self->{defn_t}->{use_name_as_id} ? (id => "${field}_$i") : ()),
434             (defined $v ? (value => $v) : ()),
435             (defined $value && ! ref $value && defined $v && $v eq $value
436             ? (checked => 'checked')
437             : ()),
438             }, 'close');
439 66         2225 my $vlabel = '';
440 66 100       163 if (ref $vlabels eq 'CODE') {
441             # Two styles are supported for vlabel subroutines - the sub
442             # can either just return a single label based on the given
443             # value, or the first invocation can return an arrayref or
444             # hashref containing the whole set of labels
445 18         56 my @vlabels = $vlabels->($v, $field, $row);
446 18 100       121 $vlabel = @vlabels == 1 ? $vlabels[0] : \@vlabels;
447             # Replace vlabels if arrayref or hashref returned
448 18 100       47 if (ref $vlabel) {
449 8         12 $vlabels = $vlabel;
450 8         13 $vlabel = '';
451             }
452             }
453 66 100       168 if (ref $vlabels eq 'HASH') {
    100          
454 28         52 $vlabel = $vlabels->{$v};
455             }
456             elsif (ref $vlabels eq 'ARRAY') {
457 28         46 $vlabel = $vlabels->[$i];
458             }
459 66 50 33     289 $vlabel = $v if ! defined $vlabel or $vlabel eq '';
460              
461             # TODO: need a way of controlling the format used here
462 66         313 push @out, "$vlabel $input";
463             }
464             # TODO: need a way of designating the join here too
465 14         85 $out .= join(' ', @out);
466             }
467             }
468             # Hidden fields
469             elsif ($fattr->{type} eq 'hidden') {
470 0         0 $out .= $self->start_tag('input',
471             { type => 'hidden', name => $field, value => $value }, 'close');
472             }
473             # Textareas
474             elsif ($fattr->{type} eq 'textarea') {
475 2         14 $out .= $self->start_tag('textarea',
476 2         4 { %{$fattr->{input_attr}}, name => $field, });
477 2         101 $out .= $value . $self->end_tag('textarea');
478             }
479             # Input fields
480             else {
481 340         19450 $out .= $self->start_tag('input',
482 340         634 { %{$fattr->{input_attr}}, name => $field,
483             type => $fattr->{type}, value => $value }, 'close');
484             }
485              
486             # Now format using $out as value
487 484         14784 return $self->SUPER::cell_format($out, $fattr, $row, $field);
488             }
489              
490             # Derived cell_format_escape - escaping not supported
491             sub cell_format_escape
492             {
493 1029     1029 0 35492 my $self = shift;
494 1029         1649 my ($data) = @_;
495 1029         3064 return $data;
496             }
497              
498             # Derived cell_format_link - ignore links except for display fields
499             sub cell_format_link
500             {
501 6     6 0 38 my $self = shift;
502 6         13 my ($data, $fattr, $row, $field, $data_unformatted) = @_;
503 6 100 100     35 return $data if $fattr->{type} && $fattr->{type} ne 'display';
504 4         18 return $self->SUPER::cell_format_link(@_);
505             }
506              
507             # Derived cell_tags, for special handling of hiddens
508             sub cell_tags
509             {
510 541     541 0 6453 my $self = shift;
511 541         1037 my ($data, $row, $field, $tx_attr) = @_;
512              
513             # Default handling for 'table' forms
514 541 100       1805 return $self->SUPER::cell_tags(@_)
515             if $self->{defn_t}->{formtype} eq 'table';
516              
517             # Default handling if not a 'hidden'
518 496         2905 my $type = $self->{defn_t}->{field_attr}->{$field}->{type};
519 496 50 66     2880 return $self->SUPER::cell_tags(@_)
520             unless $type && $type eq 'hidden';
521              
522 0         0 return $data;
523             }
524              
525             # Merge in extra default sets: -submit for submit fields, -required for
526             # required fields, -errors for fields with errors
527             sub cell_merge_extras
528             {
529 397     397 0 4891 my $self = shift;
530 397         581 my ($row, $field) = @_;
531 397         611 my %extra = ();
532            
533             # Hack: -submit => { table => 0 } is used to signal external submits
534 397 100 100     2728 if (ref $self->{defn_t}->{field_attr}->{-submit} eq 'HASH' &&
535             exists $self->{defn_t}->{field_attr}->{-submit}->{table}) {
536 4         18 $self->{defn_t}->{submit_table} = $self->{defn_t}->{field_attr}->{-submit}->{table};
537 4         13 delete $self->{defn_t}->{field_attr}->{-submit}->{table};
538             }
539              
540             # -select fields
541 11         47 @extra{keys %{$self->{defn_t}->{field_attr}->{-select}}} =
  11         40  
542 397 100 66     4586 values %{$self->{defn_t}->{field_attr}->{-select}}
      100        
      100        
543             if $self->{defn_t}->{field_attr}->{-select} &&
544             ref $self->{defn_t}->{field_attr}->{-select} eq 'HASH' &&
545             $self->{defn_t}->{field_attr}->{$field}->{type} &&
546             $self->{defn_t}->{field_attr}->{$field}->{type} eq 'select';
547              
548             # -submit fields
549 95         422 @extra{keys %{$self->{defn_t}->{field_attr}->{-submit}}} =
  95         396  
550 397 100 66     3978 values %{$self->{defn_t}->{field_attr}->{-submit}}
      100        
551             if $self->{defn_t}->{field_attr}->{-submit} &&
552             ref $self->{defn_t}->{field_attr}->{-submit} eq 'HASH' &&
553             $self->{defn_t}->{submit_hash}->{$field};
554              
555             # -required fields
556 34         136 @extra{keys %{$self->{defn_t}->{field_attr}->{-required}}} =
  34         164  
557 397 100 100     2460 values %{$self->{defn_t}->{field_attr}->{-required}}
558             if $self->{defn_t}->{field_attr}->{-required} &&
559             $self->{defn_t}->{field_attr}->{$field}->{required};
560              
561             # -errors fields
562 22         106 @extra{keys %{$self->{defn_t}->{field_attr}->{-errors}}} =
  22         115  
563 397 100 100     2492 values %{$self->{defn_t}->{field_attr}->{-errors}}
564             if $self->{defn_t}->{field_attr}->{-errors} &&
565             exists $self->{defn_t}->{errors}->{$field};
566              
567 397         14094 return %extra;
568             }
569              
570             # Extract per-field table attribute definitions (tr, th, td, td_error)
571             sub extract_field_table_attr
572             {
573 341     341 0 415 my $self = shift;
574 341         465 my ($td_attr, $th_attr) = @_;
575 341   50     716 $td_attr ||= {};
576 341   100     835 $th_attr ||= {};
577              
578 341   100     1168 my $tr_attr = $self->{defn_t}->{tr} || {};
579 341 100 66     939 if ($td_attr->{tr} && ref $td_attr->{tr} eq 'HASH') {
580 3         4 $tr_attr = { %$tr_attr, %{$td_attr->{tr}} };
  3         9  
581 3         7 delete $td_attr->{tr};
582             }
583 341 100 66     1735 if ($td_attr->{th} && ref $td_attr->{th} eq 'HASH') {
584 56         80 $th_attr = { %$th_attr, %{$td_attr->{th}} };
  56         199  
585 56         129 delete $td_attr->{th};
586             }
587 341 100 66     857 if ($td_attr->{td} && ref $td_attr->{td} eq 'HASH') {
588 3         6 $td_attr = { %$td_attr, %{$td_attr->{td}} };
  3         10  
589 3         6 delete $td_attr->{td};
590             }
591             # 'td_error' components are only applied to (column) error messages
592 341         8175 my $error_td_attr;
593 341 100 66     1130 if ($td_attr->{td_error} && ref $td_attr->{td_error} eq 'HASH') {
594 24         34 my $td = $td_attr->{td_error};
595 24         34 delete $td_attr->{td_error};
596 24 100       88 $error_td_attr = { %$td_attr, %$td }
597             if $self->{defn_t}->{errors_where} eq 'column';
598             }
599              
600 341         1200 return $tr_attr, $td_attr, $th_attr, $error_td_attr;
601             }
602              
603             # Derived row_across, for special handling of hiddens
604             sub row_across
605             {
606 281     281 0 3307 my $self = shift;
607 281         500 my ($data, $rownum, $field) = @_;
608              
609             # Default handling for 'table' forms
610 281 50       929 return $self->SUPER::row_across(@_)
611             if $self->{defn_t}->{formtype} eq 'table';
612              
613             # Need to call cell_merge_defaults early, since there may be
614             # settings that affect the whole row (single row table assumed)
615 281         1109 $self->cell_merge_defaults($rownum, $field);
616 281         688 my $lattr = $self->{defn_t}->{label_attr}->{$field};
617 281         399 my $th_attr = $lattr->{tx_attr};
618 281         566 my $fattr = $self->{defn_t}->{data_attr}->{$field};
619 281         365 my $td_attr = $fattr->{tx_attr};
620              
621             # Special handling for 'hidden' and 'omit' fields
622 281   100     859 my $type = $fattr->{type} || '';
623 281 100       969 if ($type eq 'hidden') {
    100          
624             # Don't render - just update top-level hidden hashref
625 34         72 my $value = $self->{defn_t}->{hidden}->{$field};
626 34 100       162 $self->{defn_t}->{hidden}->{$field} = $self->SUPER::cell_content(
627             $data->[0], $field, $fattr)
628             unless defined $value;
629             # Reset null-ified values
630 34 100       470 $self->{defn_t}->{hidden}->{$field} = ''
631             if $self->{defn_t}->{hidden}->{$field} eq $self->{defn_t}->{null};
632 34         143 return '';
633             }
634             elsif ($type eq 'omit') {
635 5         15 return '';
636             }
637              
638 242         304 my ($tr_attr, $error_td_attr);
639 242         633 ($tr_attr, $td_attr, $th_attr, $error_td_attr) =
640             $self->extract_field_table_attr($td_attr, $th_attr);
641              
642 242         474 my @format = ();
643 242         300 my @value = ();
644 242         422 my $th_colspan = 1;
645 242 100       646 if ($self->{defn_t}->{labels}) {
646 239         855 push @format, $self->cell(undef, $field, $lattr, $th_attr);
647 239         7224 push @value, $self->cell(undef, $field, $lattr, $th_attr, tags => 0);
648 239   50     3101 $th_colspan = $th_attr->{colspan} || 1;
649             }
650             # Omit data field if th_colspan >= 2
651 242 50       13729 if ($th_colspan < 2) {
652 242         11248 push @format, $self->cell($data->[0], $field, $fattr, $td_attr);
653 242         5659 push @value, $self->cell($data->[0], $field, $fattr, $td_attr, tags => 0);
654             }
655             # Column errors
656 242 100       4758 if ($self->{defn_t}->{errors_where} eq 'column') {
657 3         11 my $error = ref $self->{defn_t}->{errors}->{$field} eq 'ARRAY' ?
658             join ("
",
659 2         5 map { sprintf $_, $self->{defn_t}->{_labels}->{$field} }
660 15 100 100     110 @{$self->{defn_t}->{errors}->{$field}}) :
661             sprintf($self->{defn_t}->{errors}->{$field} || ' ',
662             $self->{defn_t}->{_labels}->{$field});
663 15         39 push @format, $self->cell_tags($error, 1, $field, $error_td_attr);
664             }
665              
666             # Generate output
667 242         644 $tr_attr = { %$tr_attr, %{ $self->tr_attr($rownum, \@value, $data) } };
  242         1015  
668 242         7552 my $row = $self->start_tag('tr', $tr_attr);
669 242         3446 $row .= join '', @format;
670 242         740 $row .= $self->end_tag('tr', $tr_attr) . "\n";
671              
672 242         2228 return $row;
673             }
674              
675             # Override body_across to avoid automatic field derivation
676             sub body_across
677             {
678 78     78 0 126 my $self = shift;
679 78         177 my $fields = $self->{defn_t}->{fields};
680 78 50 66     714 return '' unless $fields && ref $fields eq 'ARRAY' && @$fields;
      66        
681 77         596 $self->SUPER::body_across(@_);
682             }
683              
684             # Output hidden fields
685             sub hidden
686             {
687 78     78 1 160 my $self = shift;
688 78         122 my $out = '';
689 78 50       881 if (ref $self->{defn_t}->{hidden} eq 'HASH') {
690 78         315 for my $name (sort keys %{$self->{defn_t}->{hidden}}) {
  78         484  
691 34         191 $out .= $self->start_tag('input', {
692             type => 'hidden', name => $name,
693             value => $self->{defn_t}->{hidden}->{$name},
694             }, 'close');
695 34         790 $out .= "\n";
696             }
697             }
698 78         200 return $out;
699             }
700              
701             # Display submit etc. buttons
702             sub submit
703             {
704 87     87 1 148 my $self = shift;
705 87         218 my %arg = @_;
706              
707 87         148 my $out = '';
708 87         164 my $defn = $self->{defn_t};
709 87 100       301 return '' unless $defn->{submit};
710              
711             # Map scalars to array (and submit => 1 == submit => 'submit')
712 80 0       226 $defn->{submit} = [ $defn->{submit} == 1 ? 'submit' : $defn->{submit} ]
    50          
713             if ! ref $defn->{submit};
714 80         121 $defn->{submit_hash} = { map { $_ => 1 } @{$defn->{submit}} };
  99         398  
  80         188  
715              
716             # Build submit buttons input fields
717 80         171 my ($tr_attr, $td_attr);
718 80         127 for my $field (@{$defn->{submit}}) {
  80         192  
719 99         449 $self->cell_merge_defaults(1, $field);
720 99         273 my $fattr = $self->{defn_t}->{data_attr}->{$field};
721 99         152 my $td = $fattr->{tx_attr};
722 99         138 my $tr;
723 99         261 ($tr, $td) = $self->extract_field_table_attr($td);
724             # Save tr/td attributes from first submit
725 99 100       348 if (! $defn->{submit_attr}) {
726 78         309 $defn->{submit_attr} = {
727             tr_attr => $tr,
728             td_attr => $td,
729             };
730             }
731 99         193 $tr_attr = $defn->{submit_attr}->{tr_attr};
732 99         160 $td_attr = $defn->{submit_attr}->{td_attr};
733 99         220 my $field_id = lc $field;
734 99         274 $field_id =~ s/\s+/_/g;
735             my $field_value = $fattr->{value} || $fattr->{label} ||
736 99   66     738 join(' ', map { ucfirst } split /\s+/, $field);
737 99         1217 $out .= $self->start_tag('input', {
738             type => 'submit', name => $field_id, id => $field_id, value => $field_value,
739 99         241 %{$fattr->{input_attr}}
740             }, 'close');
741 99         4419 $out .= "\n";
742             }
743              
744             # Build submit line
745 80 100       270 if ($arg{table}) {
746 75         106 my $cols = 2;
747 75 100 100     278 $cols++ if $defn->{errors_where} && $defn->{errors_where} eq 'column';
748 75 100       324 $cols-- if ! $self->{defn_t}->{labels};
749 75 100       297 my %colspan = $cols > 1 ? ( colspan => $cols ) : ();
750 75         124 $tr_attr = { %$tr_attr, %{$self->tr_attr(1, [ 'Submit', $out ])} };
  75         333  
751 75         2096 return $self->start_tag('tr', $tr_attr) .
752             $self->start_tag('td', { %colspan, align => 'center', %$td_attr }) . "\n" .
753             $out .
754             $self->end_tag('td') .
755             $self->end_tag('tr') . "\n";
756             }
757             else {
758 5         20 return $self->start_tag('p', $td_attr) . "\n" .
759             $out .
760             $self->end_tag('p') . "\n";
761             }
762             }
763              
764             # Format error messages using errors_format
765             sub top_errors
766             {
767 6     6 0 8 my $self = shift;
768 6         7 my $defn_t = $self->{defn_t};
769              
770 6 50       18 return '' unless $defn_t->{errors_format};
771              
772             # Fields and labels should always be defined by this point
773 6         7 my %errors = %{$defn_t->{errors}};
  6         21  
774 6         10 my @errors;
775             # Report errors in field order
776 6         9 for my $field (@{$defn_t->{fields}}) {
  6         13  
777 24 100       53 if ($errors{$field}) {
778 12 100       24 if (ref $errors{$field} eq 'ARRAY') {
779 2         4 for my $err (@{$errors{$field}}) {
  2         3  
780 3         14 push @errors, sprintf($err, $defn_t->{_labels}->{$field});
781             }
782             }
783             else {
784 10         33 push @errors, sprintf($errors{$field}, $defn_t->{_labels}->{$field});
785             }
786 12         28 delete $errors{$field};
787             }
788             }
789             # Report any remaining (presumably non-field-specific) errors
790 6         30 for my $extra (sort keys %errors) {
791 0 0       0 if (ref $errors{$extra} eq 'ARRAY') {
792 0         0 push @errors, sprintf($_, $extra) foreach @{$errors{$extra}};
  0         0  
793             }
794             else {
795 0         0 push @errors, sprintf($errors{$extra}, $extra);
796             }
797             }
798 6 50       16 return '' unless @errors;
799              
800             # If sub, simply invoke, passing all errors
801 6 100       14 if (ref $defn_t->{errors_format}) {
802 5         15 return $defn_t->{errors_format}->(@errors);
803             }
804             else {
805 1         2 my $out = '';
806 1         3 for my $err (@errors) {
807 2         6 $out .= sprintf $defn_t->{errors_format}, $err;
808 2 50       19 $out .= "\n" unless substr($out,-1) eq "\n";
809             }
810 1         4 return $out;
811             }
812             }
813              
814             # Derived pre_table to include top-style error messages
815             sub pre_table
816             {
817 88     88 0 939 my $self = shift;
818 88         303 my ($set) = @_;
819 88         151 my $content = '';
820 88 50       720 $content .= $self->title($set) if $self->{defn_t}->{title};
821 88 100 100     5625 $content .= $self->top_errors
822             if $self->{defn_t}->{errors_where} &&
823             $self->{defn_t}->{errors_where} eq 'top';
824 88 50       1884 $content .= $self->text($set) if $self->{defn_t}->{text};
825 88         2785 return $content;
826             }
827              
828             #
829             # Derived start_table to include form tags
830             #
831             sub start_table
832             {
833 85     85 0 293 my ($self) = @_;
834 85         174 my $out = '';
835 85 100       450 $out .= $self->start_tag('form',$self->{defn_t}->{form}) . "\n"
836             if $self->{defn_t}->{form};
837 85   100     1793 my $submit_location = $self->{defn_t}->{submit_location} || 'bottom';
838 85 100 66     265 if ($submit_location eq 'bottom') {
    100          
839 81         413 $out .= $self->SUPER::start_table();
840             }
841             elsif (exists $self->{defn_t}->{submit_table} &&
842             $self->{defn_t}->{submit_table} == 0) {
843 2         8 $out .= $self->submit();
844 2         52 $out .= $self->SUPER::start_table();
845             }
846             else {
847 2         9 $out .= $self->SUPER::start_table();
848 2         43 $out .= $self->submit(table => 1);
849             }
850 85         1787 return $out;
851             }
852              
853             #
854             # Derived end_table to include form tags and submits
855             #
856             sub end_table
857             {
858 85     85 0 670 my ($self) = @_;
859 85         137 my $out = '';
860 85   100     428 my $submit_location = $self->{defn_t}->{submit_location} || 'bottom';
861 85 100 66     455 if ($submit_location eq 'top') {
    100          
862 2         18 $out .= $self->SUPER::end_table();
863             }
864             elsif (exists $self->{defn_t}->{submit_table} &&
865             $self->{defn_t}->{submit_table} == 0) {
866 3         14 $out .= $self->SUPER::end_table();
867 3         41 $out .= $self->submit();
868             }
869             else {
870 80         287 $out .= $self->submit(table => 1);
871 80         2641 $out .= $self->SUPER::end_table();
872             }
873 85 100       1367 $out .= $self->hidden() if $self->{defn_t}->{hidden};
874 85 100       550 $out .= $self->end_tag('form') . "\n" if $self->{defn_t}->{form};
875 85         630 return $out;
876             }
877              
878             # -------------------------------------------------------------------------
879             # Derived check_fields - unlike Tabulate, don't derive from data if undefined
880             sub check_fields {
881 88     88 0 27789 my $self = shift;
882             # Default handling for 'table' forms
883 88 100       696 $self->SUPER::check_fields(@_) if $self->{defn_t}->{formtype} eq 'table';
884             }
885              
886             # Derived render_table - skip form altogether unless 'fields' or 'submit'
887             sub render_table
888             {
889 88     88 0 420 my $self = shift;
890 88         242 my ($set) = @_;
891              
892             # Default handling for 'table' forms
893 88 100       5253 return $self->SUPER::render_table(@_)
894             if $self->{defn_t}->{formtype} eq 'table';
895              
896             # Decide whether we need a form
897 81         177 my $fields = $self->{defn_t}->{fields};
898 81         160 my $submit = $self->{defn_t}->{submit};
899 81   66     968 my $do_form = ($fields && ref $fields eq 'ARRAY' && @$fields) ||
900             ($submit && ref $submit eq 'ARRAY' && @$submit);
901              
902             # Ignore 'style' - we just always use 'across'
903 81 100       435 my $body = $self->body_across($set) if $do_form;
904            
905             # Build table
906 81         660 my $table = '';
907 81         318 $table .= $self->pre_table($set);
908 81 100       244 if ($do_form) {
909 78         1984 $table .= $self->start_table();
910 78         196 $table .= $body;
911 78         284 $table .= $self->end_table();
912             }
913 81         385 $table .= $self->post_table($set);
914              
915 81         8105 return $table;
916             }
917              
918             # -------------------------------------------------------------------------
919             # Derived render to setup procedural call if necessary
920             sub render
921             {
922 90     90 1 89883 my $self = shift;
923 90         185 my ($set, $defn) = @_;
924              
925             # If $self is not blessed, this is a procedural call, $self is $set
926 90 50 33     761 if (ref $self eq 'HASH' || ref $self eq 'ARRAY') {
927 0         0 $defn = $set;
928 0         0 $set = $self;
929 0         0 $self = __PACKAGE__->new($defn);
930 0         0 undef $defn;
931             }
932              
933             # Call super version
934 90         513 $self->SUPER::render(@_);
935             }
936              
937             1;
938              
939             __END__