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'); #, required => 1);
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}";
32 my $loc = join('.', $self->location, $name);
33 my $tangent = Reaction::UI::FocusStack->new(loc_prefix => $loc);
34 $t_map->{$name} = $tangent;
38 my ($self, $name) = @_;
39 if (my $tangent = $self->_tangent_stacks->{$name}) {
46 return keys %{shift->_tangent_stacks};
48 sub child_event_sinks {
50 return values %{$self->_tangent_stacks};
53 my ($self, $events) = @_;
54 return unless keys %$events;
55 $self->apply_child_events($events);
56 $self->apply_our_events($events);
58 sub apply_child_events {
59 my ($self, $events) = @_;
60 return unless keys %$events;
61 foreach my $child ($self->child_event_sinks) {
62 confess blessed($child) ."($child) is not a valid object"
63 unless blessed($child) && $child->can('apply_events');
64 $child->apply_events($events);
67 sub apply_our_events {
68 my ($self, $events) = @_;
69 my @keys = keys %$events;
71 my $loc = $self->location;
73 foreach my $key (keys %$events) {
74 if ($key =~ m/^${loc}:(.*)$/) {
75 $our_events{$1} = $events->{$key};
78 if (keys %our_events) {
79 #warn "$self: events ".join(', ', %our_events)."\n";
80 $self->handle_events(\%our_events);
84 my ($self, $events) = @_;
85 my $exists = exists $events->{exists};
87 my %force = $self->force_events;
88 my @need = grep { !exists $events->{$_} } keys %force;
89 @{$events}{@need} = @force{@need};
91 foreach my $event ($self->accept_events) {
92 if (exists $events->{$event}) {
94 my $name = join(' at ', $self, $self->location);
96 "Applying Event: $event on $name with value: "
97 .(defined $events->{$event} ? $events->{$event} : '<undef>');
99 $self->$event($events->{$event});
103 sub accept_events { () };
104 sub force_events { () };
106 my ($self, $name) = @_;
107 return join(':', $self->location, $name);
110 my ($self, $spec, $items) = @_;
111 return $items if not defined $spec;
114 if (ref $spec eq 'ARRAY') {
117 elsif (not ref $spec) {
118 return $items unless length $spec;
119 @order = split /\s+/, $spec;
122 my %order_map = map {$_ => 0} @$items;
123 for my $order_num (0..$#order) {
124 $order_map{ $order[$order_num] } = ($#order - $order_num) + 1;
127 return [sort {$order_map{$b} <=> $order_map{$a}} @$items];
130 __PACKAGE__->meta->make_immutable;
138 Reaction::UI::ViewPort - Page layout building block
142 # Create a new ViewPort:
143 # $stack isa Reaction::UI::FocusStack object
144 my $vp = $stack->push_viewport('Reaction::UI::ViewPort', layout => 'xthml');
146 # Fetch ViewPort higher up the stack (further out)
147 my $outer = $vp->outer();
149 # Fetch ViewPort lower down (further in)
150 my $inner = $vp->inner();
152 # Create a named tangent stack for this ViewPort
153 my $substack = $vp->create_tangent('name');
155 # Retrieve a tangent stack for this ViewPort
156 my $substack = $vp->forcus_tangent('name');
158 # Get the names of all the tangent stacks for this ViewPort
159 my @names = $vp->focus_tangents();
161 # Fetch all the tangent stacks for this ViewPort
162 # This is called by apply_events
163 my $stacks = $vp->child_event_sinks();
166 ### The following methods are all called automatically when using
167 ### Reaction::UI::Controller(s)
168 # Resolve current events with this ViewPort
169 $vp->apply_events($ctx, $param_hash);
171 # Apply current events to all tangent stacks
172 # This is called by apply_events
173 $vp->apply_child_events($ctx, $params_hash);
175 # Apply current events to this ViewPort
176 # This is called by apply_events
177 $vp->apply_our_events($ctx, $params_hash);
181 A ViewPort describes part of a page, it can be a field, a form or
182 an entire page. ViewPorts are created on a
183 L<Reaction::UI::FocusStack>, usually belonging to a controller or
184 another ViewPort. Each ViewPort knows it's own position in the stack
185 it is in, as well as the stack containing it.
187 Each ViewPort has a specific location in the heirarchy of viewports
188 making up a page. The hierarchy is determined as follows: The first
189 ViewPort in a stack is labeled C<0>, the second is C<1> and so on. If
190 a ViewPort is in a named tangent, it's location will contain the name
191 of the tangent in it's location.
193 For example, the first ViewPort in the 'left' tangent of the main
194 ViewPort has location C<0.left.0>.
196 Several ViewPort attributes are set by
197 L<Reaction::UI::FocusStack/push_viewport> when new ViewPorts are
198 created, these are as follows:
208 The outer attribute is set to the previous ViewPort in the stack when
209 creating a ViewPort, if the ViewPort is the first in the stack, it
214 The inner attribute is set to the next ViewPort down in the stack when
215 it is created, if this is the last ViewPort in the stack, it will be
220 The focus_stack attribute is set to the L<Reaction::UI::FocusStack>
221 object that created the ViewPort.
225 The ctx attribute will be passed automatically when using
226 L<Reaction::UI::Controller/push_viewport> to create a ViewPort in the
227 base stack of a controller. When creating tangent stacks, you may have
228 to pass it in yourself.
240 The layout attribute can either be specifically passed when calling
241 C<push_viewport>, or it will be determined using the last part of the
246 This is generally used by more specialised ViewPorts such as the
247 L<ListView|Reaction::UI::ViewPort::ListView> or
248 L<Action|Reaction::UI::ViewPort::Action>. It can be either a
249 space separated list of column names, or an arrayref of column names.
261 =item Arguments: none
265 Fetch the ViewPort outside this one in the page hierarchy.
271 =item Arguments: none
275 Fetch the ViewPort inside this one in the page hierarchy.
277 =head2 create_tangent
281 =item Arguments: $tangent_name
285 Create a new named L<Reaction::UI::FocusStack> inside this
286 ViewPort. The created FocusStack is returned.
292 =item Arguments: $tangent_name
296 Fetch a named FocusStack from this ViewPort.
298 =head2 focus_tangents
302 =item Arguments: none
306 Returns a list of names of all the known tangents in this ViewPort.
310 Return the L<Reaction::UI::FocusStack> object that this ViewPort is in.
316 =item Arguments: $ctx, $params_hashref
320 This method is called by the FocusStack object to resolve all events
323 =head2 apply_child_events
327 =item Arguments: $ctx, $params_hashref
331 Resolve the given events for all the tangents of this ViewPort. Called
334 =head2 apply_our_events
338 =item Arguments: $ctx, $events
342 Resolve the given events that match the location of this
343 ViewPort. Called by L<apply_events>.
349 =item Arguments: $events
353 Actually call the event handlers for this ViewPort. Called by
354 L<apply_our_events>. By default this will do nothing, subclass
355 ViewPort and implement L<accept_events>.
361 =item Arguments: none
365 Implement this method in a subclass and return a list of events that
366 your ViewPort is accepting.
372 =item Arguments: $name
376 Create an id for the given event name and this ViewPort. Generally
377 returns the location and the name, joined with a colon.
383 =item Arguments: $spec, $items
387 Sorts the given list of items such that the ones that also appear in
388 the spec are at the beginning. This is called by
389 L<Reaction::UI::ViewPort::Action> and
390 L<Reaction::UI::ViewPort::ListView>, and gets passed L<column_order>
391 as the spec argument.
395 See L<Reaction::Class> for authors.
399 See L<Reaction::Class> for the license.