File Coverage

blib/lib/Form/Diva.pm
Criterion Covered Total %
statement 256 257 99.6
branch 82 84 97.6
condition 18 19 94.7
subroutine 23 23 100.0
pod 8 8 100.0
total 387 391 98.9


line stmt bran cond sub pod time code
1 16     16   8802 use strict;
  16         27  
  16         381  
2 16     16   57 use warnings;
  16         25  
  16         336  
3 16     16   57 no warnings 'uninitialized';
  16         25  
  16         910  
4              
5             package Form::Diva;
6              
7             our $VERSION='1.04';
8              
9             # use Data::Printer;
10              
11             # ABSTRACT: Generate HTML5 form label and input fields
12              
13 16     16   483 use Storable 3.15 qw(dclone);
  16         2951  
  16         35702  
14              
15             # The _option_id sub needs access to a variable for hashing the ids
16             # in use, even though it is initialized at the beginning of generate,
17             # it needs to exist outside of the generate subroutines scope
18             # and before before the _option_id sub is declared.
19             my %id_uq = ();
20 36     36   4051 sub _clear_id_uq { %id_uq = () }
21              
22             # our $id_base = 'formdiva_';
23              
24             # True if all fields are used no more than once, if not it dies.
25             # Form::Diva->{FormHash} stores all the fields a duplicated fieldname
26             # would replace the previous value.
27             sub _field_once {
28 35     35   1015 my $self = shift;
29 35         47 my @fields = ( @{ $self->{FormMap} }, @{ $self->{HiddenMap} } );
  35         51  
  35         69  
30 35         44 my %hash = ();
31 35         50 foreach my $field (@fields) {
32 154 100       201 if ( $hash{ $field->{name} } ) {
33 1         9 die "$field->{name} would appear more than once or "
34             . "is in both hidden and visible field lists. Not "
35             . "only would this cause strange behaviour in your form "
36             . "but it could internally corrupt Form::Diva";
37             }
38 153         194 else { $hash{ $field->{name} } = 1; }
39             }
40 34         61 return 1;
41             }
42              
43             sub new {
44 32     32 1 19641 my $class = shift;
45 32         99 my $self = {@_};
46 32         50 bless $self, $class;
47 32         74 $self->{class} = $class;
48 32 100       82 unless ( $self->{input_class} ) { die 'input_class is required.' }
  1         6  
49 31 100       61 unless ( $self->{label_class} ) { die 'label_class is required.' }
  1         7  
50 30 100       79 $self->{id_base} = length $self->{id_base} ? $self->{id_base} : 'formdiva_';
51             ( $self->{HiddenMap}, my $HHash )
52 30         79 = $self->_expandshortcuts( $self->{hidden} );
53             ( $self->{FormMap}, my $FHash )
54 30         80 = $self->_expandshortcuts( $self->{form} );
55 29         42 $self->{FormHash} = { %{$HHash}, %{$FHash} };
  29         50  
  29         84  
56 29         90 $self->_field_once;
57 29         83 return $self;
58             }
59              
60             sub clone {
61 4     4 1 1831 my $self = shift;
62 4         6 my $args = shift;
63 4         4 my $new = {};
64 4         6 my $class = 'Form::Diva';
65 4         201 $new->{FormHash} = dclone $self->{FormHash};
66             $new->{input_class}
67 4 100       15 = $args->{input_class} ? $args->{input_class} : $self->{input_class};
68             $new->{label_class}
69 4 50       10 = $args->{label_class} ? $args->{label_class} : $self->{label_class};
70             $new->{form_name}
71 4 100       7 = $args->{form_name} ? $args->{form_name} : $self->{form_name};
72              
73 4 100       6 if ( $args->{neworder} ) {
74 3         3 my @reordered = map { $new->{FormHash}->{$_} } @{ $args->{neworder} };
  8         14  
  3         7  
75 3         6 $new->{FormMap} = \@reordered;
76             }
77 1         28 else { $new->{FormMap} = dclone $self->{FormMap}; }
78 4 100       9 if ( $args->{newhidden} ) {
79 1         2 my @hidden = map { $self->{FormHash}{$_} } @{ $args->{newhidden} };
  2         4  
  1         2  
80 1         3 $new->{HiddenMap} = \@hidden;
81             }
82 3         55 else { $new->{HiddenMap} = dclone $self->{HiddenMap}; }
83 4         9 bless $new, $class;
84 4         8 $self->_field_once;
85 4         8 return $new;
86             }
87              
88             # specification calls for single letter shortcuts on all fields
89             # these all need to expand to the long form.
90             sub _expandshortcuts {
91 64     64   366 my $self = shift;
92 64         87 my $FormMap = shift; # data passed to new
93 64         262 my %DivaShortMap = (
94             qw /
95             n name t type i id e extra x extra l label p placeholder
96             d default v values c class lc label_class /
97             );
98 64         148 my %DivaLongMap = map { $DivaShortMap{$_}, $_ } keys(%DivaShortMap);
  704         927  
99 64         118 my $FormHash = {};
100 64         72 foreach my $formfield ( @{$FormMap} ) {
  64         107  
101 130         174 foreach my $tag ( keys %{$formfield} ) {
  130         253  
102 449 100       597 if ( $DivaShortMap{$tag} ) {
103             $formfield->{ $DivaShortMap{$tag} }
104 216         379 = delete $formfield->{$tag};
105             }
106             }
107 130 100       296 unless ( $formfield->{type} ) { $formfield->{type} = 'text' }
  23         40  
108 130 100       286 unless ( $formfield->{name} ) { die "fields must have names" }
  1         18  
109 129 100       207 unless ( $formfield->{id} ) {
110 101         231 $formfield->{id} = $self->{id_base} . $formfield->{name};
111             }
112              
113             # dclone because otherwise it would be a ref into FormMap
114 129         2879 $FormHash->{ $formfield->{name} } = dclone $formfield;
115             }
116 63         265 return ( $FormMap, $FormHash );
117             }
118              
119             sub input_class {
120 1     1 1 355 my $self = shift;
121 1         4 return $self->{input_class};
122             }
123              
124             sub label_class {
125 1     1 1 2 my $self = shift;
126 1         3 return $self->{label_class};
127             }
128              
129             # given a field returns either the default field class="string"
130             # or the field specific one
131             sub _class_input {
132 144     144   405 my $self = shift;
133 144         139 my $field = shift;
134 144   100     332 my $fclass = $field->{class} || '';
135 144 100       187 if ($fclass) { return qq!class="$fclass"! }
  19         45  
136 125         253 else { return qq!class="$self->{input_class}"! }
137             }
138              
139             sub _field_bits {
140 103     103   139 my $self = shift;
141 103         94 my $field_ref = shift;
142 103         95 my $data = shift;
143 103         93 my %in = %{$field_ref};
  103         279  
144 103         126 my %out = ();
145 103         117 my $fname = $in{name};
146 103         117 $out{extra} = $in{extra}; # extra is taken literally
147 103         149 $out{input_class} = $self->_class_input($field_ref);
148 103         155 $out{name} = qq!name="$in{name}"!;
149 103         144 $out{id} = qq!id="$in{id}"!;
150              
151 103 100       158 if ( lc( $in{type} ) eq 'textarea' ) {
152 4         4 $out{type} = 'textarea';
153 4         4 $out{textarea} = 1;
154             }
155             else {
156 99         142 $out{type} = qq!type="$in{type}"!;
157 99         107 $out{textarea} = 0;
158 99 50       145 if ( $in{type} eq 'hidden' ) { $out{hidden} = 1 }
  0         0  
159             }
160 103 100       94 if ( keys %{$data} ) {
  103         175  
161 28         30 $out{placeholder} = '';
162 28   100     51 $out{rawvalue} = $data->{$fname} || '';
163             }
164             else {
165 75 100       100 if ( $in{default} ) { $out{rawvalue} = $in{default}; }
  22         32  
166 53         67 else { $out{rawvalue} = '' }
167             }
168 103 100       140 if ( $in{placeholder} ) {
169 34         60 $out{placeholder} = qq!placeholder="$in{placeholder}"!;
170             }
171             else {
172 69         77 $out{placeholder} = '';
173             }
174              
175 103         146 $out{value} = qq!value="$out{rawvalue}"!;
176 103         446 return %out;
177             }
178              
179             sub _label {
180              
181             # an id does not get put in label because the spec does not say either
182             # the id attribute or global attributes are supported.
183             # http://www.w3.org/TR/html5/forms.html#the-label-element
184 105     105   124 my $self = shift;
185 105         100 my $field = shift;
186              
187 105 100 100     247 return '' if exists $field->{label} && !defined $field->{label};
188              
189             my $label_class
190             = $field->{label_class}
191             ? $field->{label_class}
192 104 100       163 : $self->{label_class};
193             my $label_tag
194 104 100 100     200 = exists $field->{label} ? $field->{label} || '' : ucfirst( $field->{name} );
195 104         450 return qq|
196             . qq|$label_tag|;
197             }
198              
199             sub _input {
200 73     73   5869 my $self = shift;
201 73         69 my $field = shift;
202 73         67 my $data = shift;
203 73         127 my %B = $self->_field_bits( $field, $data );
204 73         107 my $input = '';
205 73 100       100 if ( $B{textarea} ) {
206 2         5 $input = qq||;
208             }
209             else {
210 71         205 $input .= qq|
211             $B{input_class} $B{placeholder} $B{extra} $B{value} >|;
212             }
213 73         151 return $input;
214             }
215              
216             sub _input_hidden {
217 12     12   25 my $self = shift;
218 12         12 my $field = shift;
219 12         33 my $data = shift;
220 12         31 my %B = $self->_field_bits( $field, $data );
221              
222             #hidden fields don't get a class or a placeholder
223 12         34 my $input .= qq|
224             $B{extra} $B{value} >|;
225 12         54 $input =~ s/\s+/ /g; # remove extra whitespace.
226 12         41 $input =~ s/\s+>/>/g; # cleanup space before closing >
227 12         31 return $input;
228             }
229              
230             # generates the id= for option items.
231             # uses global %id_uq to insure uniqueness in generated ids.
232             # It might be cleaner to make this a sub ref under _option_input
233             # and put the hash there too, but potentially the global hash
234             # protects against a wider (though unlikely) range of collisions,
235             # also putting the code_ref in _option_id would make it that much longer.
236             sub _option_id {
237 149     149   2293 my $self = shift;
238 149         142 my $id = shift;
239 149         134 my $value = shift;
240 149         296 my $idv = $id . '_' . lc($value);
241 149         206 $idv =~ s/\s+/_/g;
242 149         228 while ( defined $id_uq{$idv} ) {
243 2         3 $id_uq{$idv}++;
244 2         5 $idv = $idv . $id_uq{$idv};
245             }
246 149         201 $id_uq{$idv} = 1;
247 149         221 return "id=\"$idv\"";
248             }
249              
250             sub _option_input { # field, input_class, data;
251 38     38   64 my $self = shift;
252 38         41 my $field = shift; # field definition from FormMap or FormHash
253 38         37 my $data = shift; # data for this form field
254 38         56 my $replace_fields = shift; # valuelist to use instead of default
255 38         56 my $datavalue = $data->{ $field->{name} };
256 38         38 my $output = '';
257 38         54 my $input_class = $self->_class_input($field);
258 38   100     89 my $extra = $field->{extra} || "";
259              
260             # in case default is 0, it must be checked in a string context
261             my $default = length( $field->{default} )
262 38 100       78 ? do {
263 19 100       20 if ( keys %{$data} ) {undef}
  19         34  
  9         12  
264 10         21 else { $field->{default} }
265             }
266             : undef;
267             my @values
268             = $replace_fields
269 5         11 ? @{$replace_fields}
270 38 100       56 : @{ $field->{values} };
  33         59  
271 38 100       60 if ( $field->{type} eq 'select' ) {
272 13         34 $output
273             = qq|
274 13         22 foreach my $val (@values) {
275 34         50 my ( $value, $v_lab ) = ( split( /\:/, $val ), $val );
276 34         69 my $idf = $self->_option_id( $field->{id}, $value );
277 34         29 my $selected = '';
278 34 100       55 if ( $datavalue eq $value ) { $selected = 'selected ' }
  4 100       4  
279 1         1 elsif ( $default eq $value ) { $selected = 'selected ' }
280             $output
281 34         69 .= qq| \n|;
282             }
283 13         15 $output .= '';
284             }
285             else {
286 25         37 foreach my $val (@values) {
287 108         210 my ( $value, $v_lab ) = ( split( /\:/, $val ), $val );
288 108         159 my $idf = $self->_option_id( $field->{id}, $value );
289 108         116 my $checked = '';
290 108 100       316 if ( $datavalue eq $value ) {
    100          
291 5         6 $checked = q !checked="checked" !;
292             }
293             elsif ( $default eq $value ) {
294 9         11 $checked = q !checked="checked" !;
295             }
296             $output
297 108         301 .= qq!$v_lab
\n!;
298             }
299             }
300 38         99 return $output;
301             }
302              
303             # check if $data is a hashref or a dbic result row and inflate it.
304             sub _checkdatadbic {
305 52     52   2721 my $data = shift;
306 52 100       133 if ( ref $data eq 'HASH' ) { return $data }
  16 100       30  
307 36         232 elsif ( eval { $data->isa('DBIx::Class::Row') } ) {
308 5         9 return { $data->get_inflated_columns };
309             }
310 31         77 else { return {} }
311             }
312              
313             sub generate {
314 28     28 1 22878 my $self = shift @_;
315 28         55 my $data = _checkdatadbic( shift @_ );
316 28         52 my $overide = shift @_;
317 28         40 my @generated = ();
318 28         64 $self->_clear_id_uq; # needs to be empty when form generation starts.
319 28         29 foreach my $field ( @{ $self->{FormMap} } ) {
  28         58  
320 97         116 my $input = undef;
321 97 100 100     367 if ( $field->{type} eq 'radio'
      100        
322             || $field->{type} eq 'checkbox'
323             || $field->{type} eq 'select' )
324             {
325             $input
326             = $self->_option_input( $field, $data,
327             $overide->{ $field->{name} },
328 31         90 );
329             }
330             else {
331 66         104 $input = $self->_input( $field, $data );
332             }
333 97         552 $input =~ s/ +/ /g; # remove extra whitespace.
334 97         545 $input =~ s/\s+>/>/g; # cleanup space before closing >
335             push @generated,
336             {
337             label => $self->_label($field),
338             input => $input,
339             comment => $field->{comment},
340 97         189 };
341             }
342 28         87 return \@generated;
343             }
344              
345             sub prefill {
346 4     4 1 5968 my $self = shift @_;
347 4         12 my $data = _checkdatadbic( shift @_ );
348 4         12 my $overide = shift @_;
349 4         366 my $oriFormMap = dclone $self->{FormMap};
350 4         11 foreach my $item ( @{ $self->{FormMap} } ) {
  4         11  
351 20         24 my $iname = $item->{name};
352 20 100       39 if ( $data->{$iname} ) {
353 6         40 $item->{default} = $data->{$iname};
354             }
355             }
356 4         13 my $generated = $self->generate( undef, $overide );
357 4         6 $self->{FormMap} = $oriFormMap;
358 4         24 return $generated;
359             }
360              
361             sub hidden {
362 4     4 1 1381 my $self = shift;
363 4         8 my $data = _checkdatadbic( shift @_ );
364 4         10 my $output = '';
365 4         5 foreach my $field ( @{ $self->{HiddenMap} } ) {
  4         5  
366 8         14 $output .= $self->_input_hidden( $field, $data ) . "\n";
367             }
368 4         13 return $output;
369             }
370              
371              
372             # my $data = _checkdatadbic( shift @_ );
373             # my $overide = shift @_;
374             # my @generated = ();
375              
376             sub datavalues {
377 13     13 1 11432 my $self = shift;
378 13         33 my $data = _checkdatadbic( shift @_ );
379 13         19 my $skipempty = 0;
380 13         12 my $moredata = 0;
381 13         19 for (@_) {
382 8 100       13 if ( $_ eq 'skipempty' ) { $skipempty = 1 }
  3         3  
383 8 100       15 if ( $_ eq 'moredata' ) { $moredata = 1 }
  5         7  
384             }
385 13         15 my @datavalues = ();
386             PLAINLOOP:
387 13         13 foreach my $field ( @{ $self->{FormMap} } ) {
  13         20  
388 42 100       54 if ($skipempty) {
389 12 100       23 unless ( $data->{ $field->{name} } ) { next PLAINLOOP }
  6         8  
390             }
391             my %row = (
392             name => $field->{name},
393             type => $field->{type},
394             value => $data->{ $field->{name} },
395             comment => $field->{comment},
396 36         88 );
397             $row{label}
398 36 100 50     73 = exists $field->{label} ? $field->{label} || '' : ucfirst( $field->{name} );
399             $row{id} = $field->{id}
400 36         40 ; # coverage testing deletion ? $field->{id} : 'formdiva_' . $field->{name};
401 36 100       54 if ($moredata) {
402 13         16 $row{extra} = $field->{extra};
403 13         20 $row{values} = $field->{values};
404 13         14 $row{default} = $field->{default};
405 13         13 $row{placeholder} = $field->{placeholder};
406             $row{class}
407 13 100       21 = $field->{class} ? $field->{class} : $self->{input_class};
408              
409             }
410 36         58 push @datavalues, \%row;
411             }
412 13         32 return \@datavalues;
413             }
414              
415             1;
416              
417             __END__