1 package Reaction::UI::ViewPort;
4 use Scalar::Util qw/blessed/;
6 sub DEBUG_EVENTS () { $ENV{REACTION_UI_VIEWPORT_DEBUG_EVENTS} }
10 has location => (isa => 'Str', is => 'rw', required => 1);
11 has layout => (isa => 'Str', is => 'rw', lazy_build => 1);
12 has outer => (isa => 'Reaction::UI::ViewPort', is => 'rw', weak_ref => 1);
13 has inner => (isa => 'Reaction::UI::ViewPort', is => 'rw');
15 isa => 'Reaction::UI::FocusStack', is => 'rw', weak_ref => 1
17 has _tangent_stacks => (
18 isa => 'HashRef', is => 'ro', default => sub { {} }
20 has ctx => (isa => 'Catalyst', is => 'ro', required => 1);
22 implements _build_layout => as {
26 implements create_tangent => as {
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 implements focus_tangent => as {
39 my ($self, $name) = @_;
40 if (my $tangent = $self->_tangent_stacks->{$name}) {
47 implements focus_tangents => as {
48 return keys %{shift->_tangent_stacks};
51 implements child_event_sinks => as {
53 return values %{$self->_tangent_stacks};
56 implements apply_events => as {
57 my ($self, $ctx, $events) = @_;
58 return unless keys %$events;
59 $self->apply_child_events($ctx, $events);
60 $self->apply_our_events($ctx, $events);
63 implements apply_child_events => as {
64 my ($self, $ctx, $events) = @_;
65 return unless keys %$events;
66 foreach my $child ($self->child_event_sinks) {
67 confess blessed($child) ."($child) is not a valid object"
68 unless blessed($child) && $child->can('apply_events');
69 $child->apply_events($ctx, $events);
73 implements apply_our_events => as {
74 my ($self, $ctx, $events) = @_;
75 my @keys = keys %$events;
77 my $loc = $self->location;
79 foreach my $key (keys %$events) {
80 if ($key =~ m/^${loc}:(.*)$/) {
81 $our_events{$1} = $events->{$key};
84 if (keys %our_events) {
85 #warn "$self: events ".join(', ', %our_events)."\n";
86 $self->handle_events(\%our_events);
90 implements handle_events => as {
91 my ($self, $events) = @_;
92 foreach my $event ($self->accept_events) {
93 if (exists $events->{$event}) {
95 my $name = join(' at ', ref($self), $self->location);
96 $self->ctx->log->debug(
97 "Applying Event: $event on $name with value: "
101 $self->$event($events->{$event});
106 implements accept_events => as { () };
108 implements event_id_for => as {
109 my ($self, $name) = @_;
110 return join(':', $self->location, $name);
113 implements sort_by_spec => as {
114 my ($self, $spec, $items) = @_;
115 return $items if not defined $spec;
118 if (ref $spec eq 'ARRAY') {
121 elsif (not ref $spec) {
122 return $items unless length $spec;
123 @order = split /\s+/, $spec;
126 my %order_map = map {$_ => 0} @$items;
127 for my $order_num (0..$#order) {
128 $order_map{ $order[$order_num] } = ($#order - $order_num) + 1;
131 return [sort {$order_map{$b} <=> $order_map{$a}} @$items];
141 Reaction::UI::ViewPort - Page layout building block
145 # Create a new ViewPort:
146 # $stack isa Reaction::UI::FocusStack object
147 my $vp = $stack->push_viewport('Reaction::UI::ViewPort', layout => 'xthml');
149 # Fetch ViewPort higher up the stack (further out)
150 my $outer = $vp->outer();
152 # Fetch ViewPort lower down (further in)
153 my $inner = $vp->inner();
155 # Create a named tangent stack for this ViewPort
156 my $substack = $vp->create_tangent('name');
158 # Retrieve a tangent stack for this ViewPort
159 my $substack = $vp->forcus_tangent('name');
161 # Get the names of all the tangent stacks for this ViewPort
162 my @names = $vp->focus_tangents();
164 # Fetch all the tangent stacks for this ViewPort
165 # This is called by apply_events
166 my $stacks = $vp->child_event_sinks();
169 ### The following methods are all called automatically when using
170 ### Reaction::UI::Controller(s)
171 # Resolve current events with this ViewPort
172 $vp->apply_events($ctx, $param_hash);
174 # Apply current events to all tangent stacks
175 # This is called by apply_events
176 $vp->apply_child_events($ctx, $params_hash);
178 # Apply current events to this ViewPort
179 # This is called by apply_events
180 $vp->apply_our_events($ctx, $params_hash);
184 A ViewPort describes part of a page, it can be a field, a form or
185 an entire page. ViewPorts are created on a
186 L<Reaction::UI::FocusStack>, usually belonging to a controller or
187 another ViewPort. Each ViewPort knows it's own position in the stack
188 it is in, as well as the stack containing it.
190 Each ViewPort has a specific location in the heirarchy of viewports
191 making up a page. The hierarchy is determined as follows: The first
192 ViewPort in a stack is labeled C<0>, the second is C<1> and so on. If
193 a ViewPort is in a named tangent, it's location will contain the name
194 of the tangent in it's location.
196 For example, the first ViewPort in the 'left' tangent of the main
197 ViewPort has location C<0.left.0>.
199 Several ViewPort attributes are set by
200 L<Reaction::UI::FocusStack/push_viewport> when new ViewPorts are
201 created, these are as follows:
211 The outer attribute is set to the previous ViewPort in the stack when
212 creating a ViewPort, if the ViewPort is the first in the stack, it
217 The inner attribute is set to the next ViewPort down in the stack when
218 it is created, if this is the last ViewPort in the stack, it will be
223 The focus_stack attribute is set to the L<Reaction::UI::FocusStack>
224 object that created the ViewPort.
228 The ctx attribute will be passed automatically when using
229 L<Reaction::UI::Controller/push_viewport> to create a ViewPort in the
230 base stack of a controller. When creating tangent stacks, you may have
231 to pass it in yourself.
243 The layout attribute can either be specifically passed when calling
244 C<push_viewport>, or it will be determined using the last part of the
249 This is generally used by more specialised ViewPorts such as the
250 L<ListView|Reaction::UI::ViewPort::ListView> or
251 L<Action|Reaction::UI::ViewPort::Action>. It can be either a
252 space separated list of column names, or an arrayref of column names.
264 =item Arguments: none
268 Fetch the ViewPort outside this one in the page hierarchy.
274 =item Arguments: none
278 Fetch the ViewPort inside this one in the page hierarchy.
280 =head2 create_tangent
284 =item Arguments: $tangent_name
288 Create a new named L<Reaction::UI::FocusStack> inside this
289 ViewPort. The created FocusStack is returned.
295 =item Arguments: $tangent_name
299 Fetch a named FocusStack from this ViewPort.
301 =head2 focus_tangents
305 =item Arguments: none
309 Returns a list of names of all the known tangents in this ViewPort.
313 Return the L<Reaction::UI::FocusStack> object that this ViewPort is in.
319 =item Arguments: $ctx, $params_hashref
323 This method is called by the FocusStack object to resolve all events
326 =head2 apply_child_events
330 =item Arguments: $ctx, $params_hashref
334 Resolve the given events for all the tangents of this ViewPort. Called
337 =head2 apply_our_events
341 =item Arguments: $ctx, $events
345 Resolve the given events that match the location of this
346 ViewPort. Called by L<apply_events>.
352 =item Arguments: $events
356 Actually call the event handlers for this ViewPort. Called by
357 L<apply_our_events>. By default this will do nothing, subclass
358 ViewPort and implement L<accept_events>.
364 =item Arguments: none
368 Implement this method in a subclass and return a list of events that
369 your ViewPort is accepting.
375 =item Arguments: $name
379 Create an id for the given event name and this ViewPort. Generally
380 returns the location and the name, joined with a colon.
386 =item Arguments: $spec, $items
390 Sorts the given list of items such that the ones that also appear in
391 the spec are at the beginning. This is called by
392 L<Reaction::UI::ViewPort::Action> and
393 L<Reaction::UI::ViewPort::ListView>, and gets passed L<column_order>
394 as the spec argument.
398 See L<Reaction::Class> for authors.
402 See L<Reaction::Class> for the license.