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