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__ |