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, $ctx, $events) = @_;
54 return unless keys %$events;
55 $self->apply_child_events($ctx, $events);
56 $self->apply_our_events($ctx, $events);
58 sub apply_child_events {
59 my ($self, $ctx, $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($ctx, $events);
67 sub apply_our_events {
68 my ($self, $ctx, $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);
95 $self->ctx->log->debug(
96 "Applying Event: $event on $name with value: "
97 .(defined $events->{$event} ? $events->{$event} : '<undef>')
100 $self->$event($events->{$event});
104 sub accept_events { () };
105 sub force_events { () };
107 my ($self, $name) = @_;
108 return join(':', $self->location, $name);
111 my ($self, $spec, $items) = @_;
112 return $items if not defined $spec;
115 if (ref $spec eq 'ARRAY') {
118 elsif (not ref $spec) {
119 return $items unless length $spec;
120 @order = split /\s+/, $spec;
123 my %order_map = map {$_ => 0} @$items;
124 for my $order_num (0..$#order) {
125 $order_map{ $order[$order_num] } = ($#order - $order_num) + 1;
128 return [sort {$order_map{$b} <=> $order_map{$a}} @$items];
131 __PACKAGE__->meta->make_immutable;
139 Reaction::UI::ViewPort - Page layout building block
143 # Create a new ViewPort:
144 # $stack isa Reaction::UI::FocusStack object
145 my $vp = $stack->push_viewport('Reaction::UI::ViewPort', layout => 'xthml');
147 # Fetch ViewPort higher up the stack (further out)
148 my $outer = $vp->outer();
150 # Fetch ViewPort lower down (further in)
151 my $inner = $vp->inner();
153 # Create a named tangent stack for this ViewPort
154 my $substack = $vp->create_tangent('name');
156 # Retrieve a tangent stack for this ViewPort
157 my $substack = $vp->forcus_tangent('name');
159 # Get the names of all the tangent stacks for this ViewPort
160 my @names = $vp->focus_tangents();
162 # Fetch all the tangent stacks for this ViewPort
163 # This is called by apply_events
164 my $stacks = $vp->child_event_sinks();
167 ### The following methods are all called automatically when using
168 ### Reaction::UI::Controller(s)
169 # Resolve current events with this ViewPort
170 $vp->apply_events($ctx, $param_hash);
172 # Apply current events to all tangent stacks
173 # This is called by apply_events
174 $vp->apply_child_events($ctx, $params_hash);
176 # Apply current events to this ViewPort
177 # This is called by apply_events
178 $vp->apply_our_events($ctx, $params_hash);
182 A ViewPort describes part of a page, it can be a field, a form or
183 an entire page. ViewPorts are created on a
184 L<Reaction::UI::FocusStack>, usually belonging to a controller or
185 another ViewPort. Each ViewPort knows it's own position in the stack
186 it is in, as well as the stack containing it.
188 Each ViewPort has a specific location in the heirarchy of viewports
189 making up a page. The hierarchy is determined as follows: The first
190 ViewPort in a stack is labeled C<0>, the second is C<1> and so on. If
191 a ViewPort is in a named tangent, it's location will contain the name
192 of the tangent in it's location.
194 For example, the first ViewPort in the 'left' tangent of the main
195 ViewPort has location C<0.left.0>.
197 Several ViewPort attributes are set by
198 L<Reaction::UI::FocusStack/push_viewport> when new ViewPorts are
199 created, these are as follows:
209 The outer attribute is set to the previous ViewPort in the stack when
210 creating a ViewPort, if the ViewPort is the first in the stack, it
215 The inner attribute is set to the next ViewPort down in the stack when
216 it is created, if this is the last ViewPort in the stack, it will be
221 The focus_stack attribute is set to the L<Reaction::UI::FocusStack>
222 object that created the ViewPort.
226 The ctx attribute will be passed automatically when using
227 L<Reaction::UI::Controller/push_viewport> to create a ViewPort in the
228 base stack of a controller. When creating tangent stacks, you may have
229 to pass it in yourself.
241 The layout attribute can either be specifically passed when calling
242 C<push_viewport>, or it will be determined using the last part of the
247 This is generally used by more specialised ViewPorts such as the
248 L<ListView|Reaction::UI::ViewPort::ListView> or
249 L<Action|Reaction::UI::ViewPort::Action>. It can be either a
250 space separated list of column names, or an arrayref of column names.
262 =item Arguments: none
266 Fetch the ViewPort outside this one in the page hierarchy.
272 =item Arguments: none
276 Fetch the ViewPort inside this one in the page hierarchy.
278 =head2 create_tangent
282 =item Arguments: $tangent_name
286 Create a new named L<Reaction::UI::FocusStack> inside this
287 ViewPort. The created FocusStack is returned.
293 =item Arguments: $tangent_name
297 Fetch a named FocusStack from this ViewPort.
299 =head2 focus_tangents
303 =item Arguments: none
307 Returns a list of names of all the known tangents in this ViewPort.
311 Return the L<Reaction::UI::FocusStack> object that this ViewPort is in.
317 =item Arguments: $ctx, $params_hashref
321 This method is called by the FocusStack object to resolve all events
324 =head2 apply_child_events
328 =item Arguments: $ctx, $params_hashref
332 Resolve the given events for all the tangents of this ViewPort. Called
335 =head2 apply_our_events
339 =item Arguments: $ctx, $events
343 Resolve the given events that match the location of this
344 ViewPort. Called by L<apply_events>.
350 =item Arguments: $events
354 Actually call the event handlers for this ViewPort. Called by
355 L<apply_our_events>. By default this will do nothing, subclass
356 ViewPort and implement L<accept_events>.
362 =item Arguments: none
366 Implement this method in a subclass and return a list of events that
367 your ViewPort is accepting.
373 =item Arguments: $name
377 Create an id for the given event name and this ViewPort. Generally
378 returns the location and the name, joined with a colon.
384 =item Arguments: $spec, $items
388 Sorts the given list of items such that the ones that also appear in
389 the spec are at the beginning. This is called by
390 L<Reaction::UI::ViewPort::Action> and
391 L<Reaction::UI::ViewPort::ListView>, and gets passed L<column_order>
392 as the spec argument.
396 See L<Reaction::Class> for authors.
400 See L<Reaction::Class> for the license.