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