remove class blocks from widget code
[catagits/Reaction.git] / lib / Reaction / UI / ViewPort.pm
CommitLineData
7adfd53f 1package Reaction::UI::ViewPort;
2
3use Reaction::Class;
36d54b14 4use Scalar::Util qw/blessed/;
7adfd53f 5
81393881 6use namespace::clean -except => [ qw(meta) ];
7
8
9sub DEBUG_EVENTS () { $ENV{REACTION_UI_VIEWPORT_DEBUG_EVENTS} }
10
11has location => (isa => 'Str', is => 'rw', required => 1);
12has layout => (isa => 'Str', is => 'rw', lazy_build => 1);
13has layout_args => (isa => 'HashRef', is => 'ro', default => sub { {} });
14has outer => (isa => 'Reaction::UI::ViewPort', is => 'rw', weak_ref => 1);
15has inner => (isa => 'Reaction::UI::ViewPort', is => 'rw');
16has focus_stack => (
17 isa => 'Reaction::UI::FocusStack', is => 'rw', weak_ref => 1
18);
19has _tangent_stacks => (
20 isa => 'HashRef', is => 'ro', default => sub { {} }
21);
22has ctx => (isa => 'Catalyst', is => 'ro'); #, required => 1);
23sub _build_layout {
24 '';
25};
26sub create_tangent {
27 my ($self, $name) = @_;
28 my $t_map = $self->_tangent_stacks;
29 if (exists $t_map->{$name}) {
30 confess "Can't create tangent with already existing name ${name}";
31 }
32 my $loc = join('.', $self->location, $name);
33 my $tangent = Reaction::UI::FocusStack->new(loc_prefix => $loc);
34 $t_map->{$name} = $tangent;
35 return $tangent;
36};
37sub focus_tangent {
38 my ($self, $name) = @_;
39 if (my $tangent = $self->_tangent_stacks->{$name}) {
7adfd53f 40 return $tangent;
81393881 41 } else {
42 return;
43 }
44};
45sub focus_tangents {
46 return keys %{shift->_tangent_stacks};
47};
48sub child_event_sinks {
49 my $self = shift;
50 return values %{$self->_tangent_stacks};
51};
52sub apply_events {
52f292b2 53 my ($self, $events) = @_;
81393881 54 return unless keys %$events;
52f292b2 55 $self->apply_child_events($events);
56 $self->apply_our_events($events);
81393881 57};
58sub apply_child_events {
52f292b2 59 my ($self, $events) = @_;
81393881 60 return unless keys %$events;
61 foreach my $child ($self->child_event_sinks) {
62 confess blessed($child) ."($child) is not a valid object"
63 unless blessed($child) && $child->can('apply_events');
52f292b2 64 $child->apply_events($events);
81393881 65 }
66};
67sub apply_our_events {
52f292b2 68 my ($self, $events) = @_;
81393881 69 my @keys = keys %$events;
70 return unless @keys;
71 my $loc = $self->location;
72 my %our_events;
73 foreach my $key (keys %$events) {
74 if ($key =~ m/^${loc}:(.*)$/) {
75 $our_events{$1} = $events->{$key};
881dd557 76 }
81393881 77 }
78 if (keys %our_events) {
79 #warn "$self: events ".join(', ', %our_events)."\n";
80 $self->handle_events(\%our_events);
81 }
82};
83sub handle_events {
84 my ($self, $events) = @_;
85 my $exists = exists $events->{exists};
86 if ($exists) {
87 my %force = $self->force_events;
88 my @need = grep { !exists $events->{$_} } keys %force;
89 @{$events}{@need} = @force{@need};
90 }
91 foreach my $event ($self->accept_events) {
92 if (exists $events->{$event}) {
93 if (DEBUG_EVENTS) {
94 my $name = join(' at ', $self, $self->location);
52f292b2 95 print STDERR
81393881 96 "Applying Event: $event on $name with value: "
52f292b2 97 .(defined $events->{$event} ? $events->{$event} : '<undef>');
7adfd53f 98 }
81393881 99 $self->$event($events->{$event});
7adfd53f 100 }
81393881 101 }
102};
103sub accept_events { () };
104sub force_events { () };
105sub event_id_for {
106 my ($self, $name) = @_;
107 return join(':', $self->location, $name);
108};
109sub sort_by_spec {
110 my ($self, $spec, $items) = @_;
111 return $items if not defined $spec;
112
113 my @order;
114 if (ref $spec eq 'ARRAY') {
115 @order = @$spec;
116 }
117 elsif (not ref $spec) {
118 return $items unless length $spec;
119 @order = split /\s+/, $spec;
120 }
121
122 my %order_map = map {$_ => 0} @$items;
123 for my $order_num (0..$#order) {
124 $order_map{ $order[$order_num] } = ($#order - $order_num) + 1;
125 }
126
127 return [sort {$order_map{$b} <=> $order_map{$a}} @$items];
128};
b8faba69 129
81393881 130__PACKAGE__->meta->make_immutable;
7adfd53f 131
7adfd53f 132
1331;
134
135
136=head1 NAME
137
138Reaction::UI::ViewPort - Page layout building block
139
140=head1 SYNOPSIS
141
142 # Create a new ViewPort:
143 # $stack isa Reaction::UI::FocusStack object
144 my $vp = $stack->push_viewport('Reaction::UI::ViewPort', layout => 'xthml');
145
146 # Fetch ViewPort higher up the stack (further out)
147 my $outer = $vp->outer();
148
149 # Fetch ViewPort lower down (further in)
150 my $inner = $vp->inner();
151
152 # Create a named tangent stack for this ViewPort
153 my $substack = $vp->create_tangent('name');
154
155 # Retrieve a tangent stack for this ViewPort
156 my $substack = $vp->forcus_tangent('name');
157
158 # Get the names of all the tangent stacks for this ViewPort
159 my @names = $vp->focus_tangents();
160
161 # Fetch all the tangent stacks for this ViewPort
162 # This is called by apply_events
163 my $stacks = $vp->child_event_sinks();
164
165
166 ### The following methods are all called automatically when using
167 ### Reaction::UI::Controller(s)
168 # Resolve current events with this ViewPort
169 $vp->apply_events($ctx, $param_hash);
170
b8faba69 171 # Apply current events to all tangent stacks
7adfd53f 172 # This is called by apply_events
173 $vp->apply_child_events($ctx, $params_hash);
174
175 # Apply current events to this ViewPort
176 # This is called by apply_events
177 $vp->apply_our_events($ctx, $params_hash);
178
179=head1 DESCRIPTION
180
181A ViewPort describes part of a page, it can be a field, a form or
182an entire page. ViewPorts are created on a
183L<Reaction::UI::FocusStack>, usually belonging to a controller or
184another ViewPort. Each ViewPort knows it's own position in the stack
185it is in, as well as the stack containing it.
186
187Each ViewPort has a specific location in the heirarchy of viewports
188making up a page. The hierarchy is determined as follows: The first
189ViewPort in a stack is labeled C<0>, the second is C<1> and so on. If
190a ViewPort is in a named tangent, it's location will contain the name
191of the tangent in it's location.
192
193For example, the first ViewPort in the 'left' tangent of the main
194ViewPort has location C<0.left.0>.
195
196Several ViewPort attributes are set by
197L<Reaction::UI::FocusStack/push_viewport> when new ViewPorts are
198created, these are as follows:
199
200=over
201
202=item Automatic:
203
204=over
205
206=item outer
207
208The outer attribute is set to the previous ViewPort in the stack when
209creating a ViewPort, if the ViewPort is the first in the stack, it
210will be undef.
211
212=item inner
213
214The inner attribute is set to the next ViewPort down in the stack when
215it is created, if this is the last ViewPort in the stack, it will be
216undef.
217
218=item focus_stack
219
220The focus_stack attribute is set to the L<Reaction::UI::FocusStack>
221object that created the ViewPort.
222
223=item ctx
224
225The ctx attribute will be passed automatically when using
226L<Reaction::UI::Controller/push_viewport> to create a ViewPort in the
227base stack of a controller. When creating tangent stacks, you may have
228to pass it in yourself.
229
230=back
231
232=item Optional:
233
234=over
235
236=item location
237
238=item layout
239
240The layout attribute can either be specifically passed when calling
241C<push_viewport>, or it will be determined using the last part of the
242ViewPorts classname.
243
244=item column_order
245
246This is generally used by more specialised ViewPorts such as the
247L<ListView|Reaction::UI::ViewPort::ListView> or
5ee24b95 248L<Action|Reaction::UI::ViewPort::Action>. It can be either a
7adfd53f 249space separated list of column names, or an arrayref of column names.
250
251=back
252
253=back
254
255=head1 METHODS
256
257=head2 outer
258
259=over
260
261=item Arguments: none
262
263=back
264
265Fetch the ViewPort outside this one in the page hierarchy.
266
267=head2 inner
268
269=over
270
271=item Arguments: none
272
273=back
274
275Fetch the ViewPort inside this one in the page hierarchy.
276
277=head2 create_tangent
278
279=over
280
281=item Arguments: $tangent_name
282
283=back
284
285Create a new named L<Reaction::UI::FocusStack> inside this
286ViewPort. The created FocusStack is returned.
287
288=head2 focus_tangent
289
290=over
291
292=item Arguments: $tangent_name
293
294=back
295
296Fetch a named FocusStack from this ViewPort.
297
298=head2 focus_tangents
299
300=over
301
302=item Arguments: none
303
304=back
305
306Returns a list of names of all the known tangents in this ViewPort.
307
308=head2 focus_stack
309
310Return the L<Reaction::UI::FocusStack> object that this ViewPort is in.
311
312=head2 apply_events
313
314=over
315
316=item Arguments: $ctx, $params_hashref
317
318=back
319
320This method is called by the FocusStack object to resolve all events
321for the ViewPort.
322
323=head2 apply_child_events
324
325=over
326
327=item Arguments: $ctx, $params_hashref
328
329=back
330
331Resolve the given events for all the tangents of this ViewPort. Called
332by L<apply_events>.
333
334=head2 apply_our_events
335
336=over
337
338=item Arguments: $ctx, $events
339
340=back
341
342Resolve the given events that match the location of this
343ViewPort. Called by L<apply_events>.
344
345=head2 handle_events
346
347=over
348
349=item Arguments: $events
350
351=back
352
353Actually call the event handlers for this ViewPort. Called by
354L<apply_our_events>. By default this will do nothing, subclass
355ViewPort and implement L<accept_events>.
356
357=head2 accept_events
358
359=over
360
361=item Arguments: none
362
363=back
364
365Implement this method in a subclass and return a list of events that
366your ViewPort is accepting.
367
368=head2 event_id_for
369
370=over
371
372=item Arguments: $name
373
374=back
375
376Create an id for the given event name and this ViewPort. Generally
377returns the location and the name, joined with a colon.
378
379=head2 sort_by_spec
380
381=over
382
383=item Arguments: $spec, $items
384
385=back
386
387Sorts the given list of items such that the ones that also appear in
388the spec are at the beginning. This is called by
5ee24b95 389L<Reaction::UI::ViewPort::Action> and
7adfd53f 390L<Reaction::UI::ViewPort::ListView>, and gets passed L<column_order>
391as the spec argument.
392
393=head1 AUTHORS
394
395See L<Reaction::Class> for authors.
396
397=head1 LICENSE
398
399See L<Reaction::Class> for the license.
400
401=cut