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