File Coverage

blib/lib/Form/Diva.pm
Criterion Covered Total %
statement 258 259 99.6
branch 80 82 97.5
condition 12 12 100.0
subroutine 23 23 100.0
pod 8 8 100.0
total 381 384 99.2


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