3c815a8d843131efe077a9bee22429695069f1e2
[catagits/Reaction.git] / lib / Reaction / UI / Controller / Collection.pm
1 package Reaction::UI::Controller::Collection;
2
3 use strict;
4 use warnings;
5 use base 'Reaction::UI::Controller';
6 use Reaction::Class;
7
8 use aliased 'Reaction::UI::ViewPort::Collection::Grid';
9 use aliased 'Reaction::UI::ViewPort::Object';
10
11 has model_name => (isa => 'Str', is => 'rw', required => 1);
12 has collection_name => (isa => 'Str', is => 'rw', required => 1);
13
14 has action_viewport_map => (isa => 'HashRef', is => 'rw', lazy_build => 1);
15 has action_viewport_args => (isa => 'HashRef', is => 'rw', lazy_build => 1);
16
17 has default_member_actions => (
18   isa => 'ArrayRef',
19   is => 'rw',
20   lazy_build => 1
21 );
22
23 has default_collection_actions => (
24   isa => 'ArrayRef',
25   is => 'rw',
26   lazy_build => 1
27 );
28
29 sub _build_default_member_actions { ['view'] }
30
31 sub _build_default_collection_actions { [] }
32
33 sub _build_action_viewport_map {
34   my $self = shift;
35   my %map;
36   $map{list} = Grid;
37   $map{view} = Object; #if grep {$_ eq 'view'} @{$self->default_member_actions};
38   return \%map;
39 }
40
41 sub _build_action_viewport_args {
42   my $self = shift;
43   my $args = { list => { Member => {} } };
44
45   my $m_protos = $args->{list}{Member}{action_prototypes} = {};
46   for my $action_name( @{ $self->default_member_actions }){
47     my $label = ucfirst(join(' ', split(/_/, $action_name)));
48     my $proto = $self->_build_member_action_prototype($label, $action_name);
49     $m_protos->{$action_name} = $proto;
50   }
51
52   my $c_protos = $args->{list}{action_prototypes} = {};
53   for my $action_name( @{ $self->default_collection_actions }){
54     my $label = ucfirst(join(' ', split(/_/, $action_name)));
55     my $proto = $self->_build_collection_action_prototype($label, $action_name);
56     $c_protos->{$action_name} = $proto;
57   }
58
59   return $args;
60 }
61
62 sub _build_member_action_prototype {
63   my ($self, $label, $action_name) = @_;
64   return {
65     label => $label,
66     uri => sub {
67       my $action = $self->action_for($action_name);
68       $_[1]->uri_for($action, [ @{$_[1]->req->captures}, $_[0]->__id ]);
69     },
70   };
71 }
72
73 sub _build_collection_action_prototype {
74   my ($self, $label, $action_name) = @_;
75   return {
76     label => $label,
77     uri => sub {
78       my $action = $self->action_for($action_name);
79       $_[1]->uri_for($action, $_[1]->req->captures);
80     },
81   };
82 }
83
84 #XXX candidate for futre optimization, should cache reader?
85 sub get_collection {
86   my ($self, $c) = @_;
87   my $model = $c->model( $self->model_name );
88   confess "Failed to find Catalyst model named: " . $self->model_name
89     unless $model;
90   my $collection = $self->collection_name;
91   if( my $meth = $model->can( $collection ) ){
92     return $model->$meth;
93   } elsif ( my $meta = $model->can('meta') ){
94     if ( my $attr = $model->$meta->find_attribute_by_name($collection) ) {
95       my $reader = $attr->get_read_method;
96       return $model->$reader;
97     }
98   }
99   confess "Failed to find collection $collection";
100 }
101
102 sub base :Action :CaptureArgs(0) {
103   my ($self, $c) = @_;
104 }
105
106 sub object :Chained('base') :PathPart('id') :CaptureArgs(1) {
107   my ($self, $c, $key) = @_;
108   my $object = $self->get_collection($c)->find($key);
109   $c->detach("/error_404") unless $object;
110   $c->stash(object => $object);
111 }
112
113 sub list :Chained('base') :PathPart('') :Args(0) {
114   my ($self, $c) = @_;
115   my $collection = $c->stash->{collection} || $self->get_collection($c);
116   $self->basic_page($c, { collection => $collection });
117 }
118
119 sub view :Chained('object') :Args(0) {
120   my ($self, $c) = @_;
121   $self->basic_page($c, { model => $c->stash->{object} });
122 }
123
124 sub basic_page {
125   my ($self, $c, $vp_args) = @_;
126   my $action_name = $c->stack->[-1]->name;
127   my $vp = $self->action_viewport_map->{$action_name},
128   my $args = $self->merge_config_hashes
129     (
130      $vp_args || {},
131      $self->action_viewport_args->{$action_name} || {} ,
132     );
133   return $self->push_viewport($vp, %$args);
134 }
135
136 1;
137
138 __END__;
139
140 =head1 NAME
141
142 Reaction::UI::Controller
143
144 =head1 DESCRIPTION
145
146 Controller class used to make displaying collections easier.
147 Inherits from L<Reaction::UI::Controller>.
148
149 =head1 ATTRIBUTES
150
151 =head2 model_name
152
153 The name of the model this controller will use as it's data source. Should be a
154 name that can be passed to C<$C-E<gt>model>
155
156 =head2 collection_name
157
158 The name of the collection whithin the model that this Controller will be
159 utilizing.
160
161 =head2 action_viewport_map
162
163 =over 4
164
165 =item B<_build_action_viewport_map> - Provided builder method, see METHODS
166
167 =item B<has_action_viewport_map> - Auto generated predicate
168
169 =item B<clear_action_viewport_map>- Auto generated clearer method
170
171 =back
172
173 Read-write lazy building hashref. The keys should match action names in the
174 Controller and the value should be the ViewPort class that this action should
175 use. See method C<basic_page> for more info.
176
177 =head2 action_viewport_args
178
179 Read-write lazy building hashref. Additional ViewPort arguments for the action
180 named as the key in the controller.  See method C<basic_page> for more info.
181
182 =over 4
183
184 =item B<_build_action_viewport_args> - Provided builder method, see METHODS
185
186 =item B<has_action_viewport_args> - Auto generated predicate
187
188 =item B<clear_action_viewport_args>- Auto generated clearer method
189
190 =back
191
192 =head2 default_member_actions
193
194 Read-write lazy building arrayref. The names of the member actions (the actions
195 that apply to each member of the collection and typically have an object as a
196 target e.g. update,delete) to be enabled by default. By default, this is only
197 'view'
198
199 =over 4
200
201 =item B<_build_defualt_member_actions> - Provided builder method, see METHODS
202
203 =item B<has_default_member_actions> - Auto generated predicate
204
205 =item B<clear_default_member_actions>- Auto generated clearer method
206
207 =back
208
209 =head2 default_collection_actions
210
211 Read-write lazy building arrayref. The names of the collection actions (the
212 actions that apply to the entire collection and typically have a collection as
213 a target e.g. create, delete_all) to be enabled by default. By default, this
214 is only empty.
215
216 =over 4
217
218 =item B<_build_defualt_member_actions> - Provided builder method, see METHODS
219
220 =item B<has_default_member_actions> - Auto generated predicate
221
222 =item B<clear_default_member_actions>- Auto generated clearer method
223
224 =back
225
226 =head1 METHODS
227
228 =head2 get_collection $c
229
230 Returns an instance of the collection this controller uses.
231
232 =head2 _build_action_viewport_map
233
234 Provided builder for C<action_viewport_map>. Returns a hash containing:
235
236     list => 'Reaction::UI::ViewPort::Collection::Grid',
237     view => 'Reaction::UI::ViewPort::Object',
238
239 =head2 _build_action_viewport_args
240
241 By default will reurn a hashref containing action prototypes for all default
242 member and collection actions. The prototype URI generators are generated by
243 C<_build_member_action_prototype> and C<_build_collection_action_prototype>
244 respectively and labels are the result of replacing underscores in the name
245 with spaces and capitalizing the first letter. If you plan to use custom
246 actions that are not supported by this scheme or you would like to customize
247 the values it is suggested you wrap / override this method.
248
249 Default output for a controller having only 'view' enabled:
250
251     { list => {
252         action_prototypes => {},
253         Member => {
254           action_prototypes => {
255             view => {label => 'View', uri => sub{...} },
256           },
257         },
258       },
259     }
260
261 =head2 _build_member_action_prototype $label, $action_name
262
263 Creates an action prototype suitable for creating action links in
264 L<Reaction::UI::ViewPort::Role::Actions>. C<$action_name> should be the name of
265 a Catalyst action in this controller.The prototype will generate a URI
266 based on the action, current captures.
267
268 =head2 _build_collection_action_prototype $label, $action_name
269
270 =head2 basic_page $c, \%vp_args
271
272 Accepts two arguments, context, and a hashref of viewport arguments. It will
273 automatically determine the action name using the catalyst stack and call
274 C<push_viewport> with the ViewPort class name contained in the
275 C<action_viewport_map> with a set of options determined by merging C<$vp_args>
276 and the arguments contained in C<action_viewport_args>, if any.
277
278 =head1 ACTIONS
279
280 =head2 base
281
282 Chain link, no-op.
283
284 =head2 list
285
286 Chain link, chained to C<base>. C<list> fetches the collection for the model
287 and calls C<basic_page> with a single argument, C<collection>.
288
289 The default ViewPort for this action is C<Reaction::UI::ViewPort::ListView> and
290 can be changed by altering the C<action_viewport_map> attribute hash.
291
292 =head2 object
293
294 Chain link, chained to C<base>, captures one argument, 'id'. Attempts to find
295 a single object by searching for a member of the current collection which has a
296 Primary Key or Unique constraint matching that argument. If the object is found
297 it is stored in the stash under the C<object> key.
298
299 =head2 view
300
301 Chain link, chained to C<object>. Calls C<basic page> with one argument,
302 C<model>, which contains an instance of the object fetched by the C<object>
303 action link.
304
305 The default ViewPort for this action is C<Reaction::UI::ViewPort::Object> and
306 can be changed by altering the C<action_viewport_map> attribute hash.
307
308 =head1 SEE ALSO
309
310 L<Reaction::UI::Controller>
311
312 =head1 AUTHORS
313
314 See L<Reaction::Class> for authors.
315
316 =head1 LICENSE
317
318 See L<Reaction::Class> for the license.
319
320 =cut