File Coverage

blib/lib/JQuery/DataTables/Request.pm
Criterion Covered Total %
statement 134 144 93.0
branch 74 94 78.7
condition 28 36 77.7
subroutine 17 17 100.0
pod 8 8 100.0
total 261 299 87.2


line stmt bran cond sub pod time code
1             package JQuery::DataTables::Request;
2              
3 4     4   139193 use 5.012;
  4         17  
  4         179  
4 4     4   25 use strict;
  4         6  
  4         126  
5 4     4   21 use warnings;
  4         16  
  4         168  
6              
7             our $VERSION = '0.108'; # VERSION
8              
9 4     4   22 use Carp;
  4         23  
  4         378  
10              
11 4     4   22 use base 'Class::Accessor';
  4         8  
  4         5289  
12              
13             =head1 NAME
14              
15             JQuery::DataTables::Request - represents a DataTables server-side request
16              
17             =head1 SYNOPSIS
18              
19             my $dt_req = JQuery::DataTables::Request->new( $client_parameters );
20             if ( $dt_req->column(0)->{searchable} ) {
21             # do something
22             }
23              
24             $dt_req->search->{value}; # the global search value
25             if ($dt_req->search->{regex}) {
26             # global search is set to regex
27             }
28              
29             # find the column definition with the name 'col_name'
30             my $cols = $dt_req->find_columns( by_name => 'col_name' );
31              
32             $dt_req->draw; #sEcho or draw parameter
33             $dt_req->start; #iDisplayStart or start parameter
34              
35             =head1 DESCRIPTION
36              
37             This module represents a DataTables server-side request originating from the DataTables
38             client side JS library. There are two major versions of DataTables(v1.9 and v1.10) that send
39             differently named parameters server-side for processing. This module only provides an API
40             that corresponds to the v1.10 parameters but maps the v1.9 parameters to the corresponding v1.10
41             parameters.
42              
43             The DataTable parameters are documented at the following locations:
44              
45             =over
46              
47             =item L
48              
49             =item L
50              
51             =back
52              
53             Each column parameter is represented as a HashRef like so:
54              
55             {
56             name => 'col_name',
57             data => 'col_name',
58             orderable => 1,
59             searchable => 1,
60             search => {
61             value => 'search string',
62             regex => 0,
63             }
64             }
65              
66             e.g.
67              
68             $dt_req->column(0)->{search}{value}
69              
70             Order parameters look like this:
71              
72             {
73             dir => 'asc',
74             column => 1
75             }
76              
77             e.g.
78              
79             $dt_req->order(0)->{dir}
80              
81             The order and column accessors are indexed the same way as your column parameters so
82             C<< $req->column(0) >> returns the column in the client_params C<[columns][0]> column.
83              
84             C is similar in that C<< $req->order(0) >> returns the C parameter data.
85              
86             =head1 METHODS
87              
88             =cut
89              
90             # V1.10 accessors
91             __PACKAGE__->mk_accessors(qw(
92             draw
93             start
94             length
95             search
96             _version
97             _order
98             _columns
99             )
100             );
101              
102             =head2 new
103              
104             Creates a new JQuery::DataTables::Request object.
105              
106             my $dt_request = JQuery::DataTables::Request->new( client_params => $c->parameters );
107              
108             Accepts the following parameters
109              
110             =over
111              
112             =item client_params
113              
114             This is a HashRef that should contain your DataTables parameters as provided by the DataTables
115             JS library. Any parameters provided that are not recognized as DataTables request are silently ignored.
116              
117             =back
118              
119             new will confess/croak on the following scenarios:
120              
121             =over
122              
123             =item client_params is not provided
124              
125             =item client_params is not a HashRef
126              
127             =item client_params isn't recognized as containing DataTables parameters
128              
129             =back
130              
131             You should catch these if you are worried about it.
132              
133             =cut
134              
135             # client_params should be hashref
136             sub new {
137 6     6 1 8151 my ($class, %options) = @_;
138              
139 6 50       24 confess 'No DataTables parameters provided in the constructor - see client_params option'
140             unless defined($options{'client_params'});
141            
142 6 50       26 confess 'client_params must be a HashRef'
143             unless ref($options{'client_params'}) eq 'HASH';
144              
145 6         19 my $obj = bless {}, __PACKAGE__;
146              
147 6         28 my $version = $obj->version( $options{client_params} );
148 6 100 100     49 if (defined $version && $version eq '1.10') {
    100 66        
149 3         17 $obj->_process_v1_10_params( $options{'client_params'} );
150             } elsif (defined $version && $version eq '1.9') {
151 2         12 $obj->_process_v1_9_params( $options{'client_params'} );
152             } else {
153 1         256 confess 'client_params provided do not contain DataTables server-side parameters (i.e. this is not DataTables request data)';
154             }
155 5         35 $obj->_version( $version );
156 5         48 return $obj;
157             }
158              
159             =head2 column
160              
161             my \%column = $request->column(0);
162              
163             Returns a single column definition of the requested index
164              
165             =cut
166              
167             sub column {
168 6     6 1 1163 my ($self,$idx_arr) = @_;
169 6 50       16 return if !defined($idx_arr);
170 6         16 return $self->_columns->[$idx_arr];
171             }
172              
173             =head2 columns
174              
175             my \@columns = $request->columns([0,1]);
176              
177             Returns column definitions for the requested indexes. Can accept either an
178             arrayref of scalars or a single column scalar. If no column index is provided
179             all columns are returned.
180              
181             =cut
182              
183             sub columns {
184 9     9 1 879 my ($self, $idx_arr) = @_;
185 9         23 my $col_ref = $self->_columns;
186 9 100       109 return $col_ref if !defined($idx_arr);
187            
188 3 100       18 $idx_arr = [ $idx_arr ] if ref($idx_arr) ne 'ARRAY';
189              
190 3         3 my $ret_arr;
191 3         11 foreach my $idx ( sort @$idx_arr ) {
192 5         11 push(@$ret_arr, $col_ref->[$idx]);
193             }
194 3         19 return $ret_arr;
195             }
196              
197             =head2 columns_hashref
198              
199             Get all column definitions as a Hashref, with the column index as the key
200              
201             =cut
202              
203             sub columns_hashref {
204 1     1 1 3 my ($self) = @_;
205 1         2 my %col_hash;
206 1         2 @col_hash{ 0 .. $#{$self->_columns} } = @{$self->_columns};
  1         12  
  1         4  
207 1         16 return \%col_hash;
208             }
209              
210             =head2 find_columns
211              
212             $request->find_columns( %options )
213              
214             where C<%options> hash accepts the following parameters:
215              
216             =over
217              
218             =item by_name
219              
220             by_name accepts a scalar or arrayref of values and returns an arrayref of
221             column definitions
222              
223             my \@columns = $request->find_columns( by_name => ['col_name','col_name2'] );
224              
225             Searchs the columns C and/or C parameter.
226              
227             =item search_field
228              
229             my \@columns = $request->find_columns( by_name => 'something', search_field => 'name' );
230              
231             Set to either C or C to search those respective fields when
232             doing a C seach. If no search_field is specified, C searches
233             that match either field will be returned (i.e. defaults to search both fields)
234              
235             =item by_idx
236              
237             my \@columns = $request->find_columns( by_idx => $col_idx )
238              
239             This is just a passthrough to C<< $request->columns( $col_idx ); >>
240              
241             =back
242              
243             =cut
244              
245             sub find_columns {
246 3     3 1 883 my ($self, %options) = @_;
247 3 50       7 return unless %options;
248              
249 3 100       8 if (defined($options{by_idx})) {
250 1         7 return $self->columns($options{by_idx});
251             }
252              
253 2 50       6 if (my $searches = $options{by_name}) {
254 2         3 my $ret_cols;
255 2         3 my $key = $options{search_field};
256 2 50       7 $searches = [ $searches ] if ref($searches) ne 'ARRAY';
257 2         5 my $col_ref = $self->_columns;
258              
259 2         24 foreach my $search_val ( @$searches ) {
260 2         3 foreach my $col ( @$col_ref ) {
261 2 50       4 if ( defined $key ) {
262 0 0       0 if ( $col->{$key} eq $search_val ) {
263 0         0 push(@$ret_cols, $col);
264             }
265             } else {
266 2 50 33     8 if ( $col->{name} eq $search_val || $col->{data} eq $search_val ) {
267 2         7 push(@$ret_cols, $col);
268             }
269             }
270             }
271             }
272 2         12 return $ret_cols;
273             }
274             }
275              
276             =head2 order
277              
278             $req->order(0)->{dir}
279              
280             Returns the order data at provided index.
281              
282             =cut
283              
284             sub order
285             {
286 5     5 1 2202 my ($self,$idx) = @_;
287 5 50       13 return unless defined($idx);
288 5         13 return $self->_order->[$idx];
289             }
290              
291             =head2 orders
292              
293             $req->orders([0,1]);
294              
295             Returns an arrayref of the order data records at the provided indexes. Accepts an arrayref or scalar.
296             C<< ->orders([0,1]) >> will get C and C data.
297              
298             =cut
299              
300             sub orders
301             {
302 2     2 1 262 my ($self,$ar_idx) = @_;
303 2         7 my $ord_ref = $self->_order;
304 2 50       25 return $ord_ref unless defined($ar_idx);
305              
306 0 0       0 $ar_idx = [ $ar_idx ] unless ref($ar_idx) eq 'ARRAY';
307              
308 0         0 my $ret_arr;
309 0         0 foreach my $idx ( @$ar_idx ) {
310 0         0 push(@$ret_arr, $ord_ref->[$idx]);
311             }
312              
313 0         0 return $ret_arr;
314             }
315              
316             =head2 version
317              
318             my $version = $request->version( \%client_params? )
319              
320             Returns the version of DataTables we need to support based on the parameters sent.
321             v1.9 version of DataTables sends different named parameters than v1.10. Returns a string
322             of '1.9' if we think we have a 1.9 request, '1.10' if we think it is a 1.10 request or C
323             if we dont' think it is a DataTables request at all.
324              
325             This can be invoked as a class method as well as an instance method.
326              
327             =cut
328              
329             sub version
330             {
331 13     13 1 436 my ($self,$client_params) = @_;
332              
333 13 100 66     50 if (!ref($self) && !defined($client_params)) {
334 1         4 return;
335             }
336              
337 12 100       34 return $self->_version unless $client_params;
338 10         14 my $ref = $client_params;
339              
340             # v1.10 parameters
341 10 100 100     69 if (defined $ref->{draw} && defined $ref->{start} && defined $ref->{'length'}) {
      66        
342 4         16 return '1.10';
343             }
344              
345             # v1.9 parameters
346 6 50 66     43 if (defined $ref->{sEcho} && defined $ref->{iDisplayStart} && defined $ref->{iDisplayLength}) {
      66        
347 3         13 return '1.9';
348             }
349              
350 3         8 return;
351             }
352              
353             =head1 PRIVATE METHODS
354              
355              
356             =head2 _process_v1_9_params
357              
358             Processes v1.9 parameters, mapping them to 1.10 parameters
359              
360             $self->_process_v1_9_params( \%client_params )
361              
362             where C<\%client_params> is a HashRef containing the v1.9 parameters that DataTables
363             client library sends the server in server-side mode.
364              
365             =cut
366            
367             # maps 1.9 to 1.10 variables
368             # only thing not mapped is iColumns
369             my $vmap = {
370             top => {
371             'iDisplayStart' => 'start',
372             'iDisplayLength' => 'length',
373             'sEcho' => 'draw',
374             },
375             col_and_order => {
376             'bSearchable' => ['columns', 'searchable', undef],
377             'sSearch' => ['columns', 'search', 'value'],
378             'bRegex' => ['columns', 'search', 'regex'],
379             'bSortable' => ['columns', 'orderable', undef],
380             'mDataProp' => ['columns', 'data', undef],
381             'iSortCol' => ['order', 'column', undef],
382             'sSortDir' => ['order', 'dir', undef]
383             }
384             };
385              
386             sub _process_v1_9_params {
387 2     2   4 my ($self, $client_params) = @_;
388 2         3 my $columns;
389             my $order;
390 0         0 my $search;
391              
392 2         14 while ( my ($name,$val) = each %$client_params ) {
393             # handle top level parameters
394 28 100       132 if ( grep { $_ eq $name && $val =~ m/^[0-9]+$/ } keys %{$vmap->{top}} ) {
  84 100       353  
  28 100       72  
    100          
    100          
395 6         15 my $acc = $vmap->{top}->{$name};
396 6         24 $self->$acc( $val );
397             } elsif ($name eq 'sSearch') {
398 2         9 $search->{value} = $val;
399             } elsif ($name eq 'bRegex') {
400 2 50       31 $search->{regex} = $val eq 'true' ? 1 : 0;
401             } elsif ($name =~ m/^(?bSearchable|sSearch|bRegex|bSortable|iSortCol|sSortDir|mDataProp)_(?\d+)$/) {
402 4     4   19412 my $map = $vmap->{col_and_order}->{$+{param}};
  4         2521  
  4         4527  
  14         89  
403 14         67 my ($param,$idx,$sub_param1,$sub_param2,$new_val) =
404             $self->_validate_and_convert( $map->[0], $+{idx}, $map->[1], $map->[2], $val);
405              
406 14 100       52 if ($map->[0] eq 'columns') {
    50          
407 10 100       26 if (defined($sub_param2)) {
408 4         23 $columns->{$idx}{$sub_param1}{$sub_param2} = $new_val;
409             } else {
410 6         22 $columns->{$idx}{$sub_param1} = $new_val;
411             # copy name => data for v1.9 so that find_columns works as expected
412             # not really sure how to do this, Data::Alias? alias it eventually
413             # right now just copy
414 6 100       36 if ($sub_param1 eq 'data') {
415 2         11 $columns->{$idx}{'name'} = $new_val;
416             }
417             }
418             } elsif ($map->[0] eq 'order') {
419 4         30 $order->{$idx}{$sub_param1} = $new_val;
420             }
421             }
422             }
423              
424 2         4 my @col_arr;
425 2         13 push(@col_arr, $columns->{$_}) for ( sort keys %$columns );
426              
427 2         4 my @order_arr;
428 2         11 push(@order_arr, $order->{$_}) for ( sort keys %$order );
429              
430 2         9 $self->_columns( \@col_arr );
431 2         28 $self->_order( \@order_arr );
432 2         22 $self->search( $search );
433             }
434              
435             =head2 _process_v1_10_params
436              
437             $self->_process_v1_10_params( \%client_params );
438              
439             where C<\%client_params> is a HashRef containing the v1.10 parameters that DataTables
440             client library sends the server in server-side mode.
441              
442             =cut
443              
444             sub _process_v1_10_params {
445 3     3   7 my ($self, $client_params) = @_;
446              
447 3         6 my $columns;
448             my $order;
449 0         0 my $search;
450 3         20 while ( my ($name,$val) = each %$client_params ) {
451 47 100       66 $self->$name( $val ) if ( grep { $_ eq $name && $val =~ m/^[0-9]+$/ } qw(draw start length) );
  141 100       506  
452              
453 47 100       365 if ($name =~ m/^(?columns|order)\[(?[0-9]+)\]\[(?[^]]+)\](\[(?[^]]+)\])?$/) {
    100          
454 32         288 my ($param,$idx,$sub_param1,$sub_param2,$new_val) =
455             $self->_validate_and_convert($+{param}, $+{idx}+0, $+{sub_param1}, $+{sub_param2}, $val);
456              
457 32 100       130 if ($param eq 'columns') {
    50          
458 24 100       39 if (defined($sub_param2)) {
459 8         47 $columns->{$idx}{$sub_param1}{$sub_param2} = $new_val;
460             } else {
461 16         81 $columns->{$idx}{$sub_param1} = $new_val;
462             }
463             } elsif ($param eq 'order') {
464 8         50 $order->{$idx}{$sub_param1} = $new_val;
465             }
466             } elsif ($name =~ m/^search\[(?regex|value)\]$/) {
467 6         47 my $sp = $+{search_param};
468 6 100       23 if ($sp eq 'regex') {
469 3 100       23 $search->{$sp} = $val eq 'true' ? 1 : 0;
470             } else {
471 3         17 $search->{$sp} = $val;
472             }
473             }
474             }
475              
476 3         33 my @col_arr;
477 3         24 push(@col_arr, $columns->{$_}) for ( sort keys %$columns );
478              
479 3         7 my @order_arr;
480 3         16 push(@order_arr, $order->{$_}) for ( sort keys %$order );
481              
482 3         15 $self->_columns( \@col_arr );
483 3         41 $self->_order( \@order_arr );
484 3         31 $self->search( $search );
485 3         31 return $self;
486             }
487              
488             =head2 _validate_and_convert
489              
490             Validates parameters are set properly and does boolean conversion
491              
492             =cut
493              
494             #XXX: make this not a mess
495             sub _validate_and_convert
496             {
497 46     46   263 my ($self,$param,$idx,$sub1,$sub2,$val) = @_;
498 46 100       115 if ($param eq 'columns') {
    50          
499 34 100 100     213 if ($sub1 eq 'orderable' || $sub1 eq 'searchable') {
    100 100        
500 12 100       36 $val = lc $val eq 'true' ? 1 : 0;
501             } elsif ( $sub1 eq 'search' && $sub2 eq 'regex' ) {
502 6 100       19 $val = lc $val eq 'true' ? 1 : 0;
503             }
504             } elsif ($param eq 'order') {
505 12 50 100     72 if ($sub1 eq 'dir' && lc $val ne 'asc' && lc $val ne 'desc') {
      66        
506             #warn 'Unknown order[dir] value provided. Must be asc or desc, defaulting to asc';
507 0         0 $val = 'asc';
508             }
509             }
510 46         182 return ($param,$idx,$sub1,$sub2,$val);
511             }
512              
513             =head1 AUTHOR
514              
515             Mike Wisener Exmikew_cpan_orgE
516              
517             =head1 COPYRIGHT AND LICENSE
518              
519             Copyright E 2014 by Mike Wisener
520              
521             This library is free software; you can redistribute it and/or modify
522             it under the same terms as Perl itself.
523              
524             =cut
525              
526             1;