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', weak_ref => 1); #, required => 1);
29 my ($self, $name) = @_;
30 my $t_map = $self->_tangent_stacks;
31 if (exists $t_map->{$name}) {
32 confess "Can't create tangent with already existing name ${name}";
34 my $loc = join('.', $self->location, $name);
35 my $tangent = Reaction::UI::FocusStack->new(loc_prefix => $loc);
36 $t_map->{$name} = $tangent;
41 my ($self, $name) = @_;
42 if (my $tangent = $self->_tangent_stacks->{$name}) {
50 return keys %{shift->_tangent_stacks};
53 sub child_event_sinks {
55 return values %{$self->_tangent_stacks};
59 my ($self, $events) = @_;
60 return unless keys %$events;
61 $self->apply_child_events($events);
62 $self->apply_our_events($events);
65 sub apply_child_events {
66 my ($self, $events) = @_;
67 return unless keys %$events;
68 foreach my $child ($self->child_event_sinks) {
69 confess blessed($child) ."($child) is not a valid object"
70 unless blessed($child) && $child->can('apply_events');
71 my $loc = $child->location;
72 my %child_events = map { $_ => delete $events->{$_} }
73 grep { /^${loc}[-:]/ } keys %$events;
74 $child->apply_events(\%child_events);
78 sub apply_our_events {
79 my ($self, $events) = @_;
80 my $loc = $self->location;
82 foreach my $key (keys %$events) {
83 if ($key =~ m/^${loc}:(.*)$/) {
84 $our_events{$1} = delete $events->{$key};
87 $self->handle_events(\%our_events) if keys %our_events;
91 my ($self, $events) = @_;
92 my $exists = exists $events->{exists};
94 my %force = $self->force_events;
95 my @need = grep { !exists $events->{$_} } keys %force;
96 @{$events}{@need} = @force{@need};
98 foreach my $event ($self->accept_events) {
99 if (exists $events->{$event}) {
101 my $name = join(' at ', $self, $self->location);
103 "Applying Event: $event on $name with value: "
104 .(defined $events->{$event} ? $events->{$event} : '<undef>')."\n";
106 $self->$event($events->{$event});
111 sub accept_events { () }
113 sub force_events { () }
116 my ($self, $name) = @_;
117 return join(':', $self->location, $name);
121 my ($self, $spec, $items) = @_;
122 return [sort @$items] unless $spec;
125 if (ref $spec eq 'ARRAY') {
127 } elsif (not ref $spec) {
128 @order = split /\s+/, $spec;
131 my %order_map = map {$_ => 0} @$items;
132 for my $order_num (0..$#order) {
133 $order_map{ $order[$order_num] } = ($#order - $order_num) + 1;
136 return [sort {$order_map{$b} <=> $order_map{$a}} @$items];
139 __PACKAGE__->meta->make_immutable;
147 Reaction::UI::ViewPort - Page layout building block
151 # Create a new ViewPort:
152 # $stack isa Reaction::UI::FocusStack object
153 my $vp = $stack->push_viewport('Reaction::UI::ViewPort', layout => 'xhtml');
155 # Fetch ViewPort higher up the stack (further out)
156 my $outer = $vp->outer();
158 # Fetch ViewPort lower down (further in)
159 my $inner = $vp->inner();
161 # Create a named tangent stack for this ViewPort
162 my $substack = $vp->create_tangent('name');
164 # Retrieve a tangent stack for this ViewPort
165 my $substack = $vp->forcus_tangent('name');
167 # Get the names of all the tangent stacks for this ViewPort
168 my @names = $vp->focus_tangents();
170 # Fetch all the tangent stacks for this ViewPort
171 # This is called by apply_events
172 my $stacks = $vp->child_event_sinks();
175 ### The following methods are all called automatically when using
176 ### Reaction::UI::Controller(s)
177 # Resolve current events with this ViewPort
178 $vp->apply_events($ctx, $param_hash);
180 # Apply current events to all tangent stacks
181 # This is called by apply_events
182 $vp->apply_child_events($ctx, $params_hash);
184 # Apply current events to this ViewPort
185 # This is called by apply_events
186 $vp->apply_our_events($ctx, $params_hash);
190 A ViewPort describes part of a page, it can be a field, a form or
191 an entire page. ViewPorts are created on a
192 L<Reaction::UI::FocusStack>, usually belonging to a controller or
193 another ViewPort. Each ViewPort knows it's own position in the stack
194 it is in, as well as the stack containing it.
196 Each ViewPort has a specific location in the heirarchy of viewports
197 making up a page. The hierarchy is determined as follows: The first
198 ViewPort in a stack is labeled C<0>, the second is C<1> and so on. If
199 a ViewPort is in a named tangent, it's location will contain the name
200 of the tangent in it's location.
202 For example, the first ViewPort in the 'left' tangent of the main
203 ViewPort has location C<0.left.0>.
205 Several ViewPort attributes are set by
206 L<Reaction::UI::FocusStack/push_viewport> when new ViewPorts are
207 created, these are as follows:
217 The outer attribute is set to the previous ViewPort in the stack when
218 creating a ViewPort, if the ViewPort is the first in the stack, it
223 The inner attribute is set to the next ViewPort down in the stack when
224 it is created, if this is the last ViewPort in the stack, it will be
229 The focus_stack attribute is set to the L<Reaction::UI::FocusStack>
230 object that created the ViewPort.
234 The ctx attribute will be passed automatically when using
235 L<Reaction::UI::Controller/push_viewport> to create a ViewPort in the
236 base stack of a controller. When creating tangent stacks, you may have
237 to pass it in yourself.
249 The layout attribute can either be specifically passed when calling
250 C<push_viewport>, or it will be determined using the last part of the
255 This read-only hashref attribute will pass all it's keys as variables to the
256 layout at render time. They should be accessible from both the layout templates
257 and the widget, if applicable, through the C<%_> hash.
259 $controller->push_viewport(VPName, layout => 'foo', layout_args => { bar => 'bar'});
261 [% bar %] in template
265 This is generally used by more specialised ViewPorts such as the
266 L<ListView|Reaction::UI::ViewPort::ListView> or
267 L<Action|Reaction::UI::ViewPort::Action>. It can be either a
268 space separated list of column names, or an arrayref of column names.
280 =item Arguments: none
284 Fetch the ViewPort outside this one in the page hierarchy.
290 =item Arguments: none
294 Fetch the ViewPort inside this one in the page hierarchy.
296 =head2 create_tangent
300 =item Arguments: $tangent_name
304 Create a new named L<Reaction::UI::FocusStack> inside this
305 ViewPort. The created FocusStack is returned.
311 =item Arguments: $tangent_name
315 Fetch a named FocusStack from this ViewPort.
317 =head2 focus_tangents
321 =item Arguments: none
325 Returns a list of names of all the known tangents in this ViewPort.
329 Return the L<Reaction::UI::FocusStack> object that this ViewPort is in.
335 =item Arguments: $ctx, $params_hashref
339 This method is called by the FocusStack object to resolve all events
342 =head2 apply_child_events
346 =item Arguments: $ctx, $params_hashref
350 Resolve the given events for all the tangents of this ViewPort. Called
353 =head2 apply_our_events
357 =item Arguments: $ctx, $events
361 Resolve the given events that match the location of this
362 ViewPort. Called by L<apply_events>.
368 =item Arguments: $events
372 Actually call the event handlers for this ViewPort. Called by
373 L<apply_our_events>. By default this will do nothing, subclass
374 ViewPort and implement L<accept_events>.
380 =item Arguments: none
384 Implement this method in a subclass and return a list of events that
385 your ViewPort is accepting.
391 =item Arguments: $name
395 Create an id for the given event name and this ViewPort. Generally
396 returns the location and the name, joined with a colon.
402 =item Arguments: $spec, $items
406 Sorts the given list of items such that the ones that also appear in
407 the spec are at the beginning. This is called by
408 L<Reaction::UI::ViewPort::Action> and
409 L<Reaction::UI::ViewPort::ListView>, and gets passed L<column_order>
410 as the spec argument.
414 See L<Reaction::Class> for authors.
418 See L<Reaction::Class> for the license.