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