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