fix the parameter attributes goof re meta
[catagits/Reaction.git] / lib / Reaction / UI / ViewPort / TimeRangeCollection.pm
CommitLineData
7adfd53f 1package Reaction::UI::ViewPort::TimeRangeCollection;
2
3use Reaction::Class;
4use Reaction::Types::DateTime;
5use Moose::Util::TypeConstraints ();
6use DateTime::Event::Recurrence;
7use aliased 'Reaction::UI::ViewPort::Field::String';
8use aliased 'Reaction::UI::ViewPort::Field::DateTime';
9use aliased 'Reaction::UI::ViewPort::Field::HiddenArray';
10use aliased 'Reaction::UI::ViewPort::Field::TimeRange';
11
12class TimeRangeCollection is 'Reaction::UI::ViewPort', which {
13
6ab43711 14 #has '+layout' => (default => 'timerangecollection');
15
7adfd53f 16 has '+column_order' => (
17 default => sub{[ qw/ time_from time_to pattern repeat_from repeat_to / ]},
18 );
6ab43711 19
7adfd53f 20 has time_from => (
21 isa => 'Reaction::UI::ViewPort::Field::DateTime',
22 is => 'rw', lazy_build => 1,
23 );
6ab43711 24
7adfd53f 25 has time_to => (
26 isa => 'Reaction::UI::ViewPort::Field::DateTime',
27 is => 'rw', lazy_build => 1,
28 );
6ab43711 29
7adfd53f 30 has repeat_from => (
31 isa => 'Reaction::UI::ViewPort::Field::DateTime',
32 is => 'rw', lazy_build => 1,
33 );
6ab43711 34
7adfd53f 35 has repeat_to => (
36 isa => 'Reaction::UI::ViewPort::Field::DateTime',
37 is => 'rw', lazy_build => 1,
38 );
6ab43711 39
7adfd53f 40 has pattern => (
41 isa => 'Reaction::UI::ViewPort::Field::String',
42 # valid_values => [ qw/none daily weekly monthly/ ],
43 is => 'rw', lazy_build => 1,
44 );
6ab43711 45
7adfd53f 46 has range_vps => (isa => 'ArrayRef', is => 'rw', lazy_build => 1,);
6ab43711 47
7adfd53f 48 has max_range_vps => (isa => 'Int', is => 'rw', lazy_build => 1,);
6ab43711 49
7adfd53f 50 has error => (
51 isa => 'Str',
52 is => 'rw',
53 required => 0,
54 );
6ab43711 55
7adfd53f 56 has field_names => (
57 isa => 'ArrayRef', is => 'rw',
58 lazy_build => 1, clearer => 'clear_field_names',
59 );
6ab43711 60
7adfd53f 61 has _field_map => (
62 isa => 'HashRef', is => 'rw', init_arg => 'fields',
63 clearer => '_clear_field_map',
64 predicate => '_has_field_map',
89939ff9 65 lazy_build => 1,
7adfd53f 66 );
6ab43711 67
7adfd53f 68 has on_next_callback => (
69 isa => 'CodeRef',
70 is => 'rw',
71 predicate => 'has_on_next_callback',
72 );
6ab43711 73
7adfd53f 74 implements fields => as { shift->_field_map };
6ab43711 75
89939ff9 76 implements _build_range_vps => as { [] };
6ab43711 77
7adfd53f 78 implements spanset => as {
79 my ($self) = @_;
80 my $spanset = DateTime::SpanSet->empty_set;
81 $spanset = $spanset->union($_->value) for @{$self->range_vps};
82 return $spanset;
83 };
6ab43711 84
7adfd53f 85 implements range_strings => as {
86 my ($self) = @_;
87 return [ map { $_->value_string } @{$self->range_vps} ];
88 };
6ab43711 89
7adfd53f 90 implements remove_range_vp => as {
6ab43711 91 my ($self, $to_remove) = @_;
7adfd53f 92 $self->range_vps([ grep { $_ != $to_remove } @{$self->range_vps} ]);
93 $self->_clear_field_map;
94 $self->clear_field_names;
95 };
6ab43711 96
7adfd53f 97 implements add_range_vp => as {
98 my ($self) = @_;
99 if ($self->can_add) {
100 $self->_clear_field_map;
101 $self->clear_field_names;
102 my @span_info = (
103 $self->time_from->value,
104 $self->time_to->value,
105 (map { $_->has_value ? $_->value : '' }
106 map { $self->$_ } qw/repeat_from repeat_to/),
107 $self->pattern->value,
108 );
109 my $encoded_spanset = join ',', @span_info;
110 my $args = {
111 value_string => $encoded_spanset,
112 parent => $self
113 };
114 my $count = scalar(@{$self->range_vps});
89939ff9 115 my $field = $self->_build_simple_field(TimeRange, 'range-'.$count, $args);
7adfd53f 116 my $d = DateTime::Format::Duration->new( pattern => '%s' );
117 if ($d->format_duration( $self->spanset->intersection($field->value)->duration ) > 0) {
118 # XXX - Stop using the stash here?
119 $self->ctx->stash->{warning} = 'Warning: Most recent time range overlaps '.
120 'with existing time range in this booking.';
121 }
122 #warn "encoded spanset = $encoded_spanset\n";
123 #warn "current range = ".join(', ', (@{$self->range_vps}))."\n";
124 push(@{$self->range_vps}, $field);
125 }
126 };
6ab43711 127
89939ff9 128 implements _build_field_map => as {
7adfd53f 129 my ($self) = @_;
130 my %map;
131 foreach my $field (@{$self->range_vps}) {
132 $map{$field->name} = $field;
133 }
134 foreach my $name (@{$self->column_order}) {
135 $map{$name} = $self->$name;
136 }
137 return \%map;
138 };
6ab43711 139
89939ff9 140 implements _build_field_names => as {
7adfd53f 141 my ($self) = @_;
142 return [
143 (map { $_->name } @{$self->range_vps}),
144 @{$self->column_order}
145 ];
146 };
6ab43711 147
7adfd53f 148 implements can_add => as {
149 my ($self) = @_;
150 my $error;
151 if ($self->time_to->has_value && $self->time_from->has_value) {
152 my $time_to = $self->time_to->value;
153 my $time_from = $self->time_from->value;
6ab43711 154
7adfd53f 155 my ($pattern, $repeat_from, $repeat_to) = ('','','');
156 $pattern = $self->pattern->value if $self->pattern->has_value;
157 $repeat_from = $self->repeat_from->value if $self->repeat_from->has_value;
158 $repeat_to = $self->repeat_to->value if $self->repeat_to->has_value;
6ab43711 159
7adfd53f 160 my $duration = $time_to - $time_from;
161 if ($time_to < $time_from) {
162 $error = 'Please make sure that the Time To is after the Time From.';
163 } elsif ($time_to == $time_from) {
164 $error = 'Your desired booking slot is too small.';
165 } elsif ($pattern && $pattern ne 'none') {
166 my %pattern = (hourly => [ hours => 1 ],
167 daily => [ days => 1 ],
168 weekly => [ days => 7 ],
169 monthly => [ months => 1 ]);
170 my $pattern_comp = DateTime::Duration->compare(
171 $duration, DateTime::Duration->new( @{$pattern{$pattern}} )
172 );
173 if (!$repeat_to || !$repeat_from) {
174 $error = 'Please make sure that you enter a valid range for the '.
175 'repetition period.';
176 } elsif ($time_to == $time_from) {
177 $error = 'Your desired repetition period is too short.';
178 } elsif ($repeat_to && ($repeat_to < $repeat_from)) {
179 $error = 'Please make sure that the Repeat To is after the Repeat From.';
180 } elsif ( ( ($pattern eq 'hourly') && ($pattern_comp > 0) ) ||
181 ( ($pattern eq 'daily') && ($pattern_comp > 0) ) ||
182 ( ($pattern eq 'weekly') && ($pattern_comp > 0) ) ||
183 ( ($pattern eq 'monthly') && ($pattern_comp > 0) ) ) {
184 $error = "Your repetition pattern ($pattern) is too short for your ".
185 "desired booking length.";
186 }
187 }
188 } else {
189 $error = 'Please complete both the Time To and Time From fields.';
190 }
191 $self->error($error);
192 return !defined($error);
193 };
6ab43711 194
89939ff9 195 implements _build_simple_field => as {
7adfd53f 196 my ($self, $class, $name, $args) = @_;
197 return $class->new(
198 name => $name,
199 location => join('-', $self->location, 'field', $name),
200 ctx => $self->ctx,
201 %$args
202 );
203 };
6ab43711 204
89939ff9 205 implements _build_time_to => as {
7adfd53f 206 my ($self) = @_;
89939ff9 207 return $self->_build_simple_field(DateTime, 'time_to', {});
7adfd53f 208 };
6ab43711 209
89939ff9 210 implements _build_time_from => as {
7adfd53f 211 my ($self) = @_;
89939ff9 212 return $self->_build_simple_field(DateTime, 'time_from', {});
7adfd53f 213 };
6ab43711 214
89939ff9 215 implements _build_repeat_to => as {
7adfd53f 216 my ($self) = @_;
89939ff9 217 return $self->_build_simple_field(DateTime, 'repeat_to', {});
7adfd53f 218 };
6ab43711 219
89939ff9 220 implements _build_repeat_from => as {
7adfd53f 221 my ($self) = @_;
89939ff9 222 return $self->_build_simple_field(DateTime, 'repeat_from', {});
7adfd53f 223 };
6ab43711 224
89939ff9 225 implements _build_pattern => as {
7adfd53f 226 my ($self) = @_;
89939ff9 227 return $self->_build_simple_field(String, 'pattern', {});
7adfd53f 228 };
6ab43711 229
7adfd53f 230 implements next => as {
231 $_[0]->on_next_callback->(@_);
232 };
6ab43711 233
7adfd53f 234 override accept_events => sub {
235 my $self = shift;
236 ('add_range_vp', ($self->has_on_next_callback ? ('next') : ()), super());
237 };
6ab43711 238
7adfd53f 239 override child_event_sinks => sub {
240 my ($self) = @_;
241 return ((grep { ref($_) =~ 'Hidden' } values %{$self->_field_map}),
242 (grep { ref($_) !~ 'Hidden' } values %{$self->_field_map}),
243 super());
244 };
6ab43711 245
7adfd53f 246 override apply_events => sub {
247 my ($self, $ctx, $events) = @_;
6ab43711 248
7adfd53f 249 # auto-inflate range fields based on number from hidden field
6ab43711 250
7adfd53f 251 my $max = $events->{$self->location.':max_range_vps'};
252 my @range_vps = map {
253 TimeRange->new(
254 name => "range-$_",
255 location => join('-', $self->location, 'field', 'range', $_),
256 ctx => $self->ctx,
257 parent => $self,
258 )
259 } ($max ? (0 .. $max - 1) : ());
260 $self->range_vps(\@range_vps);
261 $self->_clear_field_map;
262 $self->clear_field_names;
6ab43711 263
7adfd53f 264 # call original event handling
6ab43711 265
7adfd53f 266 super();
6ab43711 267
268 # repack range VPs in case of deletion
269
7adfd53f 270 my $prev_idx = -1;
6ab43711 271
7adfd53f 272 foreach my $vp (@{$self->range_vps}) {
273 my $cur_idx = ($vp->name =~ m/range-(\d+)/);
274 if (($cur_idx - $prev_idx) > 1) {
275 $cur_idx--;
276 my $name = "range-${cur_idx}";
277 $vp->name($name);
278 $vp->location(join('-', $self->location, 'field', $name));
279 }
280 $prev_idx = $cur_idx;
281 }
282 };
283
284};
285
6ab43711 2861;
7adfd53f 287
288=head1 NAME
289
290Reaction::UI::ViewPort::TimeRangeCollection
291
292=head1 SYNOPSIS
293
294 my $trc = $self->push_viewport(TimeRangeCollection,
295 layout => 'avail_search_form',
296 on_apply_callback => $search_callback,
297 name => 'TRC',
298 );
299
300=head1 DESCRIPTION
301
302=head1 ATTRIBUTES
303
304=head2 can_add
305
306=head2 column_order
307
308=head2 error
309
310=head2 field_names
311
312=head2 fields
313
314=head2 layout
315
316=head2 pattern
317
318Typically either: none, daily, weekly or monthly
319
320=head2 max_range_vps
321
322=head2 range_vps
323
324=head2 repeat_from
325
326A DateTime field.
327
328=head2 repeat_to
329
330A DateTime field.
331
332=head2 time_from
333
334A DateTime field.
335
336=head2 time_to
337
338A DateTime field.
339
340=head1 METHODS
341
342=head2 spanset
343
344Returns: $spanset consisting of all the TimeRange spans combined
345
346=head2 range_strings
347
348Returns: ArrayRef of Str consisting of the value_strings of all TimeRange
349VPs
6ab43711 350
7adfd53f 351=head2 remove_range_vp
352
353Arguments: $to_remove
6ab43711 354
7adfd53f 355=head2 add_range_vp
356
357Arguments: $to_add
358
89939ff9 359=head2 _build_simple_field
7adfd53f 360
361Arguments: $class, $name, $args
362where $class is an object, $name is a scalar and $args is a hashref
363
364=head2 next
365
366=head2 on_next_callback
367
368=head2 clear_field_names
369
370=head2 child_event_sinks
371
372=head1 SEE ALSO
373
374=head2 L<Reaction::UI::ViewPort>
375
376=head2 L<Reaction::UI::ViewPort::Field::TimeRange>
377
378=head2 L<Reaction::UI::ViewPort::Field::DateTime>
379
380=head2 L<DateTime::Event::Recurrence>
381
382=head1 AUTHORS
383
384See L<Reaction::Class> for authors.
385
386=head1 LICENSE
387
388See L<Reaction::Class> for the license.
389
390=cut