File Coverage

blib/lib/HTML/FormBuilder/Field.pm
Criterion Covered Total %
statement 112 122 91.8
branch 50 62 80.6
condition 15 20 75.0
subroutine 11 11 100.0
pod 0 2 0.0
total 188 217 86.6


line stmt bran cond sub pod time code
1             package HTML::FormBuilder::Field;
2              
3 7     7   31 use strict;
  7         18  
  7         173  
4 7     7   39 use warnings;
  7         55  
  7         150  
5 7     7   119 use 5.008_005;
  7         23  
6             our $VERSION = '0.11';
7              
8 7     7   39 use Carp;
  7         10  
  7         463  
9 7     7   38 use Scalar::Util qw(weaken blessed);
  7         14  
  7         641  
10              
11 7     7   5079 use Moo;
  7         91445  
  7         43  
12 7     7   18145 use namespace::clean;
  7         78302  
  7         33  
13             extends qw(HTML::FormBuilder::Base);
14              
15             has data => (
16             is => 'ro',
17             isa => sub {
18             my $data = shift;
19             croak('data should be a hashref') unless ref($data) eq 'HASH';
20             },
21             default => sub {
22             {};
23             },
24             );
25              
26             sub BUILDARGS {
27 97     97 0 10802 my ($class, @args) = @_;
28 97 50       410 my %args = (@args % 2) ? %{$args[0]} : @args;
  0         0  
29              
30 97         159 my $data = $args{data};
31              
32             # normalize: if 'input' is not an array, then make it as an array, so that
33             # we can process the array directly
34 97 100 100     509 if ($data->{input} && ref($data->{input}) ne 'ARRAY') {
35 61         177 $data->{input} = [$data->{input}];
36             }
37              
38 97         2098 return \%args;
39             }
40              
41             sub build {
42 71     71 0 145 my $self = shift;
43 71         94 my $env = shift;
44              
45 71         118 my $data = $self->{data};
46              
47 71         99 my $stacked = $env->{stacked};
48 71         143 my $classes = $self->classes;
49              
50 71         104 my $div_span = "div";
51 71         99 my $label_column = $classes->{label_column};
52 71         106 my $input_column = $classes->{input_column};
53 71 50       162 $input_column = $data->{override_input_class} if ($data->{override_input_class});
54              
55 71 50       139 if ($stacked == 0) {
56 0         0 $div_span = "span";
57 0         0 $label_column = "";
58 0         0 $input_column = "";
59             }
60 71         97 my $input_fields_html = '';
61              
62 71         104 my $stacked_attr = {};
63              
64 71 50       164 if ($stacked == 1) {
65 71 50       171 my $class = $data->{'class'} ? " $data->{class}" : '';
66              
67 71 50 33     208 if ($data->{'type'} and $data->{'type'} eq 'hidden') {
68 0         0 $stacked_attr->{class} = $class;
69             } else {
70 71         238 $stacked_attr->{class} = "$classes->{row_padding} $classes->{row} clear$class";
71             }
72             }
73              
74             #create the field label
75 71 100       163 if (defined $data->{'label'}) {
76 56   100     141 my $label_text = $data->{'label'}->{'text'} || '';
77 56         92 undef $data->{'label'}->{'text'};
78 56   100     232 my $required_mark = delete $data->{label}{required_mark} || 0;
79 56         227 my $label_html = $self->_build_element_and_attributes('label', $data->{'label'}, $label_text, {required_mark => $required_mark},);
80              
81             # add a tooltip explanation if given
82 56 100       162 if ($data->{'label'}{'tooltip'}) {
83              
84             # img_url is the url of question mark picture
85 1         5 my $tooltip = _tooltip($data->{'label'}{'tooltip'}{'desc'}, $data->{'label'}{tooltip}{img_url});
86              
87 1         5 $input_fields_html .= qq{<div class="$classes->{extra_tooltip_container}">$label_html$tooltip</div>};
88             } else {
89 55 100       117 my $hide_mobile = $label_text ? "" : $classes->{hide_mobile};
90              
91 55         192 $input_fields_html .= qq{<$div_span class="$label_column $hide_mobile form_label">$label_html</$div_span>};
92             }
93             }
94              
95             # create the input field
96 71 100       181 if (defined $data->{'input'}) {
97              
98             #if there are more than 1 input field in a single row then we generate 1 by 1
99 61         86 my $inputs = $data->{input};
100 61         140 $input_fields_html .= qq{<$div_span class="$input_column">};
101 61         77 foreach my $input (@{$inputs}) {
  61         124  
102 76         184 $input_fields_html .= $self->_build_input($input, $env);
103             }
104             }
105              
106 71 100       191 if (defined $data->{'comment'}) {
107 1   50     5 $data->{'comment'}->{'class'} ||= '';
108              
109 1 50       3 unless ($data->{comment}->{no_new_line}) {
110 1         2 $input_fields_html .= '<br>';
111             }
112 1         5 $input_fields_html .= $self->_build_element_and_attributes('p', $data->{'comment'}, $data->{'comment'}->{'text'});
113             }
114              
115 71 100       193 if (defined $data->{'error'}) {
116              
117             my @errors =
118             ref($data->{'error'}) eq 'ARRAY'
119 1         3 ? @{$data->{error}}
120 48 100       167 : $data->{error};
121              
122 48         77 foreach my $error_box (@errors) {
123 48         156 $input_fields_html .= $self->_build_element_and_attributes('p', $error_box, $error_box->{text});
124             }
125              
126             }
127              
128             #close the input tag
129 71 100       185 if (defined $data->{'input'}) {
130 61         106 $input_fields_html .= '</' . $div_span . '>';
131             }
132              
133 71 50       192 if ($stacked == 1) {
134 71         233 $input_fields_html = $self->_build_element_and_attributes('div', $stacked_attr, $input_fields_html);
135             }
136              
137 71         364 return $input_fields_html;
138             }
139              
140             #####################################################################
141             # Usage : build the input field its own attributes
142             # Purpose : perform checking build the input field according to its own
143             # characteristics
144             # Returns : input field with its attributes in string
145             # Parameters : $input_field in HASH ref for example
146             # $attributes = {'id' => 'test', 'name' => 'test', 'class' => 'myclass'}
147             # Comments : check pod below to understand how to create different input fields
148             # See Also :
149             #####################################################################
150             sub _build_input {
151 76     76   95 my $self = shift;
152 76         92 my $input_field = shift;
153 76         103 my $env = shift;
154              
155 76         89 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         118 my $heading = delete $input_field->{'heading'};
160 76         107 my $trailing = delete $input_field->{'trailing'};
161              
162             # wrap <input>, heading & trailing <span> in <div>
163 76         89 my ($wrap_input, $wrap_heading, $wrap_trailing);
164 76 50       173 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     147 if (eval { $input_field->can('widget_html') }) {
  76 100       802  
    100          
170 15         44 $html = $input_field->widget_html;
171             } elsif ($input_field->{'type'} and $input_field->{'type'} eq 'textarea') {
172 7         12 undef $input_field->{'type'};
173 7   50     30 my $textarea_value = $input_field->{'value'} || '';
174 7         12 undef $input_field->{'value'};
175 7         22 $html = $self->_build_element_and_attributes('textarea', $input_field, $textarea_value);
176             } elsif ($input_field->{'type'}) {
177 53         86 my $type = $input_field->{'type'};
178 53 100       245 if ($type =~ /^(?:text|password)$/i) {
    100          
179 28         66 $input_field->{'class'} .= ' text';
180             } elsif ($type =~ /button|submit/) {
181 1         3 $input_field->{'class'} .= ' button';
182             }
183              
184 53 100       155 my $tag = ($type =~ /button|submit/ ? 'button' : 'input');
185              
186 53         161 $html = $self->_build_element_and_attributes($tag, $input_field);
187              
188 53 100       190 if ($type =~ /button|submit/) {
189 1         4 $html = qq{<span class="$input_field->{class}">$html</span>};
190             }
191             }
192 76 50       190 if ($wrap_input) {
193 0         0 $html = qq{<div class="$wrap_input">$html</div>};
194             }
195              
196 76 100       177 if ($heading) {
197 3         7 my $heading_html = qq{<span id="inputheading">$heading</span>};
198 3 50       7 if ($wrap_heading) {
199 0         0 $heading_html = qq{<div class="$wrap_heading">$heading_html</div>};
200             }
201              
202 3 100 66     20 if ($input_field->{'type'}
203             && ($input_field->{'type'} =~ /radio|checkbox/i))
204             {
205 1         5 $html .= qq{$heading_html<br />};
206             } else {
207 2         5 $html = $heading_html . $html;
208             }
209             }
210              
211 76 100       151 if ($trailing) {
212 1         5 my $trailing_html = qq{<span class="$self->{classes}{inputtrailing}">$trailing</span>};
213 1 50       3 if ($wrap_trailing) {
214 0         0 $trailing_html = qq{<div class="$wrap_trailing">$trailing_html</div>};
215             }
216 1         3 $html .= $trailing_html;
217             }
218              
219 76         247 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   2 my $content = shift;
231 1         2 my $url = shift;
232 1         2 $content =~ s/\'/&apos;/g; # Escape for quoting below
233              
234 1         4 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 AUTHOR
289              
290             Chylli L<chylli@binary.com>
291              
292             =head1 CONTRIBUTOR
293              
294             Fayland Lam L<fayland@binary.com>
295              
296             Tee Shuwn Yuan L<shuwnyuan@binary.com>
297              
298             =head1 COPYRIGHT AND LICENSE
299              
300             =cut
301