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 return unless keys %$events;
57 $self->apply_child_events($ctx, $events);
58 $self->apply_our_events($ctx, $events);
61 implements apply_child_events => as {
62 my ($self, $ctx, $events) = @_;
63 return unless keys %$events;
64 foreach my $child ($self->child_event_sinks) {
65 confess blessed($child) ."($child) is not a valid object"
66 unless blessed($child) && $child->can('apply_events');
67 $child->apply_events($ctx, $events);
71 implements apply_our_events => as {
72 my ($self, $ctx, $events) = @_;
73 my @keys = keys %$events;
75 my $loc = $self->location;
77 foreach my $key (keys %$events) {
78 if ($key =~ m/^${loc}:(.*)$/) {
79 $our_events{$1} = $events->{$key};
82 if (keys %our_events) {
83 #warn "$self: events ".join(', ', %our_events)."\n";
84 $self->handle_events(\%our_events);
88 implements handle_events => as {
89 my ($self, $events) = @_;
90 foreach my $event ($self->accept_events) {
91 if (exists $events->{$event}) {
92 #my $name = eval{$self->name};
93 #$self->ctx->log->debug("Applying Event: $event on $name with value: ". $events->{$event});
94 $self->$event($events->{$event});
99 implements accept_events => as { () };
101 implements event_id_for => as {
102 my ($self, $name) = @_;
103 return join(':', $self->location, $name);
106 implements sort_by_spec => as {
107 my ($self, $spec, $items) = @_;
108 return $items if not defined $spec;
111 if (ref $spec eq 'ARRAY') {
114 elsif (not ref $spec) {
115 return $items unless length $spec;
116 @order = split /\s+/, $spec;
119 my %order_map = map {$_ => 0} @$items;
120 for my $order_num (0..$#order) {
121 $order_map{ $order[$order_num] } = ($#order - $order_num) + 1;
124 return [sort {$order_map{$b} <=> $order_map{$a}} @$items];
134 Reaction::UI::ViewPort - Page layout building block
138 # Create a new ViewPort:
139 # $stack isa Reaction::UI::FocusStack object
140 my $vp = $stack->push_viewport('Reaction::UI::ViewPort', layout => 'xthml');
142 # Fetch ViewPort higher up the stack (further out)
143 my $outer = $vp->outer();
145 # Fetch ViewPort lower down (further in)
146 my $inner = $vp->inner();
148 # Create a named tangent stack for this ViewPort
149 my $substack = $vp->create_tangent('name');
151 # Retrieve a tangent stack for this ViewPort
152 my $substack = $vp->forcus_tangent('name');
154 # Get the names of all the tangent stacks for this ViewPort
155 my @names = $vp->focus_tangents();
157 # Fetch all the tangent stacks for this ViewPort
158 # This is called by apply_events
159 my $stacks = $vp->child_event_sinks();
162 ### The following methods are all called automatically when using
163 ### Reaction::UI::Controller(s)
164 # Resolve current events with this ViewPort
165 $vp->apply_events($ctx, $param_hash);
167 # Apply current events to all tangent stacks
168 # This is called by apply_events
169 $vp->apply_child_events($ctx, $params_hash);
171 # Apply current events to this ViewPort
172 # This is called by apply_events
173 $vp->apply_our_events($ctx, $params_hash);
177 A ViewPort describes part of a page, it can be a field, a form or
178 an entire page. ViewPorts are created on a
179 L<Reaction::UI::FocusStack>, usually belonging to a controller or
180 another ViewPort. Each ViewPort knows it's own position in the stack
181 it is in, as well as the stack containing it.
183 Each ViewPort has a specific location in the heirarchy of viewports
184 making up a page. The hierarchy is determined as follows: The first
185 ViewPort in a stack is labeled C<0>, the second is C<1> and so on. If
186 a ViewPort is in a named tangent, it's location will contain the name
187 of the tangent in it's location.
189 For example, the first ViewPort in the 'left' tangent of the main
190 ViewPort has location C<0.left.0>.
192 Several ViewPort attributes are set by
193 L<Reaction::UI::FocusStack/push_viewport> when new ViewPorts are
194 created, these are as follows:
204 The outer attribute is set to the previous ViewPort in the stack when
205 creating a ViewPort, if the ViewPort is the first in the stack, it
210 The inner attribute is set to the next ViewPort down in the stack when
211 it is created, if this is the last ViewPort in the stack, it will be
216 The focus_stack attribute is set to the L<Reaction::UI::FocusStack>
217 object that created the ViewPort.
221 The ctx attribute will be passed automatically when using
222 L<Reaction::UI::Controller/push_viewport> to create a ViewPort in the
223 base stack of a controller. When creating tangent stacks, you may have
224 to pass it in yourself.
236 The layout attribute can either be specifically passed when calling
237 C<push_viewport>, or it will be determined using the last part of the
242 This is generally used by more specialised ViewPorts such as the
243 L<ListView|Reaction::UI::ViewPort::ListView> or
244 L<ActionForm|Reaction::UI::ViewPort::ActionForm>. It can be either a
245 space separated list of column names, or an arrayref of column names.
257 =item Arguments: none
261 Fetch the ViewPort outside this one in the page hierarchy.
267 =item Arguments: none
271 Fetch the ViewPort inside this one in the page hierarchy.
273 =head2 create_tangent
277 =item Arguments: $tangent_name
281 Create a new named L<Reaction::UI::FocusStack> inside this
282 ViewPort. The created FocusStack is returned.
288 =item Arguments: $tangent_name
292 Fetch a named FocusStack from this ViewPort.
294 =head2 focus_tangents
298 =item Arguments: none
302 Returns a list of names of all the known tangents in this ViewPort.
306 Return the L<Reaction::UI::FocusStack> object that this ViewPort is in.
312 =item Arguments: $ctx, $params_hashref
316 This method is called by the FocusStack object to resolve all events
319 =head2 apply_child_events
323 =item Arguments: $ctx, $params_hashref
327 Resolve the given events for all the tangents of this ViewPort. Called
330 =head2 apply_our_events
334 =item Arguments: $ctx, $events
338 Resolve the given events that match the location of this
339 ViewPort. Called by L<apply_events>.
345 =item Arguments: $events
349 Actually call the event handlers for this ViewPort. Called by
350 L<apply_our_events>. By default this will do nothing, subclass
351 ViewPort and implement L<accept_events>.
357 =item Arguments: none
361 Implement this method in a subclass and return a list of events that
362 your ViewPort is accepting.
368 =item Arguments: $name
372 Create an id for the given event name and this ViewPort. Generally
373 returns the location and the name, joined with a colon.
379 =item Arguments: $spec, $items
383 Sorts the given list of items such that the ones that also appear in
384 the spec are at the beginning. This is called by
385 L<Reaction::UI::ViewPort::ActionForm> and
386 L<Reaction::UI::ViewPort::ListView>, and gets passed L<column_order>
387 as the spec argument.
391 See L<Reaction::Class> for authors.
395 See L<Reaction::Class> for the license.