be82198af36de53eaf91bffe6c4e98042762c847
[catagits/Reaction.git] / lib / Reaction / UI / Window.pm
1 package Reaction::UI::Window;
2
3 use Reaction::Class;
4 use Reaction::UI::FocusStack;
5
6 use namespace::clean -except => [ qw(meta) ];
7
8
9 has ctx => (isa => 'Catalyst', is => 'ro', required => 1, weak_ref => 1);
10 has view_name => (isa => 'Str', is => 'ro', lazy_fail => 1);
11 has content_type => (isa => 'Str', is => 'rw', lazy_fail => 1);
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) = @_;
30   my $res = $self->ctx->res;
31   if ( $res->status =~ /^3/ || ( defined $res->body && length($res->body) ) ) {
32       $res->content_type('text/plain') unless $res->content_type;
33       return;
34   }
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";
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
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     }
69   }
70 };
71 sub flush_view {
72   my ($self) = @_;
73   my $res = $self->ctx->res;
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);
77   $res->content_type($self->content_type);
78 };
79
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);
85 };
86
87 __PACKAGE__->meta->make_immutable;
88
89
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
105   # More commonly, as Reaction::UI::Controller::Root creates one for you:
106   my $window = $ctx->stash->{window};
107
108   # Resolve current events and render the view of the UI
109   #  elements of this Window:
110   # This is called by the end action of Reaction::UI::Controller::Root
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
131 L<Reaction::UI::Controller::Root>, it is used to contain all the
132 elements (ViewPorts) that make up the UI. The Window is rendered in
133 the end action of the Root Controller to make up the page.
134
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.
139
140 =head1 ATTRIBUTES
141
142 These are set for you by L<Reaction::UI::Controller::Root/begin> from
143 your Root controller configuration.
144
145 =head2 ctx
146
147 =over
148
149 =item Arguments: $ctx?
150
151 =back
152
153 The current L<Catalyst> context object.
154
155 =head2 view_name
156
157 =over
158
159 =item Arguments: $viewname?
160
161 =back
162
163 Retrieve/set the name of the L<Catalyst::View> component used to render
164 this Window. If this has not been set, rendering the Window will fail.
165
166 =head2 content_type
167
168 =over
169
170 =item Arguments: $contenttype?
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
187 Retrieve/set the title of this page, if not set, it will default to
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
215 Controller::Root when the Window is created.
216
217 =head1 METHODS
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
229 you by L<Reaction::UI::Controller::Root/end>.
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
254 filled. This is done via L<Reaction::UI::View/render_window>. This
255 method is called by L<flush>.
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