1 package Reaction::UI::ViewPort;
4 use Scalar::Util qw/blessed/;
8 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 layout_args => (isa => 'HashRef', is => 'ro', default => sub { {} });
13 has outer => (isa => 'Reaction::UI::ViewPort', is => 'rw', weak_ref => 1);
14 has inner => (isa => 'Reaction::UI::ViewPort', is => 'rw');
16 isa => 'Reaction::UI::FocusStack', is => 'rw', weak_ref => 1
18 has _tangent_stacks => (
19 isa => 'HashRef', is => 'ro', default => sub { {} }
21 has ctx => (isa => 'Catalyst', is => 'ro'); #, required => 1);
23 implements _build_layout => as {
27 implements create_tangent => as {
28 my ($self, $name) = @_;
29 my $t_map = $self->_tangent_stacks;
30 if (exists $t_map->{$name}) {
31 confess "Can't create tangent with already existing name ${name}";
33 my $loc = join('.', $self->location, $name);
34 my $tangent = Reaction::UI::FocusStack->new(loc_prefix => $loc);
35 $t_map->{$name} = $tangent;
39 implements focus_tangent => as {
40 my ($self, $name) = @_;
41 if (my $tangent = $self->_tangent_stacks->{$name}) {
48 implements focus_tangents => as {
49 return keys %{shift->_tangent_stacks};
52 implements child_event_sinks => as {
54 return values %{$self->_tangent_stacks};
57 implements apply_events => as {
58 my ($self, $ctx, $events) = @_;
59 return unless keys %$events;
60 $self->apply_child_events($ctx, $events);
61 $self->apply_our_events($ctx, $events);
64 implements apply_child_events => as {
65 my ($self, $ctx, $events) = @_;
66 return unless keys %$events;
67 foreach my $child ($self->child_event_sinks) {
68 confess blessed($child) ."($child) is not a valid object"
69 unless blessed($child) && $child->can('apply_events');
70 $child->apply_events($ctx, $events);
74 implements apply_our_events => as {
75 my ($self, $ctx, $events) = @_;
76 my @keys = keys %$events;
78 my $loc = $self->location;
80 foreach my $key (keys %$events) {
81 if ($key =~ m/^${loc}:(.*)$/) {
82 $our_events{$1} = $events->{$key};
85 if (keys %our_events) {
86 #warn "$self: events ".join(', ', %our_events)."\n";
87 $self->handle_events(\%our_events);
91 implements handle_events => as {
92 my ($self, $events) = @_;
93 my $exists = exists $events->{exists};
95 my %force = $self->force_events;
96 my @need = grep { !exists $events->{$_} } keys %force;
97 @{$events}{@need} = @force{@need};
99 foreach my $event ($self->accept_events) {
100 if (exists $events->{$event}) {
102 my $name = join(' at ', $self, $self->location);
103 $self->ctx->log->debug(
104 "Applying Event: $event on $name with value: "
105 .(defined $events->{$event} ? $events->{$event} : '<undef>')
108 $self->$event($events->{$event});
113 implements accept_events => as { () };
115 implements force_events => as { () };
117 implements event_id_for => as {
118 my ($self, $name) = @_;
119 return join(':', $self->location, $name);
122 implements sort_by_spec => as {
123 my ($self, $spec, $items) = @_;
124 return $items if not defined $spec;
127 if (ref $spec eq 'ARRAY') {
130 elsif (not ref $spec) {
131 return $items unless length $spec;
132 @order = split /\s+/, $spec;
135 my %order_map = map {$_ => 0} @$items;
136 for my $order_num (0..$#order) {
137 $order_map{ $order[$order_num] } = ($#order - $order_num) + 1;
140 return [sort {$order_map{$b} <=> $order_map{$a}} @$items];
150 Reaction::UI::ViewPort - Page layout building block
154 # Create a new ViewPort:
155 # $stack isa Reaction::UI::FocusStack object
156 my $vp = $stack->push_viewport('Reaction::UI::ViewPort', layout => 'xthml');
158 # Fetch ViewPort higher up the stack (further out)
159 my $outer = $vp->outer();
161 # Fetch ViewPort lower down (further in)
162 my $inner = $vp->inner();
164 # Create a named tangent stack for this ViewPort
165 my $substack = $vp->create_tangent('name');
167 # Retrieve a tangent stack for this ViewPort
168 my $substack = $vp->forcus_tangent('name');
170 # Get the names of all the tangent stacks for this ViewPort
171 my @names = $vp->focus_tangents();
173 # Fetch all the tangent stacks for this ViewPort
174 # This is called by apply_events
175 my $stacks = $vp->child_event_sinks();
178 ### The following methods are all called automatically when using
179 ### Reaction::UI::Controller(s)
180 # Resolve current events with this ViewPort
181 $vp->apply_events($ctx, $param_hash);
183 # Apply current events to all tangent stacks
184 # This is called by apply_events
185 $vp->apply_child_events($ctx, $params_hash);
187 # Apply current events to this ViewPort
188 # This is called by apply_events
189 $vp->apply_our_events($ctx, $params_hash);
193 A ViewPort describes part of a page, it can be a field, a form or
194 an entire page. ViewPorts are created on a
195 L<Reaction::UI::FocusStack>, usually belonging to a controller or
196 another ViewPort. Each ViewPort knows it's own position in the stack
197 it is in, as well as the stack containing it.
199 Each ViewPort has a specific location in the heirarchy of viewports
200 making up a page. The hierarchy is determined as follows: The first
201 ViewPort in a stack is labeled C<0>, the second is C<1> and so on. If
202 a ViewPort is in a named tangent, it's location will contain the name
203 of the tangent in it's location.
205 For example, the first ViewPort in the 'left' tangent of the main
206 ViewPort has location C<0.left.0>.
208 Several ViewPort attributes are set by
209 L<Reaction::UI::FocusStack/push_viewport> when new ViewPorts are
210 created, these are as follows:
220 The outer attribute is set to the previous ViewPort in the stack when
221 creating a ViewPort, if the ViewPort is the first in the stack, it
226 The inner attribute is set to the next ViewPort down in the stack when
227 it is created, if this is the last ViewPort in the stack, it will be
232 The focus_stack attribute is set to the L<Reaction::UI::FocusStack>
233 object that created the ViewPort.
237 The ctx attribute will be passed automatically when using
238 L<Reaction::UI::Controller/push_viewport> to create a ViewPort in the
239 base stack of a controller. When creating tangent stacks, you may have
240 to pass it in yourself.
252 The layout attribute can either be specifically passed when calling
253 C<push_viewport>, or it will be determined using the last part of the
258 This is generally used by more specialised ViewPorts such as the
259 L<ListView|Reaction::UI::ViewPort::ListView> or
260 L<Action|Reaction::UI::ViewPort::Action>. It can be either a
261 space separated list of column names, or an arrayref of column names.
273 =item Arguments: none
277 Fetch the ViewPort outside this one in the page hierarchy.
283 =item Arguments: none
287 Fetch the ViewPort inside this one in the page hierarchy.
289 =head2 create_tangent
293 =item Arguments: $tangent_name
297 Create a new named L<Reaction::UI::FocusStack> inside this
298 ViewPort. The created FocusStack is returned.
304 =item Arguments: $tangent_name
308 Fetch a named FocusStack from this ViewPort.
310 =head2 focus_tangents
314 =item Arguments: none
318 Returns a list of names of all the known tangents in this ViewPort.
322 Return the L<Reaction::UI::FocusStack> object that this ViewPort is in.
328 =item Arguments: $ctx, $params_hashref
332 This method is called by the FocusStack object to resolve all events
335 =head2 apply_child_events
339 =item Arguments: $ctx, $params_hashref
343 Resolve the given events for all the tangents of this ViewPort. Called
346 =head2 apply_our_events
350 =item Arguments: $ctx, $events
354 Resolve the given events that match the location of this
355 ViewPort. Called by L<apply_events>.
361 =item Arguments: $events
365 Actually call the event handlers for this ViewPort. Called by
366 L<apply_our_events>. By default this will do nothing, subclass
367 ViewPort and implement L<accept_events>.
373 =item Arguments: none
377 Implement this method in a subclass and return a list of events that
378 your ViewPort is accepting.
384 =item Arguments: $name
388 Create an id for the given event name and this ViewPort. Generally
389 returns the location and the name, joined with a colon.
395 =item Arguments: $spec, $items
399 Sorts the given list of items such that the ones that also appear in
400 the spec are at the beginning. This is called by
401 L<Reaction::UI::ViewPort::Action> and
402 L<Reaction::UI::ViewPort::ListView>, and gets passed L<column_order>
403 as the spec argument.
407 See L<Reaction::Class> for authors.
411 See L<Reaction::Class> for the license.