1package Reaction::UI::Window;
2
3use Reaction::Class;
4use Reaction::UI::FocusStack;
5
6use namespace::clean -except => [ qw(meta) ];
7
8
9has ctx => (isa => 'Catalyst', is => 'ro', required => 1, weak_ref => 1);
10has view_name => (isa => 'Str', is => 'ro', lazy_fail => 1);
11has content_type => (isa => 'Str', is => 'rw', lazy_fail => 1);
12has title => (isa => 'Str', is => 'rw', default => sub { 'Untitled window' });
13has 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);
19has focus_stack => (
20  isa => 'Reaction::UI::FocusStack',
21  is => 'ro', required => 1,
22  default => sub { Reaction::UI::FocusStack->new },
23);
24sub _build_view {
25  my ($self) = @_;
26  return $self->ctx->view($self->view_name);
27};
28sub 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};
38sub 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};
71sub 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
81sub 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
901;
91
92=head1 NAME
93
94Reaction::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
130A Window object is created and stored in the stash by
131L<Reaction::UI::Controller::Root>, it is used to contain all the
132elements (ViewPorts) that make up the UI. The Window is rendered in
133the end action of the Root Controller to make up the page.
134
135To add L<ViewPorts|Reaction::UI::ViewPort> to the stack, use the
136L<Reaction::UI::Controller/push_viewport> method. For more detailed
137information, read the L<Reaction::UI::FocusStack> and
138L<Reaction::UI::ViewPort> documentation.
139
140=head1 ATTRIBUTES
141
142These are set for you by L<Reaction::UI::Controller::Root/begin> from
143your Root controller configuration.
144
145=head2 ctx
146
147=over
148
149=item Arguments: $ctx?
150
151=back
152
153The current L<Catalyst> context object.
154
155=head2 view_name
156
157=over
158
159=item Arguments: $viewname?
160
161=back
162
163Retrieve/set the name of the L<Catalyst::View> component used to render
164this 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
174Retrieve the content_type for the page. If this has not been set,
175rendering the Window will fail.
176
177=head2 title
178
179=over
180
181=item Arguments: $title?
182
183=back
184
185  [% window.title %]
186
187Retrieve/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
198Retrieve the L<Catalyst::View> instance, this can be set, or will be
199instantiated 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
211Retrieve the L<stack|Reaction::UI::FocusStack> of
212L<ViewPorts|Reaction::UI::ViewPorts> that contains all the UI elements
213for this Window. Use L<Reaction::UI::FocusStack/push_viewport> on this
214to create more elements. An empty FocusStack is created by the
215Controller::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
227Synchronize the current events with all the L<Reaction::UI::ViewPort>
228objects in the UI, then render the root ViewPort. This is called for
229you by L<Reaction::UI::Controller::Root/end>.
230
231=head2 flush_events
232
233=over
234
235=item Arguments: none
236
237=back
238
239Resolves all the current events, first the query parameters then the
240body parameters, with all the L<Reaction::UI::ViewPort> objects in the
241UI. This calls L<Reaction::UI::FocusStack/apply_events>. This method
242is called by L<flush>.
243
244=head2 flush_view
245
246=over
247
248=item Arguments: none
249
250=back
251
252Renders the page into the L<Catalyst::Response> body, unless the
253response status is already set to 3xx, or the body has already been
254filled. This is done via L<Reaction::UI::View/render_window>. This
255method is called by L<flush>.
256
257=head1 AUTHORS
258
259See L<Reaction::Class> for authors.
260
261=head1 LICENSE
262
263See L<Reaction::Class> for the license.
264
265=cut
266