File Coverage

blib/lib/Rose/HTML/Form/Field/DateTime/Range.pm
Criterion Covered Total %
statement 84 97 86.6
branch 25 34 73.5
condition 8 18 44.4
subroutine 23 26 88.4
pod 11 18 61.1
total 151 193 78.2


line stmt bran cond sub pod time code
1             package Rose::HTML::Form::Field::DateTime::Range;
2              
3 2     2   1007 use strict;
  2         6  
  2         70  
4              
5 2     2   14 use Rose::HTML::Object::Errors qw(:date);
  2         8  
  2         19  
6              
7             use Rose::HTML::Object::Messages
8 2     2   18 qw(FIELD_ERROR_LABEL_MINIMUM_DATE FIELD_ERROR_LABEL_MAXIMUM_DATE);
  2         6  
  2         14  
9              
10 2     2   609 use Rose::HTML::Form::Field::DateTime::StartDate;
  2         8  
  2         24  
11 2     2   13 use Rose::HTML::Form::Field::DateTime::EndDate;
  2         7  
  2         18  
12              
13 2     2   11 use base 'Rose::HTML::Form::Field::Compound';
  2         8  
  2         1236  
14              
15             use Rose::Object::MakeMethods::Generic
16             (
17 2         17 'scalar --get_set_init' =>
18             [
19             'range_separator_regex',
20             'min_prefix_html',
21             'max_prefix_html',
22             'separator_html',
23             ]
24 2     2   20 );
  2         4  
