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