$self->blessed sense make no
[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);
f6ec638f 12 has layout_args => (isa => 'HashRef', is => 'ro', default => sub { {} });
7adfd53f 13 has outer => (isa => 'Reaction::UI::ViewPort', is => 'rw', weak_ref => 1);
14 has inner => (isa => 'Reaction::UI::ViewPort', is => 'rw');
15 has focus_stack => (
16 isa => 'Reaction::UI::FocusStack', is => 'rw', weak_ref => 1
17 );
18 has _tangent_stacks => (
19 isa => 'HashRef', is => 'ro', default => sub { {} }
20 );
881dd557 21 has ctx => (isa => 'Catalyst', is => 'ro'); #, required => 1);
b8faba69 22
89939ff9 23 implements _build_layout => as {
7adfd53f 24 '';
25 };
b8faba69 26
7adfd53f 27 implements create_tangent => as {
28 my ($self, $name) = @_;
29 my $t_map = $self->_tangent_stacks;
30 if (exists $t_map->{$name}) {
31 confess "Can't create tangent with already existing name ${name}";
32 }
33 my $loc = join('.', $self->location, $name);
34 my $tangent = Reaction::UI::FocusStack->new(loc_prefix => $loc);
35 $t_map->{$name} = $tangent;
36 return $tangent;
37 };
b8faba69 38
7adfd53f 39 implements focus_tangent => as {
40 my ($self, $name) = @_;
41 if (my $tangent = $self->_tangent_stacks->{$name}) {
42 return $tangent;
43 } else {
44 return;
45 }
46 };
b8faba69 47
7adfd53f 48 implements focus_tangents => as {
49 return keys %{shift->_tangent_stacks};
50 };
b8faba69 51
7adfd53f 52 implements child_event_sinks => as {
53 my $self = shift;
54 return values %{$self->_tangent_stacks};
55 };
b8faba69 56
7adfd53f 57 implements apply_events => as {
58 my ($self, $ctx, $events) = @_;
54756bc8 59 return unless keys %$events;
7adfd53f 60 $self->apply_child_events($ctx, $events);
61 $self->apply_our_events($ctx, $events);
62 };
b8faba69 63
7adfd53f 64 implements apply_child_events => as {
65 my ($self, $ctx, $events) = @_;
54756bc8 66 return unless keys %$events;
7adfd53f 67 foreach my $child ($self->child_event_sinks) {
36d54b14 68 confess blessed($child) ."($child) is not a valid object"
69 unless blessed($child) && $child->can('apply_events');
7adfd53f 70 $child->apply_events($ctx, $events);
71 }
72 };
b8faba69 73
7adfd53f 74 implements apply_our_events => as {
75 my ($self, $ctx, $events) = @_;
54756bc8 76 my @keys = keys %$events;
77 return unless @keys;
7adfd53f 78 my $loc = $self->location;
79 my %our_events;
80 foreach my $key (keys %$events) {
81 if ($key =~ m/^${loc}:(.*)$/) {
82 $our_events{$1} = $events->{$key};
83 }
84 }
85 if (keys %our_events) {
86 #warn "$self: events ".join(', ', %our_events)."\n";
87 $self->handle_events(\%our_events);
88 }
89 };
b8faba69 90
7adfd53f 91 implements handle_events => as {
92 my ($self, $events) = @_;
881dd557 93 my $exists = exists $events->{exists};
94 if ($exists) {
95 my %force = $self->force_events;
96 my @need = grep { !exists $events->{$_} } keys %force;
97 @{$events}{@need} = @force{@need};
98 }
7adfd53f 99 foreach my $event ($self->accept_events) {
100 if (exists $events->{$event}) {
d9a3266f 101 if (DEBUG_EVENTS) {
105ffac7 102 my $name = join(' at ', $self, $self->location);
d9a3266f 103 $self->ctx->log->debug(
104 "Applying Event: $event on $name with value: "
881dd557 105 .(defined $events->{$event} ? $events->{$event} : '<undef>')
d9a3266f 106 );
107 }
7adfd53f 108 $self->$event($events->{$event});
109 }
110 }
111 };
b8faba69 112
7adfd53f 113 implements accept_events => as { () };
b8faba69 114
881dd557 115 implements force_events => as { () };
116
7adfd53f 117 implements event_id_for => as {
118 my ($self, $name) = @_;
119 return join(':', $self->location, $name);
120 };
b8faba69 121
7adfd53f 122 implements sort_by_spec => as {
123 my ($self, $spec, $items) = @_;
124 return $items if not defined $spec;
b8faba69 125
7adfd53f 126 my @order;
127 if (ref $spec eq 'ARRAY') {
128 @order = @$spec;
129 }
130 elsif (not ref $spec) {
131 return $items unless length $spec;
132 @order = split /\s+/, $spec;
133 }
b8faba69 134
7adfd53f 135 my %order_map = map {$_ => 0} @$items;
136 for my $order_num (0..$#order) {
137 $order_map{ $order[$order_num] } = ($#order - $order_num) + 1;
138 }
b8faba69 139
7adfd53f 140 return [sort {$order_map{$b} <=> $order_map{$a}} @$items];
141 };
142
143};
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
156 my $vp = $stack->push_viewport('Reaction::UI::ViewPort', layout => 'xthml');
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
256=item column_order
257
258This is generally used by more specialised ViewPorts such as the
259L<ListView|Reaction::UI::ViewPort::ListView> or
5ee24b95 260L<Action|Reaction::UI::ViewPort::Action>. It can be either a
7adfd53f 261space separated list of column names, or an arrayref of column names.
262
263=back
264
265=back
266
267=head1 METHODS
268
269=head2 outer
270
271=over
272
273=item Arguments: none
274
275=back
276
277Fetch the ViewPort outside this one in the page hierarchy.
278
279=head2 inner
280
281=over
282
283=item Arguments: none
284
285=back
286
287Fetch the ViewPort inside this one in the page hierarchy.
288
289=head2 create_tangent
290
291=over
292
293=item Arguments: $tangent_name
294
295=back
296
297Create a new named L<Reaction::UI::FocusStack> inside this
298ViewPort. The created FocusStack is returned.
299
300=head2 focus_tangent
301
302=over
303
304=item Arguments: $tangent_name
305
306=back
307
308Fetch a named FocusStack from this ViewPort.
309
310=head2 focus_tangents
311
312=over
313
314=item Arguments: none
315
316=back
317
318Returns a list of names of all the known tangents in this ViewPort.
319
320=head2 focus_stack
321
322Return the L<Reaction::UI::FocusStack> object that this ViewPort is in.
323
324=head2 apply_events
325
326=over
327
328=item Arguments: $ctx, $params_hashref
329
330=back
331
332This method is called by the FocusStack object to resolve all events
333for the ViewPort.
334
335=head2 apply_child_events
336
337=over
338
339=item Arguments: $ctx, $params_hashref
340
341=back
342
343Resolve the given events for all the tangents of this ViewPort. Called
344by L<apply_events>.
345
346=head2 apply_our_events
347
348=over
349
350=item Arguments: $ctx, $events
351
352=back
353
354Resolve the given events that match the location of this
355ViewPort. Called by L<apply_events>.
356
357=head2 handle_events
358
359=over
360
361=item Arguments: $events
362
363=back
364
365Actually call the event handlers for this ViewPort. Called by
366L<apply_our_events>. By default this will do nothing, subclass
367ViewPort and implement L<accept_events>.
368
369=head2 accept_events
370
371=over
372
373=item Arguments: none
374
375=back
376
377Implement this method in a subclass and return a list of events that
378your ViewPort is accepting.
379
380=head2 event_id_for
381
382=over
383
384=item Arguments: $name
385
386=back
387
388Create an id for the given event name and this ViewPort. Generally
389returns the location and the name, joined with a colon.
390
391=head2 sort_by_spec
392
393=over
394
395=item Arguments: $spec, $items
396
397=back
398
399Sorts the given list of items such that the ones that also appear in
400the spec are at the beginning. This is called by
5ee24b95 401L<Reaction::UI::ViewPort::Action> and
7adfd53f 402L<Reaction::UI::ViewPort::ListView>, and gets passed L<column_order>
403as the spec argument.
404
405=head1 AUTHORS
406
407See L<Reaction::Class> for authors.
408
409=head1 LICENSE
410
411See L<Reaction::Class> for the license.
412
413=cut