checking in more pod, some fixes for weird bugs, a manual for migration, an extra...
[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   my $collection = $self->collection_name;
89   if( my $meth = $model->can( $collection ) ){
90     return $model->$meth;
91   } elsif ( my $attr = $model->meta->find_attribute_by_name($collection) ) {
92     my $reader = $attr->get_read_method;
93     return $model->$reader;
94   }
95   confess "Failed to find collection $collection";
96 }
97
98 sub base :Action :CaptureArgs(0) {
99   my ($self, $c) = @_;
100 }
101
102 sub object :Chained('base') :PathPart('id') :CaptureArgs(1) {
103   my ($self, $c, $key) = @_;
104   my $object = $self->get_collection($c)->find($key);
105   $c->detach("/error_404") unless $object;
106   $c->stash(object => $object);
107 }
108
109 sub list :Chained('base') :PathPart('') :Args(0) {
110   my ($self, $c) = @_;
111   $self->basic_page($c, { collection => $self->get_collection($c) });
112 }
113
114 sub view :Chained('object') :Args(0) {
115   my ($self, $c) = @_;
116   $self->basic_page($c, { model => $c->stash->{object} });
117 }
118
119 sub basic_page {
120   my ($self, $c, $vp_args) = @_;
121   my $action_name = $c->stack->[-1]->name;
122   my $vp = $self->action_viewport_map->{$action_name},
123   my $args = $self->merge_config_hashes
124     (
125      $vp_args || {},
126      $self->action_viewport_args->{$action_name} || {} ,
127     );
128   return $self->push_viewport($vp, %$args);
129 }
130
131 1;
132
133 __END__;
134
135 =head1 NAME
136
137 Reaction::UI::Controller
138
139 =head1 DESCRIPTION
140
141 Controller class used to make displaying collections easier.
142 Inherits from L<Reaction::UI::Controller>.
143
144 =head1 ATTRIBUTES
145
146 =head2 model_name
147
148 The name of the model this controller will use as it's data source. Should be a
149 name that can be passed to C<$C-E<gt>model>
150
151 =head2 collection_name
152
153 The name of the collection whithin the model that this Controller will be
154 utilizing.
155
156 =head2 action_viewport_map
157
158 =over 4
159
160 =item B<_build_action_viewport_map> - Provided builder method, see METHODS
161
162 =item B<has_action_viewport_map> - Auto generated predicate
163
164 =item B<clear_action_viewport_map>- Auto generated clearer method
165
166 =back
167
168 Read-write lazy building hashref. The keys should match action names in the
169 Controller and the value should be the ViewPort class that this action should
170 use. See method C<basic_page> for more info.
171
172 =head2 action_viewport_args
173
174 Read-write lazy building hashref. Additional ViewPort arguments for the action
175 named as the key in the controller.  See method C<basic_page> for more info.
176
177 =over 4
178
179 =item B<_build_action_viewport_args> - Provided builder method, see METHODS
180
181 =item B<has_action_viewport_args> - Auto generated predicate
182
183 =item B<clear_action_viewport_args>- Auto generated clearer method
184
185 =back
186
187 =head2 default_member_actions
188
189 Read-write lazy building arrayref. The names of the member actions (the actions
190 that apply to each member of the collection and typically have an object as a
191 target e.g. update,delete) to be enabled by default. By default, this is only
192 'view'
193
194 =over 4
195
196 =item B<_build_defualt_member_actions - Provided builder method, see METHODS
197
198 =item B<has_default_member_actions> - Auto generated predicate
199
200 =item B<clear_default_member_actions>- Auto generated clearer method
201
202 =back
203
204 =head2 default_collection_actions
205
206 Read-write lazy building arrayref. The names of the collection actions (the
207 actions that apply to the entire collection and typically have a collection as
208 a target e.g. create, delete_all) to be enabled by default. By default, this
209 is only empty.
210
211 =over 4
212
213 =item B<_build_defualt_member_actions - Provided builder method, see METHODS
214
215 =item B<has_default_member_actions> - Auto generated predicate
216
217 =item B<clear_default_member_actions>- Auto generated clearer method
218
219 =back
220
221 =head1 METHODS
222
223 =head2 get_collection $c
224
225 Returns an instance of the collection this controller uses.
226
227 =head2 _build_action_viewport_map
228
229 Provided builder for C<action_viewport_map>. Returns a hash containing:
230
231     list => 'Reaction::UI::ViewPort::Collection::Grid',
232     view => 'Reaction::UI::ViewPort::Object',
233
234 =head2 _build_action_viewport_args
235
236 By default will reurn a hashref containing action prototypes for all default
237 member and collection actions. The prototype URI generators are generated by
238 C<_build_member_action_prototype> and C<_build_collection_action_prototype>
239 respectively and labels are the result of replacing underscores in the name
240 with spaces and capitalizing the first letter. If you plan to use custom
241 actions that are not supported by this scheme or you would like to customize
242 the values it is suggested you wrap / override this method.
243
244 Default output for a controller having only 'view' enabled:
245
246     { list => {
247         action_prototypes => {},
248         Member => {
249           action_prototypes => {
250             view => {label => 'View', uri => sub{...} },
251           },
252         },
253       },
254     }
255
256 =head2 _build_member_action_prototype $label, $action_name
257
258 Creates an action prototype suitable for creating action links in
259 L<Reaction::UI::ViewPort::Role::Actions>. C<$action_name> should be the name of
260 a Catalyst action in this controller.The prototype will generate a URI
261 based on the action, current captures.
262
263 =head2 _build_collection_action_prototype $label, $action_name
264
265 =head2 basic_page $c, \%vp_args
266
267 Accepts two arguments, context, and a hashref of viewport arguments. It will
268 automatically determine the action name using the catalyst stack and call
269 C<push_viewport> with the ViewPort class name contained in the
270 C<action_viewport_map> with a set of options determined by merging C<$vp_args>
271 and the arguments contained in C<action_viewport_args>, if any.
272
273 =head1 ACTIONS
274
275 =head2 base
276
277 Chain link, no-op.
278
279 =head2 list
280
281 Chain link, chained to C<base>. C<list> fetches the collection for the model
282 and calls C<basic_page> with a single argument, C<collection>.
283
284 The default ViewPort for this action is C<Reaction::UI::ViewPort::ListView> and
285 can be changed by altering the C<action_viewport_map> attribute hash.
286
287 =head2 object
288
289 Chain link, chained to C<base>, captures one argument, 'id'. Attempts to find
290 a single object by searching for a member of the current collection which has a
291 Primary Key or Unique constraint matching that argument. If the object is found
292 it is stored in the stash under the C<object> key.
293
294 =head2 view
295
296 Chain link, chained to C<object>. Calls C<basic page> with one argument,
297 C<model>, which contains an instance of the object fetched by the C<object>
298 action link.
299
300 The default ViewPort for this action is C<Reaction::UI::ViewPort::Object> and
301 can be changed by altering the C<action_viewport_map> attribute hash.
302
303 =head1 SEE ALSO
304
305 L<Reaction::UI::Controller>
306
307 =head1 AUTHORS
308
309 See L<Reaction::Class> for authors.
310
311 =head1 LICENSE
312
313 See L<Reaction::Class> for the license.
314
315 =cut