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