completely new way of handling action prototypes for actions in CRUD that is much...
[catagits/Reaction.git] / lib / Reaction / UI / Controller / Collection.pm
CommitLineData
89b70ba7 1package Reaction::UI::Controller::Collection;
2
3use strict;
4use warnings;
5use base 'Reaction::UI::Controller';
6use Reaction::Class;
7
8cc0103d 8use aliased 'Reaction::UI::ViewPort::Collection::Grid';
c8fbb8ad 9use aliased 'Reaction::UI::ViewPort::Object';
89b70ba7 10
37728bba 11has model_name => (isa => 'Str', is => 'rw', required => 1);
12has collection_name => (isa => 'Str', is => 'rw', required => 1);
89b70ba7 13
37728bba 14has action_viewport_map => (isa => 'HashRef', is => 'rw', lazy_build => 1);
89b70ba7 15has action_viewport_args => (isa => 'HashRef', is => 'rw', lazy_build => 1);
16
37728bba 17has default_member_actions => (
18 isa => 'ArrayRef',
19 is => 'rw',
20 lazy_build => 1
21);
22
23has default_collection_actions => (
24 isa => 'ArrayRef',
25 is => 'rw',
26 lazy_build => 1
27);
28
29sub _build_default_member_actions { ['view'] }
30
31sub _build_default_collection_actions { [] }
32
89b70ba7 33sub _build_action_viewport_map {
37728bba 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;
89b70ba7 39}
40
41sub _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
62sub _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
73sub _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?
85sub get_collection {
86 my ($self, $c) = @_;
87 my $model = $c->model( $self->model_name );
0ccdac9b 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";
89b70ba7 96}
97
b3832dbc 98sub base :Action :CaptureArgs(0) {
89b70ba7 99 my ($self, $c) = @_;
89b70ba7 100}
101
102sub object :Chained('base') :PathPart('id') :CaptureArgs(1) {
103 my ($self, $c, $key) = @_;
1810d302 104 my $object = $self->get_collection($c)->find($key);
cb92a3a3 105 $c->detach("/error_404") unless $object;
1810d302 106 $c->stash(object => $object);
89b70ba7 107}
108
b3832dbc 109sub list :Chained('base') :PathPart('') :Args(0) {
110 my ($self, $c) = @_;
b68d6a35 111 $self->basic_page($c, { collection => $self->get_collection($c) });
b3832dbc 112}
113
89b70ba7 114sub view :Chained('object') :Args(0) {
115 my ($self, $c) = @_;
b68d6a35 116 $self->basic_page($c, { model => $c->stash->{object} });
89b70ba7 117}
118
aee256be 119sub basic_page {
89b70ba7 120 my ($self, $c, $vp_args) = @_;
aee256be 121 my $action_name = $c->stack->[-1]->name;
380db949 122 my $vp = $self->action_viewport_map->{$action_name},
123 my $args = $self->merge_config_hashes
89b70ba7 124 (
380db949 125 $vp_args || {},
126 $self->action_viewport_args->{$action_name} || {} ,
89b70ba7 127 );
380db949 128 return $self->push_viewport($vp, %$args);
89b70ba7 129}
130
1311;
b3832dbc 132
b3832dbc 133__END__;
134
135=head1 NAME
136
7d3fe0d2 137Reaction::UI::Controller
b3832dbc 138
139=head1 DESCRIPTION
140
141Controller class used to make displaying collections easier.
142Inherits from L<Reaction::UI::Controller>.
143
144=head1 ATTRIBUTES
145
146=head2 model_name
147
0ccdac9b 148The name of the model this controller will use as it's data source. Should be a
7d3fe0d2 149name that can be passed to C<$C-E<gt>model>
b3832dbc 150
151=head2 collection_name
152
0ccdac9b 153The name of the collection whithin the model that this Controller will be
7d3fe0d2 154utilizing.
b3832dbc 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
0ccdac9b 168Read-write lazy building hashref. The keys should match action names in the
169Controller and the value should be the ViewPort class that this action should
7d3fe0d2 170use. See method C<basic_page> for more info.
b3832dbc 171
de5b3fab 172=head2 action_viewport_args
b3832dbc 173
0ccdac9b 174Read-write lazy building hashref. Additional ViewPort arguments for the action
7d3fe0d2 175named as the key in the controller. See method C<basic_page> for more info.
b3832dbc 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=head1 METHODS
188
189=head2 get_collection $c
190
191Returns an instance of the collection this controller uses.
192
193=head2 _build_action_viewport_map
194
195Provided builder for C<action_viewport_map>. Returns a hash with two items:
196
197 list => 'Reaction::UI::ViewPort::ListView',
198 view => 'Reaction::UI::ViewPort::Object',
199
200=head2 _build_action_viewport_args
201
202Returns an empty hashref.
203
7d3fe0d2 204=head2 basic_page $c, \%vp_args
205
206Accepts two arguments, context, and a hashref of viewport arguments. It will
207automatically determine the action name using the catalyst stack and call
0ccdac9b 208C<push_viewport> with the ViewPort class name contained in the
7d3fe0d2 209C<action_viewport_map> with a set of options determined by merging C<$vp_args>
210and the arguments contained in C<action_viewport_args>, if any.
211
b3832dbc 212=head1 ACTIONS
213
214=head2 base
215
216Chain link, no-op.
217
218=head2 list
219
7d3fe0d2 220Chain link, chained to C<base>. C<list> fetches the collection for the model
221and calls C<basic_page> with a single argument, C<collection>.
b3832dbc 222
7d3fe0d2 223The default ViewPort for this action is C<Reaction::UI::ViewPort::ListView> and
224can be changed by altering the C<action_viewport_map> attribute hash.
b3832dbc 225
226=head2 object
227
0ccdac9b 228Chain link, chained to C<base>, captures one argument, 'id'. Attempts to find
7d3fe0d2 229a single object by searching for a member of the current collection which has a
230Primary Key or Unique constraint matching that argument. If the object is found
231it is stored in the stash under the C<object> key.
b3832dbc 232
233=head2 view
234
7d3fe0d2 235Chain link, chained to C<object>. Calls C<basic page> with one argument,
236C<model>, which contains an instance of the object fetched by the C<object>
237action link.
b3832dbc 238
0ccdac9b 239The default ViewPort for this action is C<Reaction::UI::ViewPort::Object> and
7d3fe0d2 240can be changed by altering the C<action_viewport_map> attribute hash.
b3832dbc 241
de5b3fab 242=head1 SEE ALSO
b3832dbc 243
7d3fe0d2 244L<Reaction::UI::Controller>
b3832dbc 245
246=head1 AUTHORS
247
248See L<Reaction::Class> for authors.
249
250=head1 LICENSE
251
252See L<Reaction::Class> for the license.
253
254=cut