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