1 package Reaction::UI::ViewPort;
4 use Scalar::Util qw/blessed/;
6 use namespace::clean -except => [ qw(meta) ];
9 sub DEBUG_EVENTS () { $ENV{REACTION_UI_VIEWPORT_DEBUG_EVENTS} }
11 has location => (isa => 'Str', is => 'rw', required => 1);
12 has layout => (isa => 'Str', is => 'rw', lazy_build => 1);
13 has layout_args => (isa => 'HashRef', is => 'ro', default => sub { {} });
14 has outer => (isa => 'Reaction::UI::ViewPort', is => 'rw', weak_ref => 1);
15 has inner => (isa => 'Reaction::UI::ViewPort', is => 'rw');
17 isa => 'Reaction::UI::FocusStack', is => 'rw', weak_ref => 1
19 has _tangent_stacks => (
20 isa => 'HashRef', is => 'ro', default => sub { {} }
22 has ctx => (isa => 'Catalyst', is => 'ro', weak_ref => 1); #, required => 1);
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}";
34 my $loc = join('.', $self->location, $name);
35 my $tangent = Reaction::UI::FocusStack->new(loc_prefix => $loc);
36 $t_map->{$name} = $tangent;
41 my ($self, $name) = @_;
42 if (my $tangent = $self->_tangent_stacks->{$name}) {
50 return keys %{shift->_tangent_stacks};
53 sub child_event_sinks {
55 return values %{$self->_tangent_stacks};
59 my ($self, $events) = @_;
60 return unless keys %$events;
61 $self->apply_child_events($events);
62 $self->apply_our_events($events);
65 sub apply_child_events {
66 my ($self, $events) = @_;
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');
71 my $loc = $child->location;
72 my %child_events = map { $_ => delete $events->{$_} }
73 grep { /^${loc}[-:]/ } keys %$events;
74 $child->apply_events(\%child_events);
78 sub apply_our_events {
79 my ($self, $events) = @_;
80 my $loc = $self->location;
82 foreach my $key (keys %$events) {
83 if ($key =~ m/^${loc}:(.*)$/) {
84 $our_events{$1} = delete $events->{$key};
87 $self->handle_events(\%our_events) if keys %our_events;
91 my ($self, $events) = @_;
92 my $exists = exists $events->{exists};
94 my %force = $self->force_events;
95 my @need = grep { !exists $events->{$_} } keys %force;
96 @{$events}{@need} = @force{@need};
98 foreach my $event ($self->accept_events) {
99 if (exists $events->{$event}) {
100 $self->_dump_event($event, $events->{$event}) if DEBUG_EVENTS;
101 $self->$event($events->{$event});
107 my ( $self, $name, $value ) = @_;
108 my $vp_name = join(' at ', $self, $self->location);
110 "Applying Event: $name on $vp_name with value: "
111 . (defined $value ? $value : '<undef>') . "\n";
114 sub accept_events { () }
116 sub force_events { () }
119 my ($self, $name) = @_;
120 return join(':', $self->location, $name);
124 my ($self, $spec, $items) = @_;
125 return [sort @$items] unless $spec;
128 if (ref $spec eq 'ARRAY') {
130 } elsif (not ref $spec) {
131 @order = split /\s+/, $spec;
134 my %order_map = map {$_ => 0} @$items;
135 for my $order_num (0..$#order) {
136 $order_map{ $order[$order_num] } = ($#order - $order_num) + 1;
139 return [sort {$order_map{$b} <=> $order_map{$a}} @$items];
142 __PACKAGE__->meta->make_immutable;
150 Reaction::UI::ViewPort - Page layout building block
154 # Create a new ViewPort:
155 # $stack isa Reaction::UI::FocusStack object
156 my $vp = $stack->push_viewport('Reaction::UI::ViewPort', layout => 'xhtml');
158 # Fetch ViewPort higher up the stack (further out)
159 my $outer = $vp->outer();
161 # Fetch ViewPort lower down (further in)
162 my $inner = $vp->inner();
164 # Create a named tangent stack for this ViewPort
165 my $substack = $vp->create_tangent('name');
167 # Retrieve a tangent stack for this ViewPort
168 my $substack = $vp->forcus_tangent('name');
170 # Get the names of all the tangent stacks for this ViewPort
171 my @names = $vp->focus_tangents();
173 # Fetch all the tangent stacks for this ViewPort
174 # This is called by apply_events
175 my $stacks = $vp->child_event_sinks();
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);
183 # Apply current events to all tangent stacks
184 # This is called by apply_events
185 $vp->apply_child_events($ctx, $params_hash);
187 # Apply current events to this ViewPort
188 # This is called by apply_events
189 $vp->apply_our_events($ctx, $params_hash);
193 A ViewPort describes part of a page, it can be a field, a form or
194 an entire page. ViewPorts are created on a
195 L<Reaction::UI::FocusStack>, usually belonging to a controller or
196 another ViewPort. Each ViewPort knows it's own position in the stack
197 it is in, as well as the stack containing it.
199 Each ViewPort has a specific location in the heirarchy of viewports
200 making up a page. The hierarchy is determined as follows: The first
201 ViewPort in a stack is labeled C<0>, the second is C<1> and so on. If
202 a ViewPort is in a named tangent, it's location will contain the name
203 of the tangent in it's location.
205 For example, the first ViewPort in the 'left' tangent of the main
206 ViewPort has location C<0.left.0>.
208 Several ViewPort attributes are set by
209 L<Reaction::UI::FocusStack/push_viewport> when new ViewPorts are
210 created, these are as follows:
220 The outer attribute is set to the previous ViewPort in the stack when
221 creating a ViewPort, if the ViewPort is the first in the stack, it
226 The inner attribute is set to the next ViewPort down in the stack when
227 it is created, if this is the last ViewPort in the stack, it will be
232 The focus_stack attribute is set to the L<Reaction::UI::FocusStack>
233 object that created the ViewPort.
237 The ctx attribute will be passed automatically when using
238 L<Reaction::UI::Controller/push_viewport> to create a ViewPort in the
239 base stack of a controller. When creating tangent stacks, you may have
240 to pass it in yourself.
252 The layout attribute can either be specifically passed when calling
253 C<push_viewport>, or it will be determined using the last part of the
258 This read-only hashref attribute will pass all it's keys as variables to the
259 layout at render time. They should be accessible from both the layout templates
260 and the widget, if applicable, through the C<%_> hash.
262 $controller->push_viewport(VPName, layout => 'foo', layout_args => { bar => 'bar'});
264 [% bar %] in template
268 This is generally used by more specialised ViewPorts such as the
269 L<ListView|Reaction::UI::ViewPort::ListView> or
270 L<Action|Reaction::UI::ViewPort::Action>. It can be either a
271 space separated list of column names, or an arrayref of column names.
283 =item Arguments: none
287 Fetch the ViewPort outside this one in the page hierarchy.
293 =item Arguments: none
297 Fetch the ViewPort inside this one in the page hierarchy.
299 =head2 create_tangent
303 =item Arguments: $tangent_name
307 Create a new named L<Reaction::UI::FocusStack> inside this
308 ViewPort. The created FocusStack is returned.
314 =item Arguments: $tangent_name
318 Fetch a named FocusStack from this ViewPort.
320 =head2 focus_tangents
324 =item Arguments: none
328 Returns a list of names of all the known tangents in this ViewPort.
332 Return the L<Reaction::UI::FocusStack> object that this ViewPort is in.
338 =item Arguments: $ctx, $params_hashref
342 This method is called by the FocusStack object to resolve all events
345 =head2 apply_child_events
349 =item Arguments: $ctx, $params_hashref
353 Resolve the given events for all the tangents of this ViewPort. Called
356 =head2 apply_our_events
360 =item Arguments: $ctx, $events
364 Resolve the given events that match the location of this
365 ViewPort. Called by L<apply_events>.
371 =item Arguments: $events
375 Actually call the event handlers for this ViewPort. Called by
376 L<apply_our_events>. By default this will do nothing, subclass
377 ViewPort and implement L<accept_events>.
383 =item Arguments: none
387 Implement this method in a subclass and return a list of events that
388 your ViewPort is accepting.
394 =item Arguments: $name
398 Create an id for the given event name and this ViewPort. Generally
399 returns the location and the name, joined with a colon.
405 =item Arguments: $spec, $items
409 Sorts the given list of items such that the ones that also appear in
410 the spec are at the beginning. This is called by
411 L<Reaction::UI::ViewPort::Action> and
412 L<Reaction::UI::ViewPort::ListView>, and gets passed L<column_order>
413 as the spec argument.
417 See L<Reaction::Class> for authors.
421 See L<Reaction::Class> for the license.