complete MooseX::Types port
[catagits/Reaction.git] / lib / Reaction / UI / ViewPort.pm
1 package Reaction::UI::ViewPort;
2
3 use Reaction::Class;
4 use Scalar::Util qw/blessed/;
5
6 sub DEBUG_EVENTS () { $ENV{REACTION_UI_VIEWPORT_DEBUG_EVENTS} }
7
8 class ViewPort which {
9
10   has location => (isa => 'Str', is => 'rw', required => 1);
11   has layout => (isa => 'Str', is => 'rw', lazy_build => 1);
12   has outer => (isa => 'Reaction::UI::ViewPort', is => 'rw', weak_ref => 1);
13   has inner => (isa => 'Reaction::UI::ViewPort', is => 'rw');
14   has focus_stack => (
15     isa => 'Reaction::UI::FocusStack', is => 'rw', weak_ref => 1
16   );
17   has _tangent_stacks => (
18     isa => 'HashRef', is => 'ro', default => sub { {} }
19   );
20   has ctx => (isa => 'Catalyst', is => 'ro', required => 1);
21
22   implements _build_layout => as {
23     '';
24   };
25
26   implements create_tangent => as {
27     my ($self, $name) = @_;
28     my $t_map = $self->_tangent_stacks;
29     if (exists $t_map->{$name}) {
30       confess "Can't create tangent with already existing name ${name}";
31     }
32     my $loc = join('.', $self->location, $name);
33     my $tangent = Reaction::UI::FocusStack->new(loc_prefix => $loc);
34     $t_map->{$name} = $tangent;
35     return $tangent;
36   };
37
38   implements focus_tangent => as {
39     my ($self, $name) = @_;
40     if (my $tangent = $self->_tangent_stacks->{$name}) {
41       return $tangent;
42     } else {
43       return;
44     }
45   };
46
47   implements focus_tangents => as {
48     return keys %{shift->_tangent_stacks};
49   };
50
51   implements child_event_sinks => as {
52     my $self = shift;
53     return values %{$self->_tangent_stacks};
54   };
55
56   implements apply_events => as {
57     my ($self, $ctx, $events) = @_;
58     return unless keys %$events;
59     $self->apply_child_events($ctx, $events);
60     $self->apply_our_events($ctx, $events);
61   };
62
63   implements apply_child_events => as {
64     my ($self, $ctx, $events) = @_;
65     return unless keys %$events;
66     foreach my $child ($self->child_event_sinks) {
67       confess blessed($child) ."($child) is not a valid object"
68         unless blessed($child) && $child->can('apply_events');
69       $child->apply_events($ctx, $events);
70     }
71   };
72
73   implements apply_our_events => as {
74     my ($self, $ctx, $events) = @_;
75     my @keys = keys %$events;
76     return unless @keys;
77     my $loc = $self->location;
78     my %our_events;
79     foreach my $key (keys %$events) {
80       if ($key =~ m/^${loc}:(.*)$/) {
81         $our_events{$1} = $events->{$key};
82       }
83     }
84     if (keys %our_events) {
85       #warn "$self: events ".join(', ', %our_events)."\n";
86       $self->handle_events(\%our_events);
87     }
88   };
89
90   implements handle_events => as {
91     my ($self, $events) = @_;
92     foreach my $event ($self->accept_events) {
93       if (exists $events->{$event}) {
94         if (DEBUG_EVENTS) {
95           my $name = join(' at ', ref($self), $self->location);
96           $self->ctx->log->debug(
97             "Applying Event: $event on $name with value: "
98             .$events->{$event}
99           );
100         }
101         $self->$event($events->{$event});
102       }
103     }
104   };
105
106   implements accept_events => as { () };
107
108   implements event_id_for => as {
109     my ($self, $name) = @_;
110     return join(':', $self->location, $name);
111   };
112
113   implements sort_by_spec => as {
114     my ($self, $spec, $items) = @_;
115     return $items if not defined $spec;
116
117     my @order;
118     if (ref $spec eq 'ARRAY') {
119       @order = @$spec;
120     }
121     elsif (not ref $spec) {
122       return $items unless length $spec;
123       @order = split /\s+/, $spec;
124     }
125
126     my %order_map = map {$_ => 0} @$items;
127     for my $order_num (0..$#order) {
128       $order_map{ $order[$order_num] } = ($#order - $order_num) + 1;
129     }
130
131     return [sort {$order_map{$b} <=> $order_map{$a}} @$items];
132   };
133
134 };
135
136 1;
137
138
139 =head1 NAME
140
141 Reaction::UI::ViewPort - Page layout building block
142
143 =head1 SYNOPSIS
144
145   # Create a new ViewPort:
146   # $stack isa Reaction::UI::FocusStack object
147   my $vp = $stack->push_viewport('Reaction::UI::ViewPort', layout => 'xthml');
148
149   # Fetch ViewPort higher up the stack (further out)
150   my $outer = $vp->outer();
151
152   # Fetch ViewPort lower down (further in)
153   my $inner = $vp->inner();
154
155   # Create a named tangent stack for this ViewPort
156   my $substack = $vp->create_tangent('name');
157
158   # Retrieve a tangent stack for this ViewPort
159   my $substack = $vp->forcus_tangent('name');
160
161   # Get the names of all the tangent stacks for this ViewPort
162   my @names = $vp->focus_tangents();
163
164   # Fetch all the tangent stacks for this ViewPort
165   # This is called by apply_events
166   my $stacks = $vp->child_event_sinks();
167
168
169   ### The following methods are all called automatically when using
170   ### Reaction::UI::Controller(s)
171   # Resolve current events with this ViewPort
172   $vp->apply_events($ctx, $param_hash);
173
174   # Apply current events to all tangent stacks
175   # This is called by apply_events
176   $vp->apply_child_events($ctx, $params_hash);
177
178   # Apply current events to this ViewPort
179   # This is called by apply_events
180   $vp->apply_our_events($ctx, $params_hash);
181
182 =head1 DESCRIPTION
183
184 A ViewPort describes part of a page, it can be a field, a form or
185 an entire page. ViewPorts are created on a
186 L<Reaction::UI::FocusStack>, usually belonging to a controller or
187 another ViewPort. Each ViewPort knows it's own position in the stack
188 it is in, as well as the stack containing it.
189
190 Each ViewPort has a specific location in the heirarchy of viewports
191 making up a page. The hierarchy is determined as follows: The first
192 ViewPort in a stack is labeled C<0>, the second is C<1> and so on. If
193 a ViewPort is in a named tangent, it's location will contain the name
194 of the tangent in it's location.
195
196 For example, the first ViewPort in the 'left' tangent of the main
197 ViewPort has location C<0.left.0>.
198
199 Several ViewPort attributes are set by
200 L<Reaction::UI::FocusStack/push_viewport> when new ViewPorts are
201 created, these are as follows:
202
203 =over
204
205 =item Automatic:
206
207 =over
208
209 =item outer
210
211 The outer attribute is set to the previous ViewPort in the stack when
212 creating a ViewPort, if the ViewPort is the first in the stack, it
213 will be undef.
214
215 =item inner
216
217 The inner attribute is set to the next ViewPort down in the stack when
218 it is created, if this is the last ViewPort in the stack, it will be
219 undef.
220
221 =item focus_stack
222
223 The focus_stack attribute is set to the L<Reaction::UI::FocusStack>
224 object that created the ViewPort.
225
226 =item ctx
227
228 The ctx attribute will be passed automatically when using
229 L<Reaction::UI::Controller/push_viewport> to create a ViewPort in the
230 base stack of a controller. When creating tangent stacks, you may have
231 to pass it in yourself.
232
233 =back
234
235 =item Optional:
236
237 =over
238
239 =item location
240
241 =item layout
242
243 The layout attribute can either be specifically passed when calling
244 C<push_viewport>, or it will be determined using the last part of the
245 ViewPorts classname.
246
247 =item column_order
248
249 This is generally used by more specialised ViewPorts such as the
250 L<ListView|Reaction::UI::ViewPort::ListView> or
251 L<Action|Reaction::UI::ViewPort::Action>. It can be either a
252 space separated list of column names, or an arrayref of column names.
253
254 =back
255
256 =back
257
258 =head1 METHODS
259
260 =head2 outer
261
262 =over
263
264 =item Arguments: none
265
266 =back
267
268 Fetch the ViewPort outside this one in the page hierarchy.
269
270 =head2 inner
271
272 =over
273
274 =item Arguments: none
275
276 =back
277
278 Fetch the ViewPort inside this one in the page hierarchy.
279
280 =head2 create_tangent
281
282 =over
283
284 =item Arguments: $tangent_name
285
286 =back
287
288 Create a new named L<Reaction::UI::FocusStack> inside this
289 ViewPort. The created FocusStack is returned.
290
291 =head2 focus_tangent
292
293 =over
294
295 =item Arguments: $tangent_name
296
297 =back
298
299 Fetch a named FocusStack from this ViewPort.
300
301 =head2 focus_tangents
302
303 =over
304
305 =item Arguments: none
306
307 =back
308
309 Returns a list of names of all the known tangents in this ViewPort.
310
311 =head2 focus_stack
312
313 Return the L<Reaction::UI::FocusStack> object that this ViewPort is in.
314
315 =head2 apply_events
316
317 =over
318
319 =item Arguments: $ctx, $params_hashref
320
321 =back
322
323 This method is called by the FocusStack object to resolve all events
324 for the ViewPort.
325
326 =head2 apply_child_events
327
328 =over
329
330 =item Arguments: $ctx, $params_hashref
331
332 =back
333
334 Resolve the given events for all the tangents of this ViewPort. Called
335 by L<apply_events>.
336
337 =head2 apply_our_events
338
339 =over
340
341 =item Arguments: $ctx, $events
342
343 =back
344
345 Resolve the given events that match the location of this
346 ViewPort. Called by L<apply_events>.
347
348 =head2 handle_events
349
350 =over
351
352 =item Arguments: $events
353
354 =back
355
356 Actually call the event handlers for this ViewPort. Called by
357 L<apply_our_events>. By default this will do nothing, subclass
358 ViewPort and implement L<accept_events>.
359
360 =head2 accept_events
361
362 =over
363
364 =item Arguments: none
365
366 =back
367
368 Implement this method in a subclass and return a list of events that
369 your ViewPort is accepting.
370
371 =head2 event_id_for
372
373 =over
374
375 =item Arguments: $name
376
377 =back
378
379 Create an id for the given event name and this ViewPort. Generally
380 returns the location and the name, joined with a colon.
381
382 =head2 sort_by_spec
383
384 =over
385
386 =item Arguments: $spec, $items
387
388 =back
389
390 Sorts the given list of items such that the ones that also appear in
391 the spec are at the beginning. This is called by
392 L<Reaction::UI::ViewPort::Action> and
393 L<Reaction::UI::ViewPort::ListView>, and gets passed L<column_order>
394 as the spec argument.
395
396 =head1 AUTHORS
397
398 See L<Reaction::Class> for authors.
399
400 =head1 LICENSE
401
402 See L<Reaction::Class> for the license.
403
404 =cut