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