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