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