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 $child->apply_events($events);
75 sub apply_our_events {
76 my ($self, $events) = @_;
77 my @keys = keys %$events;
79 my $loc = $self->location;
81 foreach my $key (keys %$events) {
82 if ($key =~ m/^${loc}:(.*)$/) {
83 $our_events{$1} = $events->{$key};
86 if (keys %our_events) {
87 #warn "$self: events ".join(', ', %our_events)."\n";
88 $self->handle_events(\%our_events);
93 my ($self, $events) = @_;
94 my $exists = exists $events->{exists};
96 my %force = $self->force_events;
97 my @need = grep { !exists $events->{$_} } keys %force;
98 @{$events}{@need} = @force{@need};
100 foreach my $event ($self->accept_events) {
101 if (exists $events->{$event}) {
103 my $name = join(' at ', $self, $self->location);
105 "Applying Event: $event on $name with value: "
106 .(defined $events->{$event} ? $events->{$event} : '<undef>');
108 $self->$event($events->{$event});
113 sub accept_events { () }
115 sub force_events { () }
118 my ($self, $name) = @_;
119 return join(':', $self->location, $name);
123 my ($self, $spec, $items) = @_;
124 return [@$items] if not defined $spec;
127 if (ref $spec eq 'ARRAY') {
128 return [sort @$items] unless @$spec;
131 elsif (not ref $spec) {
132 return [@$items] unless length $spec;
133 @order = split /\s+/, $spec;
136 my %order_map = map {$_ => 0} @$items;
137 for my $order_num (0..$#order) {
138 $order_map{ $order[$order_num] } = ($#order - $order_num) + 1;
141 return [sort {$order_map{$b} <=> $order_map{$a}} @$items];
144 __PACKAGE__->meta->make_immutable;
152 Reaction::UI::ViewPort - Page layout building block
156 # Create a new ViewPort:
157 # $stack isa Reaction::UI::FocusStack object
158 my $vp = $stack->push_viewport('Reaction::UI::ViewPort', layout => 'xthml');
160 # Fetch ViewPort higher up the stack (further out)
161 my $outer = $vp->outer();
163 # Fetch ViewPort lower down (further in)
164 my $inner = $vp->inner();
166 # Create a named tangent stack for this ViewPort
167 my $substack = $vp->create_tangent('name');
169 # Retrieve a tangent stack for this ViewPort
170 my $substack = $vp->forcus_tangent('name');
172 # Get the names of all the tangent stacks for this ViewPort
173 my @names = $vp->focus_tangents();
175 # Fetch all the tangent stacks for this ViewPort
176 # This is called by apply_events
177 my $stacks = $vp->child_event_sinks();
180 ### The following methods are all called automatically when using
181 ### Reaction::UI::Controller(s)
182 # Resolve current events with this ViewPort
183 $vp->apply_events($ctx, $param_hash);
185 # Apply current events to all tangent stacks
186 # This is called by apply_events
187 $vp->apply_child_events($ctx, $params_hash);
189 # Apply current events to this ViewPort
190 # This is called by apply_events
191 $vp->apply_our_events($ctx, $params_hash);
195 A ViewPort describes part of a page, it can be a field, a form or
196 an entire page. ViewPorts are created on a
197 L<Reaction::UI::FocusStack>, usually belonging to a controller or
198 another ViewPort. Each ViewPort knows it's own position in the stack
199 it is in, as well as the stack containing it.
201 Each ViewPort has a specific location in the heirarchy of viewports
202 making up a page. The hierarchy is determined as follows: The first
203 ViewPort in a stack is labeled C<0>, the second is C<1> and so on. If
204 a ViewPort is in a named tangent, it's location will contain the name
205 of the tangent in it's location.
207 For example, the first ViewPort in the 'left' tangent of the main
208 ViewPort has location C<0.left.0>.
210 Several ViewPort attributes are set by
211 L<Reaction::UI::FocusStack/push_viewport> when new ViewPorts are
212 created, these are as follows:
222 The outer attribute is set to the previous ViewPort in the stack when
223 creating a ViewPort, if the ViewPort is the first in the stack, it
228 The inner attribute is set to the next ViewPort down in the stack when
229 it is created, if this is the last ViewPort in the stack, it will be
234 The focus_stack attribute is set to the L<Reaction::UI::FocusStack>
235 object that created the ViewPort.
239 The ctx attribute will be passed automatically when using
240 L<Reaction::UI::Controller/push_viewport> to create a ViewPort in the
241 base stack of a controller. When creating tangent stacks, you may have
242 to pass it in yourself.
254 The layout attribute can either be specifically passed when calling
255 C<push_viewport>, or it will be determined using the last part of the
260 This is generally used by more specialised ViewPorts such as the
261 L<ListView|Reaction::UI::ViewPort::ListView> or
262 L<Action|Reaction::UI::ViewPort::Action>. It can be either a
263 space separated list of column names, or an arrayref of column names.
275 =item Arguments: none
279 Fetch the ViewPort outside this one in the page hierarchy.
285 =item Arguments: none
289 Fetch the ViewPort inside this one in the page hierarchy.
291 =head2 create_tangent
295 =item Arguments: $tangent_name
299 Create a new named L<Reaction::UI::FocusStack> inside this
300 ViewPort. The created FocusStack is returned.
306 =item Arguments: $tangent_name
310 Fetch a named FocusStack from this ViewPort.
312 =head2 focus_tangents
316 =item Arguments: none
320 Returns a list of names of all the known tangents in this ViewPort.
324 Return the L<Reaction::UI::FocusStack> object that this ViewPort is in.
330 =item Arguments: $ctx, $params_hashref
334 This method is called by the FocusStack object to resolve all events
337 =head2 apply_child_events
341 =item Arguments: $ctx, $params_hashref
345 Resolve the given events for all the tangents of this ViewPort. Called
348 =head2 apply_our_events
352 =item Arguments: $ctx, $events
356 Resolve the given events that match the location of this
357 ViewPort. Called by L<apply_events>.
363 =item Arguments: $events
367 Actually call the event handlers for this ViewPort. Called by
368 L<apply_our_events>. By default this will do nothing, subclass
369 ViewPort and implement L<accept_events>.
375 =item Arguments: none
379 Implement this method in a subclass and return a list of events that
380 your ViewPort is accepting.
386 =item Arguments: $name
390 Create an id for the given event name and this ViewPort. Generally
391 returns the location and the name, joined with a colon.
397 =item Arguments: $spec, $items
401 Sorts the given list of items such that the ones that also appear in
402 the spec are at the beginning. This is called by
403 L<Reaction::UI::ViewPort::Action> and
404 L<Reaction::UI::ViewPort::ListView>, and gets passed L<column_order>
405 as the spec argument.
409 See L<Reaction::Class> for authors.
413 See L<Reaction::Class> for the license.