making reaction apply events for buttons defined as type=image
[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 use namespace::clean -except => [ qw(meta) ];
7
8
9 sub DEBUG_EVENTS () { $ENV{REACTION_UI_VIEWPORT_DEBUG_EVENTS} }
10
11 has location => (isa => 'Str', is => 'rw', required => 1);
12 has layout => (isa => 'Str', is => 'rw', lazy_build => 1);
13 has layout_args => (isa => 'HashRef', is => 'ro', default => sub { {} });
14 has outer => (isa => 'Reaction::UI::ViewPort', is => 'rw', weak_ref => 1);
15 has inner => (isa => 'Reaction::UI::ViewPort', is => 'rw');
16 has focus_stack => (
17   isa => 'Reaction::UI::FocusStack', is => 'rw', weak_ref => 1
18 );
19 has _tangent_stacks => (
20   isa => 'HashRef', is => 'ro', default => sub { {} }
21 );
22 has ctx => (isa => 'Catalyst', is => 'ro', weak_ref => 1); #, required => 1);
23
24 sub _build_layout {
25   '';
26 }
27
28 sub create_tangent {
29   my ($self, $name) = @_;
30   my $t_map = $self->_tangent_stacks;
31   if (exists $t_map->{$name}) {
32     confess "Can't create tangent with already existing name ${name}";
33   }
34   my $loc = join('.', $self->location, $name);
35   my $tangent = Reaction::UI::FocusStack->new(loc_prefix => $loc);
36   $t_map->{$name} = $tangent;
37   return $tangent;
38 }
39
40 sub focus_tangent {
41   my ($self, $name) = @_;
42   if (my $tangent = $self->_tangent_stacks->{$name}) {
43     return $tangent;
44   } else {
45     return;
46   }
47 }
48
49 sub focus_tangents {
50   return keys %{shift->_tangent_stacks};
51 }
52
53 sub child_event_sinks {
54   my $self = shift;
55   return values %{$self->_tangent_stacks};
56 }
57
58 sub apply_events {
59   my ($self, $events) = @_;
60   return unless keys %$events;
61   $self->apply_child_events($events);
62   $self->apply_our_events($events);
63 }
64
65 sub apply_child_events {
66   my ($self, $events) = @_;
67   return unless keys %$events;
68   foreach my $child ($self->child_event_sinks) {
69     confess blessed($child) ."($child) is not a valid object"
70       unless blessed($child) && $child->can('apply_events');
71     my $loc = $child->location;
72     my %child_events = map { $_ => delete $events->{$_} }
73       grep { /^${loc}[-:]/ } keys %$events;
74     $child->apply_events(\%child_events);
75   }
76 }
77
78 sub apply_our_events {
79   my ($self, $events) = @_;
80   my $loc = $self->location;
81   my %our_events;
82   foreach my $key (keys %$events) {
83     if ($key =~ m/^${loc}:(.*)$/) {
84       $our_events{$1} = delete $events->{$key};
85     }
86   }
87   $self->handle_events(\%our_events) if keys %our_events;
88 }
89
90 sub handle_events {
91   my ($self, $events) = @_;
92   my $exists = exists $events->{exists};
93   if ($exists) {
94     my %force = $self->force_events;
95     my @need = grep { !exists $events->{$_} } keys %force;
96     @{$events}{@need} = @force{@need};
97   }
98   foreach my $event ($self->accept_events) {
99     if (exists $events->{$event}) {
100       $self->_dump_event($event, $events->{$event}) if DEBUG_EVENTS;
101       $self->$event($events->{$event});
102     }
103   }
104 }
105
106 sub _dump_event {
107   my ( $self, $name, $value ) = @_;
108   my $vp_name = join(' at ', $self, $self->location);
109   print STDERR
110     "Applying Event: $name on $vp_name with value: "
111     . (defined $value ? $value : '<undef>') . "\n";
112 }
113
114 sub accept_events { () }
115
116 sub force_events { () }
117
118 sub event_id_for {
119   my ($self, $name) = @_;
120   return join(':', $self->location, $name);
121 }
122
123 sub sort_by_spec {
124   my ($self, $spec, $items) = @_;
125   return [sort @$items] unless $spec;
126
127   my @order;
128   if (ref $spec eq 'ARRAY') {
129     @order = @$spec;
130   } elsif (not ref $spec) {
131     @order = split /\s+/, $spec;
132   }
133
134   my %order_map = map {$_ => 0} @$items;
135   for my $order_num (0..$#order) {
136     $order_map{ $order[$order_num] } = ($#order - $order_num) + 1;
137   }
138
139   return [sort {$order_map{$b} <=> $order_map{$a}} @$items];
140 }
141
142 __PACKAGE__->meta->make_immutable;
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 => 'xhtml');
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 layout_args
257
258 This read-only hashref attribute will pass all it's keys as variables to the
259 layout at render time. They should be accessible from both the layout templates
260 and the widget, if applicable, through the C<%_> hash.
261
262     $controller->push_viewport(VPName, layout => 'foo', layout_args => { bar => 'bar'});
263     $_{bar} #in widget
264     [% bar %] in template
265
266 =item column_order
267
268 This is generally used by more specialised ViewPorts such as the
269 L<ListView|Reaction::UI::ViewPort::ListView> or
270 L<Action|Reaction::UI::ViewPort::Action>. It can be either a
271 space separated list of column names, or an arrayref of column names.
272
273 =back
274
275 =back
276
277 =head1 METHODS
278
279 =head2 outer
280
281 =over
282
283 =item Arguments: none
284
285 =back
286
287 Fetch the ViewPort outside this one in the page hierarchy.
288
289 =head2 inner
290
291 =over
292
293 =item Arguments: none
294
295 =back
296
297 Fetch the ViewPort inside this one in the page hierarchy.
298
299 =head2 create_tangent
300
301 =over
302
303 =item Arguments: $tangent_name
304
305 =back
306
307 Create a new named L<Reaction::UI::FocusStack> inside this
308 ViewPort. The created FocusStack is returned.
309
310 =head2 focus_tangent
311
312 =over
313
314 =item Arguments: $tangent_name
315
316 =back
317
318 Fetch a named FocusStack from this ViewPort.
319
320 =head2 focus_tangents
321
322 =over
323
324 =item Arguments: none
325
326 =back
327
328 Returns a list of names of all the known tangents in this ViewPort.
329
330 =head2 focus_stack
331
332 Return the L<Reaction::UI::FocusStack> object that this ViewPort is in.
333
334 =head2 apply_events
335
336 =over
337
338 =item Arguments: $ctx, $params_hashref
339
340 =back
341
342 This method is called by the FocusStack object to resolve all events
343 for the ViewPort.
344
345 =head2 apply_child_events
346
347 =over
348
349 =item Arguments: $ctx, $params_hashref
350
351 =back
352
353 Resolve the given events for all the tangents of this ViewPort. Called
354 by L<apply_events>.
355
356 =head2 apply_our_events
357
358 =over
359
360 =item Arguments: $ctx, $events
361
362 =back
363
364 Resolve the given events that match the location of this
365 ViewPort. Called by L<apply_events>.
366
367 =head2 handle_events
368
369 =over
370
371 =item Arguments: $events
372
373 =back
374
375 Actually call the event handlers for this ViewPort. Called by
376 L<apply_our_events>. By default this will do nothing, subclass
377 ViewPort and implement L<accept_events>.
378
379 =head2 accept_events
380
381 =over
382
383 =item Arguments: none
384
385 =back
386
387 Implement this method in a subclass and return a list of events that
388 your ViewPort is accepting.
389
390 =head2 event_id_for
391
392 =over
393
394 =item Arguments: $name
395
396 =back
397
398 Create an id for the given event name and this ViewPort. Generally
399 returns the location and the name, joined with a colon.
400
401 =head2 sort_by_spec
402
403 =over
404
405 =item Arguments: $spec, $items
406
407 =back
408
409 Sorts the given list of items such that the ones that also appear in
410 the spec are at the beginning. This is called by
411 L<Reaction::UI::ViewPort::Action> and
412 L<Reaction::UI::ViewPort::ListView>, and gets passed L<column_order>
413 as the spec argument.
414
415 =head1 AUTHORS
416
417 See L<Reaction::Class> for authors.
418
419 =head1 LICENSE
420
421 See L<Reaction::Class> for the license.
422
423 =cut