25              
26             our $VERSION = '0.606';
27              
28             sub build_field
29             {
30 1     1 1 3 my($self) = shift;
31              
32 1         36 $self->add_fields
33             (
34             min =>
35             {
36             type => 'datetime start',
37             error_label_id => FIELD_ERROR_LABEL_MINIMUM_DATE,
38             size => 21,
39             maxlength => 25,
40             },
41              
42             max =>
43             {
44             type => 'datetime end',
45             error_label_id => FIELD_ERROR_LABEL_MAXIMUM_DATE,
46             size => 21,
47             maxlength => 25,
48             },
49             );
50             }
51              
52             sub size
53             {
54 0     0 0 0 my($self) = shift;
55              
56 0 0       0 if(@_)
57             {
58 0         0 $self->field('min')->size(@_);
59 0         0 $self->field('max')->size(@_);
60             }
61              
62 0         0 return $self->field('min')->size(@_);
63             }
64              
65             sub output_format
66             {
67 0     0 0 0 my($self) = shift;
68              
69 0 0       0 if(@_)
70             {
71 0         0 $self->field('min')->output_format(@_);
72 0         0 $self->field('max')->output_format(@_);
73             }
74              
75 0         0 return $self->field('min')->output_format(@_);
76             }
77              
78 1     1 0 6 sub init_range_separator { '#' }
79 1     1 0 26 sub init_range_separator_regex { qr(#|\s+to\s+) }
80              
81             sub range_separator
82             {
83 6     6 1 13 my($self) = shift;
84              
85 6 100       17 if(@_)
86             {
87 1         11 $self->invalidate_output_value;
88 1         4 return $self->{'range_separator'} = shift;
89             }
90              
91             return (defined $self->{'range_separator'}) ? $self->{'range_separator'} :
92 5 100       25 ($self->{'range_separator'} = $self->init_range_separator);
93             }
94              
95             sub deflate_value
96             {
97 3     3 1 8 my($self, $value) = @_;
98 3 50 33     17 return $value unless(ref $value && @$value == 2);
99             return join($self->range_separator,
100 3         9 map { $_->strftime('%Y-%m-%d %H:%M:%S') } @$value);
  6         272  
101             }
102              
103             sub coalesce_value
104             {
105 2     2 1 10 my($self) = shift;
106 4 50       24 return join($self->range_separator, map { defined($_) ? $_ : '' }
107 2         6 map { $self->field($_)->output_value }
  4         15  
108             qw(min max));
109             }
110              
111             sub decompose_value
112             {
113 17     17 1 49 my($self, $value) = @_;
114              
115 17 100       49 return undef unless(defined $value);
116              
117 12         19 my($min, $max);
118              
119 12 100 66     68 if(ref $value eq 'ARRAY' && @$value == 2)
    50          
120             {
121 2         7 ($min, $max) = @$value;
122             }
123             elsif(!ref $value)
124             {
125 10         30 ($min, $max) = split($self->range_separator_regex, $value, 2);
126             }
127             else
128             {
129 0         0 Carp::croak ref($self), " can't handle the input value '$value'";
130             }
131              
132 12   33     155 my $min_date = $self->field('min')->inflate_value($min) || $min;
133 12   33     89 my $max_date = $self->field('max')->inflate_value($max) || $max;
134              
135             return
136             {
137 12         112 min => $min_date,
138             max => $max_date,
139             };
140             }
141              
142             sub inflate_value
143             {
144 7     7 1 16 my($self, $value) = @_;
145              
146 7 100       23 if(ref $value eq 'ARRAY')
147             {
148 2         12 $self->subfield_input_value(min => $value->[0]);
149 2         8 $self->subfield_input_value(max => $value->[1]);
150              
151             #$self->field('min')->_set_input_value($value->[0]);
152             #$self->field('max')->_set_input_value($value->[1]);
153              
154 2         9 return [ $self->field('min')->internal_value, $self->field('max')->internal_value ];
155             }
156             else
157             {
158 5         13 my $values = $self->decompose_value($value);
159 5         23 return [ $values->{'min'}, $values->{'max'} ];
160             }
161             }
162              
163 1     1 0 14 sub init_min_prefix_html { '' }
164 1     1 0 12 sub init_max_prefix_html { '' }
165 1     1 0 19 sub init_separator_html { ' - ' }
166              
167             sub html_field
168             {
169 1     1 1 2103 my($self) = shift;
170              
171 1         6 return '<span class="date-range">' .
172             $self->field('min')->html_label . $self->min_prefix_html . $self->field('min')->html_field . $self->separator_html .
173             $self->field('max')->html_label . $self->max_prefix_html . $self->field('max')->html_field .
174             '</span>';
175             }
176              
177             sub xhtml_field
178             {
179 0     0 1 0 my($self) = shift;
180              
181 0         0 return '<span class="date-range">' .
182             $self->field('min')->xhtml_label . $self->min_prefix_html . $self->field('min')->xhtml_field . $self->separator_html .
183             $self->field('max')->xhtml_label . $self->max_prefix_html . $self->field('max')->xhtml_field .
184             '</span>';
185             }
186              
187             sub html
188             {
189 3     3 1 9 my($self) = shift;
190              
191 3 100       12 return '<table class="date-range">' .
192             '<tr><td class="min">' .
193             $self->field('min')->html_label . $self->min_prefix_html . $self->field('min')->html . '</td><td>' . $self->separator_html . '</td><td class="max">' .
194             $self->field('max')->html_label . $self->max_prefix_html . $self->field('max')->html . '</td></tr>' .
195             ($self->has_errors ? '<tr><td colspan="3">' . $self->html_errors . '</td></tr>' : '') .
196             '</table>';
197             }
198              
199             sub xhtml
200             {
201 2     2 1 1599 my($self) = shift;
202              
203 2 50       9 return '<table class="date-range">' .
204             '<tr><td class="min">' .
205             $self->field('min')->xhtml_label . $self->min_prefix_html . $self->field('min')->xhtml . '</td><td>' . $self->separator_html . '</td><td class="max">' .
206             $self->field('max')->xhtml_label . $self->max_prefix_html . $self->field('max')->xhtml . '</td></tr>' .
207             ($self->has_errors ? '<tr><td colspan="3">' . $self->xhtml_errors . '</td></tr>' : '') .
208             '</table>';
209             }
210              
211             sub validate
212             {
213 3     3 1 8 my($self) = shift;
214              
215 3         13 my $ret = $self->SUPER::validate(@_);
216              
217 3 50       10 return $ret unless($ret);
218              
219 3         5 my @errors;
220              
221 3         8 foreach my $field (qw(min max))
222             {
223 6 100       19 unless($self->field($field)->validate)
224             {
225 1         4 push(@errors, $self->field($field)->errors);
226 1         4 $self->field($field)->set_error;
227             }
228             }
229              
230 3 100       10 unless(@errors)
231             {
232 2         6 my($min, $max) = $self->internal_value;
233              
234 2 100 33     10 if($min && $max && $min > $max)
      66        
235             {
236 1         128 $self->add_error_id(DATE_MIN_GREATER_THAN_MAX);
237 1         12 return 0;
238             }
239             }
240              
241 2 100       102 if(@errors)
242             {
243 1         3 $self->add_errors(map { $_->clone } @errors);
  1         5  
244 1         5 return 0;
245             }
246              
247 1         7 return $ret;
248             }
249              
250             if(__PACKAGE__->localizer->auto_load_messages)
251             {
252             __PACKAGE__->localizer->load_all_messages;
253             }
254              
255 2     2   2952 use utf8; # The __DATA__ section contains UTF-8 text
  2         7  
  2         13  
256              
257             1;
258              
259             __DATA__
260              
261             [% LOCALE en %]
262              
263             DATE_MIN_GREATER_THAN_MAX = "The min date cannot be later than the max date."
264              
265             FIELD_ERROR_LABEL_MINIMUM_DATE = "minimum date"
266             FIELD_ERROR_LABEL_MAXIMUM_DATE = "maximum date"
267              
268             [% LOCALE de %]
269              
270             # von/bis oder doch min/max?
271             DATE_MIN_GREATER_THAN_MAX = "Das Von-Datum darf nicht größer sein, als das Bis-Datum."
272              
273             [% LOCALE fr %]
274              
275             DATE_MIN_GREATER_THAN_MAX = "La date min ne peut pas être postérieure à la date max."
276              
277             [% LOCALE bg %]
278              
279             DATE_MIN_GREATER_THAN_MAX = "Началната дата трябва да бъде преди крайната."
280              
281             __END__
282              
283             =head1 NAME
284              
285             Rose::HTML::Form::Field::DateTime::Range - Compound field for date ranges with separate text fields for the minimum and maximum dates.
286              
287             =head1 SYNOPSIS
288              
289             $field =
290             Rose::HTML::Form::Field::DateTime::Range->new(
291             label => 'Date',
292             name => 'date',
293             default => [ '1/2/2003', '4/5/2006' ]);
294              
295             my($min, $max) = $field->internal_value; # DateTime objects
296              
297             print $min->strftime('%Y-%m-%d'); # "2003-01-02"
298             print $max->strftime('%Y-%m-%d'); # "2006-04-05"
299              
300             $field->input_value('5/6/1980 3pm to 2003-01-06 20:19:55');
301              
302             my $dates = $field->internal_value;
303              
304             print $dates->[0]->hour; # 15
305             print $dates->[1]->hour; # 20
306              
307             print $dates->[0]->day_name; # Tuesday
308              
309             print $field->html;
310              
311             ...
312              
313             =head1 DESCRIPTION
314              
315             L<Rose::HTML::Form::Field::DateTime::Range> is a compound field that represents a date range. It is made up of two subfields: a L<Rose::HTML::Form::Field::DateTime::StartDate> field and a L<Rose::HTML::Form::Field::DateTime::EndDate> field.
316              
317             The internal value of this field is a list (in list context) or reference to an array (in scalar context) of two L<DateTime> objects. The first object is the start date and the second is the end date. If either of fields are not filled in or are otherwise invalid, then the internal value is undef.
318              
319             The input value can be a reference to an array of L<DateTime> objects, or strings that can be inflated into L<DateTime> objects by the L<Rose::HTML::Form::Field::DateTime::StartDate> and L<Rose::HTML::Form::Field::DateTime::EndDate> classes. The input value can also be a concatenation of two such strings, joined by a string that matches the field's L<range_separator_regex|/range_separator_regex>.
320              
321             This class is a good example of a compound field whose internal value consists of more than one object. See L<below|/"SEE ALSO"> for more compound field examples.
322              
323             It is important that this class inherits from L<Rose::HTML::Form::Field::Compound>. See the L<Rose::HTML::Form::Field::Compound> documentation for more information.
324              
325             =head1 OBJECT METHODS
326              
327             =over 4
328              
329             =item B<range_separator [STRING]>
330              
331             Get or set the string used to join the output values of the start and end date subfields in order to produce this field's output value. The default string is "#". Example:
332              
333             $field->input_value([ '1/2/2003', '4/5/2006' ]);
334              
335             # "2003-01-02 00:00:00#2006-04-05 00:00:00"
336             print $field->output_value;
337              
338             =item B<range_separator_regex [REGEX]>
339              
340             Get or set the regular expression used to split an input string into start date and end date portions. The default value is C<qr(#|\s+to\s+)>. Example:
341              
342             $field->input_value('2005-04-20 8pm to 1/7/2006 3:05 AM');
343              
344             my($min, $max) = $field->internal_value;
345              
346             print $min->day_name; # Wednesday
347             print $max->day_name; # Saturday
348              
349             # Change regex, adding support for " - "
350             $field->range_separator_regex(qr(#|\s+(?:to|-)\s+));
351              
352             $field->input_value('2005-04-20 8pm - 1/7/2006 3:05 AM');
353              
354             ($min, $max) = $field->internal_value;
355              
356             print $min->day_name; # Wednesday
357             print $max->day_name; # Saturday
358              
359             Note that the C<range_separator_regex> B<must> match the C<range_separator> string.
360              
361             When setting C<range_separator_regex>, you should use the C<qr> operator to create a pre-compiled regex (as shown in the example above) If you do not, then the regex will be recompiled each time it's used.
362              
363             =back
364              
365             =head1 SEE ALSO
366              
367             Other examples of custom fields:
368              
369             =over 4
370              
371             =item L<Rose::HTML::Form::Field::Email>
372              
373             A text field that only accepts valid email addresses.
374              
375             =item L<Rose::HTML::Form::Field::Time>
376              
377             Uses inflate/deflate to coerce input into a fixed format.
378              
379             =item L<Rose::HTML::Form::Field::DateTime>
380              
381             Uses inflate/deflate to convert input to a L<DateTime> object.
382              
383             =item L<Rose::HTML::Form::Field::PhoneNumber::US::Split>
384              
385             A simple compound field that coalesces multiple subfields into a single value.
386              
387             =item L<Rose::HTML::Form::Field::DateTime::Split::MonthDayYear>
388              
389             A compound field that uses inflate/deflate convert input from multiple subfields into a L<DateTime> object.
390              
391             =item L<Rose::HTML::Form::Field::DateTime::Split::MDYHMS>
392              
393             A compound field that includes other compound fields and uses inflate/deflate convert input from multiple subfields into a L<DateTime> object.
394              
395             =back
396              
397             =head1 AUTHOR
398              
399             John C. Siracusa (siracusa@gmail.com)
400              
401             =head1 LICENSE
402              
403             Copyright (c) 2010 by John C. Siracusa. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.