File Coverage

blib/lib/HTML/FormBuilder/Field.pm
Criterion Covered Total %
statement 111 121 91.7
branch 50 62 80.6
condition 15 20 75.0
subroutine 11 11 100.0
pod 2 2 100.0
total 189 216 87.5


line stmt bran cond sub pod time code
1             package HTML::FormBuilder::Field;
2              
3 7     7   25 use strict;
  7         6  
  7         198  
4 7     7   24 use warnings;
  7         41  
  7         148  
5 7     7   97 use 5.008_005;
  7         15  
6              
7 7     7   22 use Carp;
  7         5  
  7         377  
8 7     7   26 use Scalar::Util qw(weaken blessed);
  7         6  
  7         496  
9              
10 7     7   2890 use Moo;
  7         62756  
  7         28  
11 7     7   8647 use namespace::clean;
  7         47653  
  7         23  
12             extends qw(HTML::FormBuilder::Base);
13              
14             our $VERSION = '0.12'; ## VERSION
15              
16             has data => (
17             is => 'ro',
18             isa => sub {
19             my $data = shift;
20             croak('data should be a hashref') unless ref($data) eq 'HASH';
21             },
22             default => sub {
23             {};
24             },
25             );
26              
27             sub BUILDARGS {
28 97     97 1 6836 my (undef, @args) = @_;
29 97 50       243 my %args = (@args % 2) ? %{$args[0]} : @args;
  0         0  
30              
31 97         94 my $data = $args{data};
32              
33             # normalize: if 'input' is not an array, then make it as an array, so that
34             # we can process the array directly
35 97 100 100     369 if ($data->{input} && ref($data->{input}) ne 'ARRAY') {
36 61         93 $data->{input} = [$data->{input}];
37             }
38              
39 97         1287 return \%args;
40             }
41              
42             sub build {
43 71     71 1 51 my $self = shift;
44 71         49 my $env = shift;
45              
46 71         68 my $data = $self->{data};
47              
48 71         54 my $stacked = $env->{stacked};
49 71         98 my $classes = $self->classes;
50              
51 71         53 my $div_span = "div";
52 71         59 my $label_column = $classes->{label_column};
53 71         55 my $input_column = $classes->{input_column};
54 71 50       102 $input_column = $data->{override_input_class} if ($data->{override_input_class});
55              
56 71 50       108 if ($stacked == 0) {
57 0         0 $div_span = "span";
58 0         0 $label_column = "";
59 0         0 $input_column = "";
60             }
61 71         50 my $input_fields_html = '';
62              
63 71         49 my $stacked_attr = {};
64              
65 71 50       100 if ($stacked == 1) {
66 71 50       88 my $class = $data->{'class'} ? " $data->{class}" : '';
67              
68 71 50 33     119 if ($data->{'type'} and $data->{'type'} eq 'hidden') {
69 0         0 $stacked_attr->{class} = $class;
70             } else {
71 71         129 $stacked_attr->{class} = "$classes->{row_padding} $classes->{row} clear$class";
72             }
73             }
74              
75             #create the field label
76 71 100       98 if (defined $data->{'label'}) {
77 56   100     91 my $label_text = $data->{'label'}->{'text'} || '';
78 56         54 undef $data->{'label'}->{'text'};
79 56   100     123 my $required_mark = delete $data->{label}{required_mark} || 0;
80 56         136 my $label_html = $self->_build_element_and_attributes('label', $data->{'label'}, $label_text, {required_mark => $required_mark},);
81              
82             # add a tooltip explanation if given
83 56 100       90 if ($data->{'label'}{'tooltip'}) {
84              
85             # img_url is the url of question mark picture
86 1         4 my $tooltip = _tooltip($data->{'label'}{'tooltip'}{'desc'}, $data->{'label'}{tooltip}{img_url});
87              
88 1         4 $input_fields_html .= qq{<div class="$classes->{extra_tooltip_container}">$label_html$tooltip</div>};
89             } else {
90 55 100       66 my $hide_mobile = $label_text ? "" : $classes->{hide_mobile};
91              
92 55         119 $input_fields_html .= qq{<$div_span class="$label_column $hide_mobile form_label">$label_html</$div_span>};
93             }
94             }
95              
96             # create the input field
97 71 100       101 if (defined $data->{'input'}) {
98              
99             #if there are more than 1 input field in a single row then we generate 1 by 1
100 61         50 my $inputs = $data->{input};
101 61         92 $input_fields_html .= qq{<$div_span class="$input_column">};
102 61         46 foreach my $input (@{$inputs}) {
  61         64  
103 76         95 $input_fields_html .= $self->_build_input($input, $env);
104             }
105             }
106              
107 71 100       102 if (defined $data->{'comment'}) {
108 1   50     4 $data->{'comment'}->{'class'} ||= '';
109              
110 1 50       3 unless ($data->{comment}->{no_new_line}) {
111 1         1 $input_fields_html .= '<br>';
112             }
113 1         4 $input_fields_html .= $self->_build_element_and_attributes('p', $data->{'comment'}, $data->{'comment'}->{'text'});
114             }
115              
116 71 100       104 if (defined $data->{'error'}) {
117              
118             my @errors =
119             ref($data->{'error'}) eq 'ARRAY'
120 1         2 ? @{$data->{error}}
121 48 100       102 : $data->{error};
122              
123 48         49 foreach my $error_box (@errors) {
124 48         97 $input_fields_html .= $self->_build_element_and_attributes('p', $error_box, $error_box->{text});
125             }
126              
127             }
128              
129             #close the input tag
130 71 100       97 if (defined $data->{'input'}) {
131 61         76 $input_fields_html .= '</' . $div_span . '>';
132             }
133              
134 71 50       93 if ($stacked == 1) {
135 71         135 $input_fields_html = $self->_build_element_and_attributes('div', $stacked_attr, $input_fields_html);
136             }
137              
138 71         219 return $input_fields_html;
139             }
140              
141             #####################################################################
142             # Usage : build the input field its own attributes
143             # Purpose : perform checking build the input field according to its own
144             # characteristics
145             # Returns : input field with its attributes in string
146             # Parameters : $input_field in HASH ref for example
147             # $attributes = {'id' => 'test', 'name' => 'test', 'class' => 'myclass'}
148             # Comments : check pod below to understand how to create different input fields
149             # See Also :
150             #####################################################################
151             sub _build_input {
152 76     76   58 my $self = shift;
153 76         49 my $input_field = shift;
154              
155 76         49 my $html = '';
156              
157             # delete this so that it doesn't carry on to the next field
158             # I don't know why should delete it(undef it)
159 76         73 my $heading = delete $input_field->{'heading'};
160 76         57 my $trailing = delete $input_field->{'trailing'};
161              
162             # wrap <input>, heading & trailing <span> in <div>
163 76         56 my ($wrap_input, $wrap_heading, $wrap_trailing);
164 76 50       108 if ($input_field->{wrap_in_div_class}) {
165 0         0 ($wrap_input, $wrap_heading, $wrap_trailing) = @{$input_field->{wrap_in_div_class}}{'input', 'heading', 'trailing'};
  0         0  
166             }
167              
168             #create the filed input
169 76 100 100     60 if (eval { $input_field->can('widget_html') }) {
  76 100       509  
    100          
170 15         36 $html = $input_field->widget_html;
171             } elsif ($input_field->{'type'} and $input_field->{'type'} eq 'textarea') {
172 7         11 undef $input_field->{'type'};
173 7   50     15 my $textarea_value = $input_field->{'value'} || '';
174 7         6 undef $input_field->{'value'};
175 7         18 $html = $self->_build_element_and_attributes('textarea', $input_field, $textarea_value);
176             } elsif ($input_field->{'type'}) {
177 53         51 my $type = $input_field->{'type'};
178 53 100       161 if ($type =~ /^(?:text|password)$/i) {
    100          
179 28         39 $input_field->{'class'} .= ' text';
180             } elsif ($type =~ /button|submit/) {
181 1         2 $input_field->{'class'} .= ' button';
182             }
183              
184 53 100       89 my $tag = ($type =~ /button|submit/ ? 'button' : 'input');
185              
186 53         98 $html = $self->_build_element_and_attributes($tag, $input_field);
187              
188 53 100       118 if ($type =~ /button|submit/) {
189 1         2 $html = qq{<span class="$input_field->{class}">$html</span>};
190             }
191             }
192 76 50       113 if ($wrap_input) {
193 0         0 $html = qq{<div class="$wrap_input">$html</div>};
194             }
195              
196 76 100       101 if ($heading) {
197 3         5 my $heading_html = qq{<span id="inputheading">$heading</span>};
198 3 50       4 if ($wrap_heading) {
199 0         0 $heading_html = qq{<div class="$wrap_heading">$heading_html</div>};
200             }
201              
202 3 100 66     21 if ($input_field->{'type'}
203             && ($input_field->{'type'} =~ /radio|checkbox/i))
204             {
205 1         3 $html .= qq{$heading_html<br />};
206             } else {
207 2         4 $html = $heading_html . $html;
208             }
209             }
210              
211 76 100       93 if ($trailing) {
212 1         4 my $trailing_html = qq{<span class="$self->{classes}{inputtrailing}">$trailing</span>};
213 1 50       4 if ($wrap_trailing) {
214 0         0 $trailing_html = qq{<div class="$wrap_trailing">$trailing_html</div>};
215             }
216 1         2 $html .= $trailing_html;
217             }
218              
219 76         156 return $html;
220             }
221              
222             #####################################################################
223             # Usage : _tooltip($content, $url)
224             # Purpose : create tooltip html code
225             # Returns : HTML
226             # Comments :
227             # See Also :
228             #####################################################################
229             sub _tooltip {
230 1     1   3 my $content = shift;
231 1         1 my $url = shift;
232 1         2 $content =~ s/\'/&apos;/g; # Escape for quoting below
233              
234 1         3 return qq{ <a href='#' title='$content' rel='tooltip'><img src="$url" /></a>};
235             }
236              
237             1;
238              
239             =head1 NAME
240              
241             HTML::FormBuilder::Field - Field container used by HTML::FormBuilder
242              
243             =head1 SYNOPSIS
244              
245             my $form = HTML::FormBuilder->new(data => {id => 'testform});
246              
247             my $fieldset = $form->add_fieldset({id => 'fieldset1'});
248              
249             $fieldset->add_field({input => {type => 'text', value => 'Join'}});
250              
251             # Text only, without input fields
252             $fieldset->add_field({
253             comment => {
254             text => 'Please check on checkbox below:',
255             class => 'grd-grid-12',
256             # no extra <br/> before <span> for comment
257             no_new_line => 1,
258             },
259             });
260              
261             # checkbox & explanation text
262             $fieldset->add_field({
263             input => {
264             id => 'tnc',
265             name => 'tnc',
266             type => 'checkbox',
267             trailing => 'I have read & agree to all terms & condition.',
268             # wrap <input> & trailing <span> respectively in <div>, with class:
269             wrap_in_div_class => {
270             input => 'grd-grid-1',
271             trailing => 'grd-grid-11'
272             },
273             },
274             error => {
275             id => 'error_tnc',
276             class => 'errorfield',
277             },
278             validation => [{
279             type => 'checkbox_checked',
280             err_msg => localize('Please agree in order to proceed.'),
281             }],
282             # override div container class for input
283             override_input_class => 'grd-grid-12',
284             });
285              
286             $form->add_field($fieldset_index, {input => {type => 'text', value => 'Join'}});
287              
288             =head1 METHODS
289              
290             =head2 BUILDARGS
291              
292             =head2 build
293              
294             =head2 data
295              
296             =head1 AUTHOR
297              
298             Chylli L<mailto:chylli@binary.com>
299              
300             =head1 CONTRIBUTOR
301              
302             Fayland Lam L<mailto:fayland@binary.com>
303              
304             Tee Shuwn Yuan L<mailto:shuwnyuan@binary.com>
305              
306             =head1 COPYRIGHT AND LICENSE
307              
308             =cut
309