making reaction apply events for buttons defined as type=image
[catagits/Reaction.git] / lib / Reaction / UI / ViewPort.pm
CommitLineData
7adfd53f 1package Reaction::UI::ViewPort;
2
3use Reaction::Class;
36d54b14 4use Scalar::Util qw/blessed/;
7adfd53f 5
81393881 6use namespace::clean -except => [ qw(meta) ];
7
8
9sub DEBUG_EVENTS () { $ENV{REACTION_UI_VIEWPORT_DEBUG_EVENTS} }
10
11has location => (isa => 'Str', is => 'rw', required => 1);
12has layout => (isa => 'Str', is => 'rw', lazy_build => 1);
13has layout_args => (isa => 'HashRef', is => 'ro', default => sub { {} });
14has outer => (isa => 'Reaction::UI::ViewPort', is => 'rw', weak_ref => 1);
15has inner => (isa => 'Reaction::UI::ViewPort', is => 'rw');
16has focus_stack => (
17 isa => 'Reaction::UI::FocusStack', is => 'rw', weak_ref => 1
18);
19has _tangent_stacks => (
20 isa => 'HashRef', is => 'ro', default => sub { {} }
21);
8b55efe2 22has ctx => (isa => 'Catalyst', is => 'ro', weak_ref => 1); #, required => 1);
114916fc 23
81393881 24sub _build_layout {
25 '';
114916fc 26}
27
81393881 28sub 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;
114916fc 38}
39
81393881 40sub focus_tangent {
41 my ($self, $name) = @_;
42 if (my $tangent = $self->_tangent_stacks->{$name}) {
7adfd53f 43 return $tangent;
81393881 44 } else {
45 return;
46 }
114916fc 47}
48
81393881 49sub focus_tangents {
50 return keys %{shift->_tangent_stacks};
114916fc 51}
52
81393881 53sub child_event_sinks {
54 my $self = shift;
55 return values %{$self->_tangent_stacks};
114916fc 56}
57
81393881 58sub apply_events {
52f292b2 59 my ($self, $events) = @_;
81393881 60 return unless keys %$events;
52f292b2 61 $self->apply_child_events($events);
62 $self->apply_our_events($events);
114916fc 63}
64
81393881 65sub apply_child_events {
52f292b2 66 my ($self, $events) = @_;
81393881 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');
e15b0d50 71 my $loc = $child->location;
72 my %child_events = map { $_ => delete $events->{$_} }
73 grep { /^${loc}[-:]/ } keys %$events;
74 $child->apply_events(\%child_events);
81393881 75 }
114916fc 76}
77
81393881 78sub apply_our_events {
52f292b2 79 my ($self, $events) = @_;
81393881 80 my $loc = $self->location;
81 my %our_events;
82 foreach my $key (keys %$events) {
83 if ($key =~ m/^${loc}:(.*)$/) {
e15b0d50 84 $our_events{$1} = delete $events->{$key};
881dd557 85 }
81393881 86 }
e15b0d50 87 $self->handle_events(\%our_events) if keys %our_events;
114916fc 88}
89
81393881 90sub 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}) {
5aec9cb9 100 $self->_dump_event($event, $events->{$event}) if DEBUG_EVENTS;
81393881 101 $self->$event($events->{$event});
7adfd53f 102 }
81393881 103 }
114916fc 104}
105
5aec9cb9 106sub _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
114916fc 114sub accept_events { () }
115
116sub force_events { () }
117
81393881 118sub event_id_for {
119 my ($self, $name) = @_;
120 return join(':', $self->location, $name);
114916fc 121}
122
81393881 123sub sort_by_spec {
124 my ($self, $spec, $items) = @_;
f10fd4d2 125 return [sort @$items] unless $spec;
81393881 126
127 my @order;
128 if (ref $spec eq 'ARRAY') {
129 @order = @$spec;
f10fd4d2 130 } elsif (not ref $spec) {
81393881 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];
114916fc 140}
b8faba69 141
81393881 142__PACKAGE__->meta->make_immutable;
7adfd53f 143
7adfd53f 144
1451;
146
147
148=head1 NAME
149
150Reaction::UI::ViewPort - Page layout building block
151
152=head1 SYNOPSIS
153
154 # Create a new ViewPort:
155 # $stack isa Reaction::UI::FocusStack object
b807f8bf 156 my $vp = $stack->push_viewport('Reaction::UI::ViewPort', layout => 'xhtml');
7adfd53f 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
b8faba69 183 # Apply current events to all tangent stacks
7adfd53f 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
193A ViewPort describes part of a page, it can be a field, a form or
194an entire page. ViewPorts are created on a
195L<Reaction::UI::FocusStack>, usually belonging to a controller or
196another ViewPort. Each ViewPort knows it's own position in the stack
197it is in, as well as the stack containing it.
198
199Each ViewPort has a specific location in the heirarchy of viewports
200making up a page. The hierarchy is determined as follows: The first
201ViewPort in a stack is labeled C<0>, the second is C<1> and so on. If
202a ViewPort is in a named tangent, it's location will contain the name
203of the tangent in it's location.
204
205For example, the first ViewPort in the 'left' tangent of the main
206ViewPort has location C<0.left.0>.
207
208Several ViewPort attributes are set by
209L<Reaction::UI::FocusStack/push_viewport> when new ViewPorts are
210created, these are as follows:
211
212=over
213
214=item Automatic:
215
216=over
217
218=item outer
219
220The outer attribute is set to the previous ViewPort in the stack when
221creating a ViewPort, if the ViewPort is the first in the stack, it
222will be undef.
223
224=item inner
225
226The inner attribute is set to the next ViewPort down in the stack when
227it is created, if this is the last ViewPort in the stack, it will be
228undef.
229
230=item focus_stack
231
232The focus_stack attribute is set to the L<Reaction::UI::FocusStack>
233object that created the ViewPort.
234
235=item ctx
236
237The ctx attribute will be passed automatically when using
238L<Reaction::UI::Controller/push_viewport> to create a ViewPort in the
239base stack of a controller. When creating tangent stacks, you may have
240to pass it in yourself.
241
242=back
243
244=item Optional:
245
246=over
247
248=item location
249
250=item layout
251
252The layout attribute can either be specifically passed when calling
253C<push_viewport>, or it will be determined using the last part of the
254ViewPorts classname.
255
4ad1eed3 256=item layout_args
257
258This read-only hashref attribute will pass all it's keys as variables to the
259layout at render time. They should be accessible from both the layout templates
260and 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
7adfd53f 266=item column_order
267
268This is generally used by more specialised ViewPorts such as the
269L<ListView|Reaction::UI::ViewPort::ListView> or
5ee24b95 270L<Action|Reaction::UI::ViewPort::Action>. It can be either a
7adfd53f 271space 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
287Fetch 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
297Fetch 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
307Create a new named L<Reaction::UI::FocusStack> inside this
308ViewPort. The created FocusStack is returned.
309
310=head2 focus_tangent
311
312=over
313
314=item Arguments: $tangent_name
315
316=back
317
318Fetch a named FocusStack from this ViewPort.
319
320=head2 focus_tangents
321
322=over
323
324=item Arguments: none
325
326=back
327
328Returns a list of names of all the known tangents in this ViewPort.
329
330=head2 focus_stack
331
332Return 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
342This method is called by the FocusStack object to resolve all events
343for the ViewPort.
344
345=head2 apply_child_events
346
347=over
348
349=item Arguments: $ctx, $params_hashref
350
351=back
352
353Resolve the given events for all the tangents of this ViewPort. Called
354by L<apply_events>.
355
356=head2 apply_our_events
357
358=over
359
360=item Arguments: $ctx, $events
361
362=back
363
364Resolve the given events that match the location of this
365ViewPort. Called by L<apply_events>.
366
367=head2 handle_events
368
369=over
370
371=item Arguments: $events
372
373=back
374
375Actually call the event handlers for this ViewPort. Called by
376L<apply_our_events>. By default this will do nothing, subclass
377ViewPort and implement L<accept_events>.
378
379=head2 accept_events
380
381=over
382
383=item Arguments: none
384
385=back
386
387Implement this method in a subclass and return a list of events that
388your ViewPort is accepting.
389
390=head2 event_id_for
391
392=over
393
394=item Arguments: $name
395
396=back
397
398Create an id for the given event name and this ViewPort. Generally
399returns 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
409Sorts the given list of items such that the ones that also appear in
410the spec are at the beginning. This is called by
5ee24b95 411L<Reaction::UI::ViewPort::Action> and
7adfd53f 412L<Reaction::UI::ViewPort::ListView>, and gets passed L<column_order>
413as the spec argument.
414
415=head1 AUTHORS
416
417See L<Reaction::Class> for authors.
418
419=head1 LICENSE
420
421See L<Reaction::Class> for the license.
422
423=cut