1 package Reaction::UI::ViewPort;
4 use Scalar::Util qw/blessed/;
8 has location => (isa => 'Str', is => 'rw', required => 1);
9 has layout => (isa => 'Str', is => 'rw', lazy_build => 1);
10 has outer => (isa => 'Reaction::UI::ViewPort', is => 'rw', weak_ref => 1);
11 has inner => (isa => 'Reaction::UI::ViewPort', is => 'rw');
13 isa => 'Reaction::UI::FocusStack', is => 'rw', weak_ref => 1
15 has _tangent_stacks => (
16 isa => 'HashRef', is => 'ro', default => sub { {} }
18 has ctx => (isa => 'Catalyst', is => 'ro', required => 1);
20 implements _build_layout => as {
24 implements create_tangent => as {
25 my ($self, $name) = @_;
26 my $t_map = $self->_tangent_stacks;
27 if (exists $t_map->{$name}) {
28 confess "Can't create tangent with already existing name ${name}";
30 my $loc = join('.', $self->location, $name);
31 my $tangent = Reaction::UI::FocusStack->new(loc_prefix => $loc);
32 $t_map->{$name} = $tangent;
36 implements focus_tangent => as {
37 my ($self, $name) = @_;
38 if (my $tangent = $self->_tangent_stacks->{$name}) {
45 implements focus_tangents => as {
46 return keys %{shift->_tangent_stacks};
49 implements child_event_sinks => as {
51 return values %{$self->_tangent_stacks};
54 implements apply_events => as {
55 my ($self, $ctx, $events) = @_;
56 $self->apply_child_events($ctx, $events);
57 $self->apply_our_events($ctx, $events);
60 implements apply_child_events => as {
61 my ($self, $ctx, $events) = @_;
62 foreach my $child ($self->child_event_sinks) {
63 confess blessed($child) ."($child) is not a valid object"
64 unless blessed($child) && $child->can('apply_events');
65 $child->apply_events($ctx, $events);
69 implements apply_our_events => as {
70 my ($self, $ctx, $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 implements handle_events => as {
85 my ($self, $events) = @_;
86 foreach my $event ($self->accept_events) {
87 if (exists $events->{$event}) {
88 # $self->ctx->log->debug("Applying Event: $event with value: ". $events->{$event});
89 $self->$event($events->{$event});
94 implements accept_events => as { () };
96 implements event_id_for => as {
97 my ($self, $name) = @_;
98 return join(':', $self->location, $name);
101 implements sort_by_spec => as {
102 my ($self, $spec, $items) = @_;
103 return $items if not defined $spec;
106 if (ref $spec eq 'ARRAY') {
109 elsif (not ref $spec) {
110 return $items unless length $spec;
111 @order = split /\s+/, $spec;
114 my %order_map = map {$_ => 0} @$items;
115 for my $order_num (0..$#order) {
116 $order_map{ $order[$order_num] } = ($#order - $order_num) + 1;
119 return [sort {$order_map{$b} <=> $order_map{$a}} @$items];
129 Reaction::UI::ViewPort - Page layout building block
133 # Create a new ViewPort:
134 # $stack isa Reaction::UI::FocusStack object
135 my $vp = $stack->push_viewport('Reaction::UI::ViewPort', layout => 'xthml');
137 # Fetch ViewPort higher up the stack (further out)
138 my $outer = $vp->outer();
140 # Fetch ViewPort lower down (further in)
141 my $inner = $vp->inner();
143 # Create a named tangent stack for this ViewPort
144 my $substack = $vp->create_tangent('name');
146 # Retrieve a tangent stack for this ViewPort
147 my $substack = $vp->forcus_tangent('name');
149 # Get the names of all the tangent stacks for this ViewPort
150 my @names = $vp->focus_tangents();
152 # Fetch all the tangent stacks for this ViewPort
153 # This is called by apply_events
154 my $stacks = $vp->child_event_sinks();
157 ### The following methods are all called automatically when using
158 ### Reaction::UI::Controller(s)
159 # Resolve current events with this ViewPort
160 $vp->apply_events($ctx, $param_hash);
162 # Apply current events to all tangent stacks
163 # This is called by apply_events
164 $vp->apply_child_events($ctx, $params_hash);
166 # Apply current events to this ViewPort
167 # This is called by apply_events
168 $vp->apply_our_events($ctx, $params_hash);
172 A ViewPort describes part of a page, it can be a field, a form or
173 an entire page. ViewPorts are created on a
174 L<Reaction::UI::FocusStack>, usually belonging to a controller or
175 another ViewPort. Each ViewPort knows it's own position in the stack
176 it is in, as well as the stack containing it.
178 Each ViewPort has a specific location in the heirarchy of viewports
179 making up a page. The hierarchy is determined as follows: The first
180 ViewPort in a stack is labeled C<0>, the second is C<1> and so on. If
181 a ViewPort is in a named tangent, it's location will contain the name
182 of the tangent in it's location.
184 For example, the first ViewPort in the 'left' tangent of the main
185 ViewPort has location C<0.left.0>.
187 Several ViewPort attributes are set by
188 L<Reaction::UI::FocusStack/push_viewport> when new ViewPorts are
189 created, these are as follows:
199 The outer attribute is set to the previous ViewPort in the stack when
200 creating a ViewPort, if the ViewPort is the first in the stack, it
205 The inner attribute is set to the next ViewPort down in the stack when
206 it is created, if this is the last ViewPort in the stack, it will be
211 The focus_stack attribute is set to the L<Reaction::UI::FocusStack>
212 object that created the ViewPort.
216 The ctx attribute will be passed automatically when using
217 L<Reaction::UI::Controller/push_viewport> to create a ViewPort in the
218 base stack of a controller. When creating tangent stacks, you may have
219 to pass it in yourself.
231 The layout attribute can either be specifically passed when calling
232 C<push_viewport>, or it will be determined using the last part of the
237 This is generally used by more specialised ViewPorts such as the
238 L<ListView|Reaction::UI::ViewPort::ListView> or
239 L<ActionForm|Reaction::UI::ViewPort::ActionForm>. It can be either a
240 space separated list of column names, or an arrayref of column names.
252 =item Arguments: none
256 Fetch the ViewPort outside this one in the page hierarchy.
262 =item Arguments: none
266 Fetch the ViewPort inside this one in the page hierarchy.
268 =head2 create_tangent
272 =item Arguments: $tangent_name
276 Create a new named L<Reaction::UI::FocusStack> inside this
277 ViewPort. The created FocusStack is returned.
283 =item Arguments: $tangent_name
287 Fetch a named FocusStack from this ViewPort.
289 =head2 focus_tangents
293 =item Arguments: none
297 Returns a list of names of all the known tangents in this ViewPort.
301 Return the L<Reaction::UI::FocusStack> object that this ViewPort is in.
307 =item Arguments: $ctx, $params_hashref
311 This method is called by the FocusStack object to resolve all events
314 =head2 apply_child_events
318 =item Arguments: $ctx, $params_hashref
322 Resolve the given events for all the tangents of this ViewPort. Called
325 =head2 apply_our_events
329 =item Arguments: $ctx, $events
333 Resolve the given events that match the location of this
334 ViewPort. Called by L<apply_events>.
340 =item Arguments: $events
344 Actually call the event handlers for this ViewPort. Called by
345 L<apply_our_events>. By default this will do nothing, subclass
346 ViewPort and implement L<accept_events>.
352 =item Arguments: none
356 Implement this method in a subclass and return a list of events that
357 your ViewPort is accepting.
363 =item Arguments: $name
367 Create an id for the given event name and this ViewPort. Generally
368 returns the location and the name, joined with a colon.
374 =item Arguments: $spec, $items
378 Sorts the given list of items such that the ones that also appear in
379 the spec are at the beginning. This is called by
380 L<Reaction::UI::ViewPort::ActionForm> and
381 L<Reaction::UI::ViewPort::ListView>, and gets passed L<column_order>
382 as the spec argument.
386 See L<Reaction::Class> for authors.
390 See L<Reaction::Class> for the license.