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