Much documentation updates
[catagits/Reaction.git] / lib / Reaction / UI / View.pm
CommitLineData
7adfd53f 1package Reaction::UI::View;
2
3use Reaction::Class;
4
5# declaring dependencies
7adfd53f 6use Reaction::UI::LayoutSet;
7use Reaction::UI::RenderingContext;
8a293e2e 8use aliased 'Reaction::UI::Skin';
9use aliased 'Path::Class::Dir';
7adfd53f 10
81393881 11use namespace::clean -except => [ qw(meta) ];
7adfd53f 12
7adfd53f 13
81393881 14has '_widget_cache' => (is => 'ro', default => sub { {} });
8a293e2e 15
81393881 16has '_layout_set_cache' => (is => 'ro', default => sub { {} });
7adfd53f 17
81393881 18has 'app' => (is => 'ro', required => 1);
7adfd53f 19
f94f51bf 20has 'skin_name' => (is => 'ro', required => 1, default => 'default');
7adfd53f 21
81393881 22has 'skin' => (
23 is => 'ro', lazy_build => 1,
24 handles => [ qw(create_layout_set search_path_for_type) ]
25);
7adfd53f 26
81393881 27has 'layout_set_class' => (is => 'ro', lazy_build => 1);
7adfd53f 28
81393881 29has 'rendering_context_class' => (is => 'ro', lazy_build => 1);
30sub _build_layout_set_class {
31 my ($self) = @_;
32 return $self->find_related_class('LayoutSet');
33};
34sub _build_rendering_context_class {
35 my ($self) = @_;
36 return $self->find_related_class('RenderingContext');
37};
38sub _build_skin {
39 my ($self) = @_;
40 Skin->new(
41 name => $self->skin_name, view => $self,
42 # path_to returns a File, not a Dir. Thanks, Catalyst.
43 skin_base_dir => Dir->new($self->app->path_to('share', 'skin')),
44 );
45};
46sub COMPONENT {
47 my ($class, $app, $args) = @_;
48 return $class->new(%{$args||{}}, app => $app);
49};
50sub render_window {
51 my ($self, $window) = @_;
52 my $root_vp = $window->focus_stack->vp_head;
53 my $rctx = $self->create_rendering_context;
54 my ($widget, $args) = $self->render_viewport_args($root_vp);
55 $widget->render(widget => $rctx, $args);
56};
57sub render_viewport_args {
58 my ($self, $vp) = @_;
59 my $layout_set = $self->layout_set_for($vp);
60 my $widget = $self->widget_for($vp, $layout_set);
61 return ($widget, { viewport => $vp });
62};
63sub widget_for {
64 my ($self, $vp, $layout_set) = @_;
65 return
66 $self->_widget_cache->{$layout_set->name}
67 ||= $layout_set->widget_class
68 ->new(
69 view => $self, layout_set => $layout_set
70 );
71};
72sub layout_set_for {
73 my ($self, $vp) = @_;
74 my $lset_name = eval { $vp->layout };
75 confess "Couldn't call layout method on \$vp arg ${vp}: $@" if $@;
76 $lset_name = $self->layout_set_name_from_viewport( blessed($vp) )
77 unless (length($lset_name));
78 my $cache = $self->_layout_set_cache;
79 return $cache->{$lset_name} ||= $self->create_layout_set($lset_name);
80};
349a57a4 81
81393881 82#XXX if it ever comes to it: this could be memoized. not bothering yet.
83sub layout_set_name_from_viewport {
84 my ($self, $class) = @_;
85 my ($last) = ($class =~ /.*(?:::ViewPort::)(.+?)$/);
86 #split when a non-uppercase letter meets an uppercase or when an
87 #uppercase letter is followed by another uppercase and then a non-uppercase
88 #FooBar = foo_bar; Foo_Bar = foo_bar; FOOBar = foo_bar; FooBAR = foo_bar
89 my @fragments = map {
90 join("_", split(/(?:(?<=[A-Z])(?=[A-Z][^_A-Z])|(?<=[^_A-Z])(?=[A-Z]))/, $_))
91 } split('::', $last);
92 return lc(join('/', @fragments));
93};
94sub layout_set_file_extension {
95 confess View." is abstract, you must subclass it";
96};
97sub find_related_class {
98 my ($self, $rel) = @_;
99 my $own_class = ref($self) || $self;
100 confess View." is abstract, you must subclass it" if $own_class eq View;
101 foreach my $super ($own_class->meta->class_precedence_list) {
102 next if $super eq View;
103 if ($super =~ /::View::/) {
104 (my $class = $super) =~ s/::View::/::${rel}::/;
105 if (eval { Class::MOP::load_class($class) }) {
106 return $class;
7adfd53f 107 }
108 }
81393881 109 }
110 confess "Unable to find related ${rel} class for ${own_class}";
111};
112sub create_rendering_context {
113 my ($self, @args) = @_;
114 return $self->rendering_context_class->new(
115 $self->rendering_context_args_for(@args),
116 @args,
117 );
118};
119sub rendering_context_args_for {
120 return ();
121};
122sub layout_set_args_for {
123 return ();
124};
7adfd53f 125
81393881 126__PACKAGE__->meta->make_immutable;
8a293e2e 127
f1cd5548 128=pod
129
130=head1 NAME
131
132Reaction::UI::View - Render the UI.
133
134=head1 SYNOPSIS
135
136 package MyApp::View::TT;
137 use base 'Reaction::UI::View::TT';
138
139 __PACKAGE__->config(
140 skin_name => 'MyApp',
141 );
142
143 ## In the Window class:
144 $res->body($self->view->render_window($self));
145
146=head1 DESCRIPTION
147
148Render the viewports in the current window using the chosen skin and
149layoutset, via the matching widgets.
150
151See also:
152
153=over
154
155=item L<Reaction::UI::Controller::Root>
156=item L<Reaction::UI::ViewPort>
157=item L<Reaction::UI::Window>
158=item L<Reaction::UI::LayoutSet>
159=item L<Reaction::UI::Widget>
160
161=back
162
163=head1 ATTRIBUTES
164
165=head2 app
166
167=over
168
169=item Arguments: $app?
170
171=back
172
173The application L<Catalyst> class. This is set at
174L<Catalyst/COMPONENT> time for you.
175
176=head2 skin_name
177
178=over
179
180=item Arguments: $skinname?
181
182=back
183
184The name of the skin to use to render the pages. This should be the
185name of a subdirectory under the F<share/skin> in your application
186directory. The default skin name is C<default>, the default skin is
187provided with Reaction.
188
189See also: L<Reaction::UI::Skin>
190
191=head2 skin
192
193=over
194
195=item Arguments: $skin?
196
197=back
198
199A L<Reaction::UI::Skin> object based on the L</skin_name>. It will be
200created for you if not provided.
201
202=head2 layout_set_class
203
204The class of the L<Reaction::UI::LayoutSet> used to layout the
205view. Defaults to searching down the precedence tree of the View class
206looking for a class of the same name with C<View> replaced with
207C<LayoutSet>.
208
209=head2 rendering_context_class
210
211The class of the L<Reaction::UI::RenderingContext> used to layout the
212view. Defaults to searching down the precedence tree of the View class
213looking for a class of the same name with C<View> replaced with
214C<RenderingContext>.
215
216=head1 METHODS
217
218=head1 AUTHORS
219
220See L<Reaction::Class> for authors.
221
222=head1 LICENSE
223
224See L<Reaction::Class> for the license.
225
226=cut
7adfd53f 227
2281;