File Coverage

blib/lib/Maypole/Plugin/QuickTable.pm
Criterion Covered Total %
statement 15 99 15.1
branch 0 48 0.0
condition 0 35 0.0
subroutine 5 12 41.6
pod 4 4 100.0
total 24 198 12.1


line stmt bran cond sub pod time code
1             package Maypole::Plugin::QuickTable;
2              
3 1     1   31724 use warnings;
  1         2  
  1         25  
4 1     1   5 use strict;
  1         2  
  1         29  
5              
6 1     1   1062 use URI();
  1         7345  
  1         19  
7              
8 1     1   906 use NEXT;
  1         2435  
  1         29  
9              
10 1     1   955 use HTML::QuickTable;
  1         2994  
  1         1278  
11              
12             our $VERSION = 0.422;
13              
14             =head1 NAME
15              
16             Maypole::Plugin::QuickTable - HTML::QuickTable convenience
17              
18             =head1 SYNOPSIS
19              
20             use Maypole::Application qw( LinkTools QuickTable );
21              
22             =head1 METHODS
23              
24             =over
25              
26             =item setup
27              
28             =cut
29              
30             sub setup
31             {
32 0     0 1   my $r = shift;
33            
34 0           $r->NEXT::DISTINCT::setup( @_ );
35              
36 0 0         warn "Running " . __PACKAGE__ . " setup for $r" if $r->debug;
37            
38 0   0       my $model = $r->config->model ||
39             die "Please configure a model in $r before calling setup()";
40            
41 0 0         die __PACKAGE__ . " needs Maypole::Plugin::LinkTools"
42             unless $r->can( 'maybe_many_link_views' );
43            
44 0 0         warn "quicktable_defaults are shared by ALL models - cf. fb_defaults, which had this same bug"
45             if $r->debug > 1;
46 0           $model->mk_classdata( 'quicktable_defaults', {} );
47             }
48              
49             =item quick_table
50              
51             Returns a L object for formatting data.
52              
53             print $request->quick_table( %args )->render( $data );
54              
55             The method gathers arguments from the C method on the model class. This
56             is a L method, so you can set global
57             defaults on the main model class, and then override them in model subclasses. To preserve
58             most settings and override others, say something like
59              
60             $sub_model->quicktable_defaults( { %{ $model->quicktable_defaults }, %hash_of_overrides } );
61              
62             Arguments passed in the method call override those stored on the model.
63              
64             Arguments are passed directly to C<< HTML::QuickTable->new >>, so see L for a
65             description.
66              
67             Additional arguments are:
68              
69             object => a Maypole/CDBI object
70              
71             Pass a Maypole/CDBI object in the C slot, and its data will be extracted
72             and C<< $qt->render >> called for you:
73              
74             print $request->quick_table( %args, object => $object );
75            
76             Related objects will be displayed as links to their view template.
77              
78             If no object is supplied, a L object is returned. If an object is
79             supplied, it is passed to C to extract its data, and the data passed to the
80             C method of the L object.
81              
82             To render a subset of an object's columns, say:
83              
84             my @data = $request->tabulate( objects => $object, with_colnames => 1, fields => [ qw( foo bar ) ] );
85            
86             $request->quick_table( @data );
87              
88             =cut
89              
90             sub quick_table
91             {
92 0     0 1   my ( $self, %args ) = @_;
93            
94 0           my $object = delete $args{object};
95            
96             # this allows the caller to pass in some prepackaged data and get a table back
97 0 0         return HTML::QuickTable->new( %args ) unless $object;
98            
99 0   0       my $model_class = ref( $object ) || $object;
100            
101 0           %args = ( %{ $model_class->quicktable_defaults }, %args );
  0            
102            
103 0   0       $args{labels} ||= 1;
104            
105 0           my $qt = HTML::QuickTable->new( %args );
106            
107 0           return $qt->render( [ $self->tabulate( objects => $object, with_colnames => 1 ) ] );
108             }
109              
110             =item tabulate( $object|$arrayref_of_objects, %args )
111              
112             Extract data from a Maypole/CDBI object (or multiple objects), ready to pass to C<< quick_table->render >>.
113             Data will start with a row of column names if C<$args{with_colnames}> is true.
114              
115             A callback subref can be passed in C<$args{callback}>. It will be called in turn with each object as
116             its argument. The result(s) of the call will be added to the row of data for that object. See
117             the C template in L, which uses this technique
118             to add C and C buttons to each row.
119              
120             Similarly, a C coderef will be called during rendering of each field, receiving the
121             object and the current field as arguments. See the C template for an example.
122              
123             Arguments:
124              
125             callback coderef
126             field_callback coderef
127             with_colnames boolean
128             fields defaults to ( $request->model_class->display_columns, $request->model_class->related )
129             objects defaults to $request->objects
130              
131             =cut
132              
133             # HTML::QuickTable seems to accept an array of arrayrefs, which is undocumented, but
134             # simplifies this code - just pass whatever this returns, directly to render(). In fact,
135             # HTML::QuickTable::render() puts the data into an arrayref if it's supplied as an array,
136             # so it seems safe to rely on.
137             sub tabulate
138             {
139 0     0 1   my ( $self, %args ) = @_;
140            
141 0   0       my $objects = $args{objects} || $self->objects;
142            
143 0 0         my @objects = ref( $objects ) eq 'ARRAY' ? @$objects : ( $objects );
144              
145             # assumes all objects are in the same class
146 0   0       my $model_class = ref( $objects[0] ) || $objects[0];
147            
148             # If we're tabulating a set of search results, and the search returned no results,
149             # there are no objects. I'm not sure at the moment whether this will return the correct
150             # class in all cases - there might have been a template switcheroo, which was why this
151             # method looks at the object's class and not the request's model class anyway. But
152             # for the moment there's nothing else available:
153 0   0       $model_class ||= $self->model_class;
154            
155 0 0         my @fields = $args{fields} ? @{ $args{fields} } :
  0            
156             ( $model_class->view_columns, $model_class->view_fields );
157            
158 0           my @data = map { $self->_tabulate_object( $_, \@fields, $args{callback}, $args{field_callback} ) } @objects;
  0            
159            
160 0 0         return @data unless $args{with_colnames};
161            
162             # If no rows (e.g. no search results), return 1 empty row to cause the table
163             # headers to be printed correctly.
164 0 0         @data = ( [ ( '' ) x @fields ] ) unless @data;
165            
166 0           my %names = ( $model_class->column_names, $model_class->field_names );
167            
168 0           my @headers = $self->action eq 'list' ? $self->_make_linked_headers( $model_class, \@fields ) :
169 0 0         map { $names{ $_ } } @fields;
170              
171 0           unshift @data, \@headers;
172            
173 0           return @data;
174             }
175              
176             # build clickable column headers to control sorting - from Ron McClain
177             sub _make_linked_headers
178             {
179 0     0     my ( $self, $model_class, $fields ) = @_;
180              
181 0           my @headers;
182            
183 0           foreach my $field ( @$fields )
184             {
185 0           push @headers, $self->orderby_link( $field, $model_class );
186             }
187 0           return @headers;
188             }
189              
190             =item orderby_link( $field, [ $model_class ] )
191              
192             Build a link for a column header. Controls whether the table should be sorted by that
193             column. Toggles sort direction.
194              
195             The C<$model_class> parameter is only necessary when building a table for a class different
196             from the current model class for the request.
197              
198             =cut
199              
200             # build clickable column headers to control sorting - from Ron McClain
201             sub orderby_link
202             {
203 0     0 1   my ( $self, $field, $model_class ) = @_;
204            
205 0   0       $model_class ||= $self->model_class;
206            
207 0           my %names = $model_class->column_names;
208              
209             # take a copy so we can delete things from it without removing data used elsewhere
210 0           my %params = %{ $self->params };
  0            
211            
212             # these come from the search form on the initial search
213 0           my($order_by, $order_dir);
214 0 0         ( $order_by, $order_dir ) = split /\s+/, $params{search_opt_order_by} if $params{search_opt_order_by};
215              
216             # otherwise, from the header links
217 0 0         $order_by = $params{order} if $params{order};
218 0   0       $order_dir ||= $params{o2} || 'desc';
      0        
219 0 0         $order_dir = ( $order_dir eq 'desc' ) ? 'asc' : 'desc';
220 0           delete $params{search_opt_order_by};
221 0           delete $params{order_by};
222 0           delete $params{o2};
223 0           delete $params{page};
224              
225             # is this a column? - it might be a has_many field instead
226 0 0         if ( $names{ $field } )
227             {
228 0           my $uri = URI->new;
229            
230 0 0         if ( $self->action eq 'do_search' )
    0          
231             {
232 0           $params{search_opt_order_by} = "$field $order_dir"
233             }
234             elsif ( $self->action eq 'list' )
235             {
236 0           $params{order} = $field;
237 0           $params{o2} = $order_dir;
238             }
239             else
240             {
241 0           %params = ( order => $field,
242             o2 => $order_dir
243             );
244             }
245            
246 0           $uri->query_form( %params );
247            
248 0           my $arrow = '';
249            
250 0 0 0       if ( $order_by and $order_by eq $field )
251             {
252 0 0         $arrow = $order_dir eq 'asc' ? ' ↓' : ' ↑';
253             }
254            
255 0           my $args = "?".$uri->equery;
256            
257 0           return $self->link( table => $self->model_class->table,
258             action => $self->action,
259             additional => $args,
260             label => $names{ $field } . $arrow,
261             );
262             }
263             else
264             {
265             # has_many, might_have fields
266 0           my $related_class = $self->model_class->related_class( $self, $field );
267 0           my $field_name = $related_class->plural_moniker;
268 0           return ucfirst( $field_name );
269             }
270             }
271              
272             # Return an arrayref of values for a single object, which will be passed to
273             # QuickTable and rendered as a row in the table. The callback is optional, and
274             # can be used to add extra entries to the row. Column values that inflate to CDBI
275             # objects will be rendered as links to the view template. Column values that inflate
276             # to non-CDBI objects will be returned as the object, which will presumably be evaluated
277             # in string context at some point in QT render.
278             sub _tabulate_object
279             {
280 0     0     my ( $self, $object, $cols, $callback, $field_callback ) = @_;
281            
282 0   0       my $str_col = $object->stringify_column || ''; # '' to silence warnings in the map
283            
284 0 0 0       if ( $self->debug && ! $str_col )
285             {
286 0           warn sprintf "No stringify_column specified in %s - please define a 'Stringify' column " .
287             "group with a single column", ref( $object );
288             }
289            
290             my $lister = sub
291             {
292 0 0   0     return '' unless @_;
293 0 0         return @_ if @_ == 1;
294             #return join( "\n", '
    ', ( map { "
  1. $_
  2. " } @_ ), '
' );
295 0 0 0       return join ', ', @_ if @_ < 3 or $self->template =~ /view/;
296 0           return join ', ', $_[0], $_[1], @_ - 2 . ' more...';
297 0           };
298            
299             # XXX: getting a 'Use of uninitialized value in string eq warning' - looks like
300             # $object->stringify_column can return undef?
301 0 0         my @data = map { $self->maybe_link_view( $_ ) }
  0            
302            
303             # for the stringification column (e.g. 'name'), return the object, which
304             # will be translated into a link to the 'view' template by
305             # maybe_link_view. Otherwise, return the value, which will be rendered
306             # verbatim, unless it is an object in a related class, in which case
307             # it will be rendered as a link to the view template.
308 0           map { $_ eq $str_col ? $object : $lister->( $self->maybe_many_link_views( $object->$_ ) ) }
309             @$cols;
310            
311 0 0         if ( $field_callback )
312             {
313 0           @data = map { [ $_, $field_callback->( $object, shift( @$cols ) ) ] } @data;
  0            
314             }
315            
316 0 0         push( @data, $callback->( $object ) ) if $callback;
317            
318 0           return \@data;
319             }
320              
321             =back
322              
323             =head1 AUTHOR
324              
325             David Baird, C<< >>
326              
327             =head1 BUGS
328              
329             Please report any bugs or feature requests to
330             C, or through the web interface at
331             L.
332             I will be notified, and then you'll automatically be notified of progress on
333             your bug as I make changes.
334              
335             =head1 COPYRIGHT & LICENSE
336              
337             Copyright 2005 David Baird, All Rights Reserved.
338              
339             This program is free software; you can redistribute it and/or modify it
340             under the same terms as Perl itself.
341              
342             =cut
343              
344             1; # End of Maypole::Plugin::QuickTable