Commit | Line | Data |
7adfd53f |
1 | package Reaction::UI::Window; |
2 | |
3 | use Reaction::Class; |
4 | use Reaction::UI::FocusStack; |
5 | |
81393881 |
6 | use namespace::clean -except => [ qw(meta) ]; |
7 | |
8 | |
2082d8f2 |
9 | has ctx => (isa => 'Catalyst', is => 'ro', required => 1, weak_ref => 1); |
81393881 |
10 | has view_name => (isa => 'Str', is => 'ro', lazy_fail => 1); |
8b3b4820 |
11 | has content_type => (isa => 'Str', is => 'rw', lazy_fail => 1); |
81393881 |
12 | has title => (isa => 'Str', is => 'rw', default => sub { 'Untitled window' }); |
13 | has view => ( |
14 | # XXX compile failure because the Catalyst::View constraint would be |
15 | # auto-generated which doesn't work with unions. ::Types::Catalyst needed. |
16 | #isa => 'Catalyst::View|Reaction::UI::View', |
17 | isa => 'Object', is => 'ro', lazy_build => 1 |
18 | ); |
19 | has focus_stack => ( |
20 | isa => 'Reaction::UI::FocusStack', |
21 | is => 'ro', required => 1, |
22 | default => sub { Reaction::UI::FocusStack->new }, |
23 | ); |
24 | sub _build_view { |
25 | my ($self) = @_; |
26 | return $self->ctx->view($self->view_name); |
27 | }; |
28 | sub flush { |
29 | my ($self) = @_; |
bdfdc97f |
30 | my $res = $self->ctx->res; |
4746f5d6 |
31 | if ( $res->status =~ /^3/ || ( defined $res->body && length($res->body) ) ) { |
bdfdc97f |
32 | $res->content_type('text/plain') unless $res->content_type; |
33 | return; |
34 | } |
81393881 |
35 | $self->flush_events; |
36 | $self->flush_view; |
37 | }; |
38 | sub flush_events { |
39 | my ($self) = @_; |
40 | my $ctx = $self->ctx; |
41 | |
42 | #I really think we should make a copies of the parameter hashes here |
43 | #and then as we handle events, delete ethem from the event hashref, so |
44 | #that it thins down as it makes it down the viewport tree. which would |
45 | #limit the number of events that get to the children viewports. it wont |
46 | #save that many subcalls unless there is a lot of child_items, but it's |
47 | #more about doing the correct thing. It also avoids children viewports |
48 | #being able to see their parents' events, which leaves the door open for |
49 | #abuse of the system. thoughts anyone? |
50 | |
51 | foreach my $type (qw/query body/) { |
52 | my $meth = "${type}_parameters"; |
049d2234 |
53 | my $req_param = $ctx->req->$meth; |
54 | my $param_hash = { |
55 | map { |
56 | $_ =~ m/(^r.+\:\w+)\.(x|y)/ ? # for <input type="image"... buttons |
57 | ( $1 => $req_param->{$_} ) |
58 | : ( $_ => $req_param->{$_} ) |
59 | } keys %$req_param |
60 | }; # yeah, FocusStack deletes it |
4edebf11 |
61 | my @param_keys = keys %$param_hash; |
62 | if (@param_keys) { |
63 | for (@param_keys) { |
64 | utf8::decode($param_hash->{$_}) |
65 | unless (utf8::is_utf8($param_hash->{$_})); |
66 | } |
67 | $self->focus_stack->apply_events($param_hash); |
68 | } |
81393881 |
69 | } |
70 | }; |
71 | sub flush_view { |
72 | my ($self) = @_; |
73 | my $res = $self->ctx->res; |
4edebf11 |
74 | my $res_body = $self->view->render_window($self); |
75 | utf8::encode($res_body) if utf8::is_utf8($res_body); |
76 | $res->body($res_body); |
81393881 |
77 | $res->content_type($self->content_type); |
78 | }; |
7adfd53f |
79 | |
81393881 |
80 | # required by old Renderer::XHTML |
81 | sub render_viewport { |
82 | my ($self, $vp) = @_; |
83 | return unless $vp; |
84 | return $self->view->render_viewport($self, $vp); |
7adfd53f |
85 | }; |
86 | |
81393881 |
87 | __PACKAGE__->meta->make_immutable; |
88 | |
89 | |
7adfd53f |
90 | 1; |
91 | |
92 | =head1 NAME |
93 | |
94 | Reaction::UI::Window - Container for rendering the UI elements in |
95 | |
96 | =head1 SYNOPSIS |
97 | |
98 | my $window = Reaction::UI::Window->new( |
99 | ctx => $ctx, |
100 | view_name => $view_name, |
101 | content_type => $content_type, |
102 | title => $window_title, |
103 | ); |
104 | |
9964b409 |
105 | # More commonly, as Reaction::UI::Controller::Root creates one for you: |
7adfd53f |
106 | my $window = $ctx->stash->{window}; |
107 | |
89939ff9 |
108 | # Resolve current events and render the view of the UI |
7adfd53f |
109 | # elements of this Window: |
9964b409 |
110 | # This is called by the end action of Reaction::UI::Controller::Root |
7adfd53f |
111 | $window->flush(); |
112 | |
113 | # Resolve current events: |
114 | $window->flush_events(); |
115 | |
116 | # Render the top ViewPort in the FocusStack of this Window: |
117 | $window->flush_view(); |
118 | |
119 | # Render a particular ViewPort: |
120 | $window->render_viewport($viewport); |
121 | |
122 | # Or in a template: |
123 | [% window.render_viewport(self.inner) %] |
124 | |
125 | # Add a ViewPort to the UI: |
126 | $window->focus_stack->push_viewport('Reaction::UI::ViewPort'); |
127 | |
128 | =head1 DESCRIPTION |
129 | |
130 | A Window object is created and stored in the stash by |
9964b409 |
131 | L<Reaction::UI::Controller::Root>, it is used to contain all the |
7adfd53f |
132 | elements (ViewPorts) that make up the UI. The Window is rendered in |
9964b409 |
133 | the end action of the Root Controller to make up the page. |
7adfd53f |
134 | |
f1cd5548 |
135 | To add L<ViewPorts|Reaction::UI::ViewPort> to the stack, use the |
136 | L<Reaction::UI::Controller/push_viewport> method. For more detailed |
137 | information, read the L<Reaction::UI::FocusStack> and |
138 | L<Reaction::UI::ViewPort> documentation. |
7adfd53f |
139 | |
f1cd5548 |
140 | =head1 ATTRIBUTES |
7adfd53f |
141 | |
f1cd5548 |
142 | These are set for you by L<Reaction::UI::Controller::Root/begin> from |
143 | your Root controller configuration. |
7adfd53f |
144 | |
145 | =head2 ctx |
146 | |
147 | =over |
148 | |
9964b409 |
149 | =item Arguments: $ctx? |
7adfd53f |
150 | |
151 | =back |
152 | |
f1cd5548 |
153 | The current L<Catalyst> context object. |
7adfd53f |
154 | |
155 | =head2 view_name |
156 | |
157 | =over |
158 | |
f1cd5548 |
159 | =item Arguments: $viewname? |
7adfd53f |
160 | |
161 | =back |
162 | |
9964b409 |
163 | Retrieve/set the name of the L<Catalyst::View> component used to render |
7adfd53f |
164 | this Window. If this has not been set, rendering the Window will fail. |
165 | |
166 | =head2 content_type |
167 | |
168 | =over |
169 | |
9964b409 |
170 | =item Arguments: $contenttype? |
7adfd53f |
171 | |
172 | =back |
173 | |
174 | Retrieve the content_type for the page. If this has not been set, |
175 | rendering the Window will fail. |
176 | |
177 | =head2 title |
178 | |
179 | =over |
180 | |
181 | =item Arguments: $title? |
182 | |
183 | =back |
184 | |
185 | [% window.title %] |
186 | |
9964b409 |
187 | Retrieve/set the title of this page, if not set, it will default to |
7adfd53f |
188 | "Untitled window". |
189 | |
190 | =head2 view |
191 | |
192 | =over |
193 | |
194 | =item Arguments: none |
195 | |
196 | =back |
197 | |
198 | Retrieve the L<Catalyst::View> instance, this can be set, or will be |
199 | instantiated using the L<view_name> class. |
200 | |
201 | =head2 focus_stack |
202 | |
203 | =over |
204 | |
205 | =item Arguments: none |
206 | |
207 | =back |
208 | |
209 | $window->focus_stack->push_viewport('Reaction::UI::ViewPort'); |
210 | |
211 | Retrieve the L<stack|Reaction::UI::FocusStack> of |
212 | L<ViewPorts|Reaction::UI::ViewPorts> that contains all the UI elements |
213 | for this Window. Use L<Reaction::UI::FocusStack/push_viewport> on this |
214 | to create more elements. An empty FocusStack is created by the |
9964b409 |
215 | Controller::Root when the Window is created. |
7adfd53f |
216 | |
f1cd5548 |
217 | =head1 METHODS |
7adfd53f |
218 | |
219 | =head2 flush |
220 | |
221 | =over |
222 | |
223 | =item Arguments: none |
224 | |
225 | =back |
226 | |
227 | Synchronize the current events with all the L<Reaction::UI::ViewPort> |
228 | objects in the UI, then render the root ViewPort. This is called for |
9964b409 |
229 | you by L<Reaction::UI::Controller::Root/end>. |
7adfd53f |
230 | |
231 | =head2 flush_events |
232 | |
233 | =over |
234 | |
235 | =item Arguments: none |
236 | |
237 | =back |
238 | |
239 | Resolves all the current events, first the query parameters then the |
240 | body parameters, with all the L<Reaction::UI::ViewPort> objects in the |
241 | UI. This calls L<Reaction::UI::FocusStack/apply_events>. This method |
242 | is called by L<flush>. |
243 | |
244 | =head2 flush_view |
245 | |
246 | =over |
247 | |
248 | =item Arguments: none |
249 | |
250 | =back |
251 | |
252 | Renders the page into the L<Catalyst::Response> body, unless the |
253 | response status is already set to 3xx, or the body has already been |
f1cd5548 |
254 | filled. This is done via L<Reaction::UI::View/render_window>. This |
255 | method is called by L<flush>. |
7adfd53f |
256 | |
257 | =head1 AUTHORS |
258 | |
259 | See L<Reaction::Class> for authors. |
260 | |
261 | =head1 LICENSE |
262 | |
263 | See L<Reaction::Class> for the license. |
264 | |
265 | =cut |