File Coverage

blib/lib/Mojolicious/Plugin/DataTables.pm
Criterion Covered Total %
statement 39 164 23.7
branch 0 54 0.0
condition 2 32 6.2
subroutine 12 16 75.0
pod 1 1 100.0
total 54 267 20.2


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::DataTables;
2              
3 2     2   59482 use Mojo::Base 'Mojolicious::Plugin';
  2         162756  
  2         13  
4 2     2   1375 use Mojo::JSON qw(decode_json encode_json true false);
  2         17881  
  2         120  
5 2     2   482 use Mojo::Collection;
  2         3614  
  2         66  
6 2     2   429 use Mojo::DOM::HTML;
  2         2880  
  2         88  
7 2     2   14 use Mojo::ByteStream;
  2         2  
  2         59  
8              
9 2     2   959 use Mojolicious::Plugin::DataTables::SSP::Column;
  2         6  
  2         12  
10 2     2   837 use Mojolicious::Plugin::DataTables::SSP::Params;
  2         23  
  2         15  
11 2     2   849 use Mojolicious::Plugin::DataTables::SSP::Results;
  2         5  
  2         15  
12              
13             our $VERSION = '1.02';
14              
15             sub register {
16              
17 1     1 1 43 my ( $c, $app, $conf ) = @_;
18              
19 1         8 $app->helper( 'datatable_js' => \&_dt_js );
20 1         130 $app->helper( 'datatable_css' => \&_dt_css );
21 1         78 $app->helper( 'datatable.ssp' => \&_dt_ssp );
22 1         326 $app->helper( 'datatable.ssp_params' => \&_dt_ssp_params );
23 1         249 $app->helper( 'datatable.ssp_results' => \&_dt_ssp_results );
24              
25             }
26              
27             sub _dt_js {
28              
29 1     1   18442 my ( $c, $url ) = @_;
30              
31 1         2 my $dt_version = '1.10.20';
32 1   33     9 my $dt_js_url = $url || "//cdn.datatables.net/$dt_version/js/jquery.dataTables.min.js";
33              
34 1         4 return _tag( 'script', 'src' => $dt_js_url );
35              
36             }
37              
38             sub _dt_css {
39              
40 1     1   11285 my ( $c, $url ) = @_;
41              
42 1         3 my $dt_version = '1.10.20';
43 1   33     8 my $dt_js_url = $url || "//cdn.datatables.net/$dt_version/css/jquery.dataTables.min.css";
44              
45 1         4 return _tag( 'link', 'rel' => 'stylesheet', 'href' => $dt_js_url );
46              
47             }
48              
49             sub _dt_ssp {
50              
51 0     0   0 my ( $c, %args ) = @_;
52              
53 0         0 my $table = delete $args{table};
54 0         0 my $options = delete $args{options};
55 0         0 my $db = delete $args{db};
56 0         0 my @columns = delete $args{columns};
57 0         0 my $debug = delete $args{debug};
58 0         0 my $where = delete $args{where};
59              
60 0 0       0 if ( !$db ) {
61 0 0       0 $c->app->log->error('Missing "db" param') if ($debug);
62 0         0 return {};
63             }
64              
65 0         0 my $regexp_operator = 'REGEXP'; # REGEXP operator for MySQL and SQLite
66 0         0 my $db_type = ref $db;
67              
68             # "~" operator for PostgreSQL
69 0 0       0 if ( $db_type =~ /Mojo::Pg/ ) {
70 0         0 $regexp_operator = '~';
71             }
72              
73 0         0 my $ssp = $c->datatable->ssp_params($options);
74              
75 0 0       0 return {} if ( !$ssp->draw );
76              
77 0         0 my $db_filter = '';
78 0         0 my @db_filters = ();
79 0         0 my $db_order = '';
80 0         0 my @db_bind = ();
81 0         0 my @db_columns = $ssp->db_columns;
82              
83 0         0 push @db_columns, @columns;
84              
85 0 0       0 if ($where) {
86 0         0 push @db_filters, $where;
87             }
88              
89             # Column filter
90 0         0 my @col_filters;
91              
92 0         0 foreach ( @{ $ssp->columns } ) {
  0         0  
93 0 0 0     0 if ( $_->database && $_->searchable != 0 && $_->search->{value} ) {
      0        
94              
95 0 0       0 if ( $_->search->{regex} ) {
96 0         0 push @col_filters, $_->database . " $regexp_operator ?";
97 0         0 push @db_bind, $_->search->{value};
98             } else {
99 0         0 push @col_filters, $_->database . " LIKE ?";
100 0         0 push @db_bind, '%' . $_->search->{value} . '%';
101             }
102              
103             }
104             }
105              
106 0 0       0 if (@col_filters) {
107 0         0 push @db_filters, '(' . join( ' AND ', @col_filters ) . ')';
108             }
109              
110             # Global Search
111 0 0       0 if ( $ssp->search->{value} ) {
112              
113 0         0 my @global_filters;
114              
115 0         0 foreach ( @{ $ssp->columns } ) {
  0         0  
116 0 0 0     0 if ( $_->database && $_->searchable != 0 ) {
117              
118 0 0       0 if ( $ssp->search->{regex} ) {
119 0         0 push @global_filters, $_->database . " $regexp_operator ?";
120 0         0 push @db_bind, $ssp->search->{value};
121             } else {
122 0         0 push @global_filters, $_->database . " LIKE ?";
123 0         0 push @db_bind, '%' . $ssp->search->{value} . '%';
124             }
125              
126             }
127             }
128              
129 0 0       0 if (@global_filters) {
130 0         0 push @db_filters, '(' . join( ' OR ', @global_filters ) . ')';
131             }
132              
133             }
134              
135             # Filter
136 0 0       0 if (@db_filters) {
137 0         0 $db_filter = 'WHERE ' . join( ' AND ', @db_filters );
138             }
139              
140             # Order
141 0 0       0 if ( %{ $ssp->db_order } ) {
  0         0  
142              
143 0         0 my @db_orders;
144 0         0 my $order = $ssp->db_order;
145              
146 0         0 foreach ( keys %{$order} ) {
  0         0  
147 0         0 push @db_orders, $_ . ' ' . $order->{$_};
148             }
149              
150 0         0 $db_order = 'ORDER BY ' . join( ',', @db_orders );
151              
152             }
153              
154 0         0 my $sql = sprintf(
155             'SELECT %s FROM %s %s %s LIMIT %s OFFSET %s',
156             join( ',', @db_columns ),
157             $table, $db_filter, $db_order, $ssp->length, $ssp->start
158             );
159              
160 0 0       0 if ($debug) {
161 0         0 $c->app->log->debug("SSP - Query: $sql");
162 0         0 $c->app->log->debug( "SSP - Bind: " . encode_json \@db_bind );
163             }
164              
165 0         0 my $query = $db->query( $sql, @db_bind );
166              
167 0         0 my @results;
168              
169 0         0 while ( my $row = $query->hash ) {
170              
171 0         0 my $data = {};
172              
173 0         0 foreach my $column ( @{ $ssp->columns } ) {
  0         0  
174              
175 0         0 $column->row($row);
176              
177 0   0     0 my $col_db = $column->database || '';
178 0         0 my $col_value = $row->{$col_db};
179              
180 0 0       0 if ( ref $column->formatter eq 'CODE' ) {
181 0         0 $col_value = $column->formatter->( $col_value, $column );
182             }
183              
184 0         0 $data->{ $column->data } = $col_value;
185              
186             }
187              
188 0         0 push @results, $data;
189              
190             }
191              
192 0         0 my $total = $db->query( sprintf( 'SELECT COUNT(*) AS TOT FROM %s', $table ) )->hash->{tot};
193 0         0 my $filtered = $total;
194              
195 0 0       0 if (@db_bind) {
196             $filtered
197 0         0 = $db->query( sprintf( 'SELECT COUNT(*) AS TOT FROM %s %s', $table, $db_filter ), @db_bind )->hash->{tot};
198             }
199              
200 0         0 my $ssp_results = $c->datatable->ssp_results(
201             draw => $ssp->draw,
202             data => \@results,
203             records_total => $total,
204             records_filtered => $filtered
205             );
206              
207 0         0 return $ssp_results;
208              
209             }
210              
211             sub _dt_ssp_params {
212              
213 0     0   0 my ( $c, $dt_options ) = @_;
214              
215 0         0 my $req_params = {};
216 0 0       0 $req_params = $c->req->query_params if ( $c->req->{method} eq 'GET' );
217 0 0       0 $req_params = $c->req->body_params if ( $c->req->{method} eq 'POST' );
218              
219 0         0 return Mojolicious::Plugin::DataTables::SSP::Params->new( %{ _decode_params( $req_params, $dt_options ) } );
  0         0  
220              
221             }
222              
223             sub _dt_ssp_results {
224 0     0   0 my ( $c, %args ) = @_;
225 0         0 return Mojolicious::Plugin::DataTables::SSP::Results->new(%args);
226             }
227              
228 2     2   11 sub _tag { Mojo::ByteStream->new( Mojo::DOM::HTML::tag_to_html(@_) ) }
229              
230             sub _decode_params {
231              
232 0     0     my ( $req_params, $dt_options ) = @_;
233              
234 0           my $dt_params = {};
235              
236 0   0       $dt_params->{draw} = $req_params->param('draw') || false;
237 0   0       $dt_params->{length} = $req_params->param('length') || 0;
238 0   0       $dt_params->{start} = $req_params->param('start') || 0;
239 0   0       $dt_params->{timestamp} = $req_params->param('_') || 0;
240 0           $dt_params->{columns} = [];
241 0           $dt_params->{order} = [];
242 0           $dt_params->{where} = undef;
243              
244 0           foreach ( @{ $req_params->names } ) {
  0            
245              
246 0           my $value = $req_params->param($_);
247 0 0         $value = true if ( $value eq 'true' );
248 0 0         $value = false if ( $value eq 'false' );
249              
250 0 0         $dt_params->{columns}[$1]->{$2} = $value if ( $_ =~ /columns\[(\d+)\]\[(data|name|searchable|orderable)\]/ );
251 0 0         $dt_params->{columns}[$1]->{search}->{$2} = $value if ( $_ =~ /columns\[(\d+)\]\[search\]\[(regex|value|)\]/ );
252 0 0         $dt_params->{order}[$1]->{$2} = $value if ( $_ =~ /order\[(\d+)\]\[(column|dir)\]/ );
253 0 0         $dt_params->{search}->{$1} = $value if ( $_ =~ /search\[(value|regex)\]/ );
254              
255             }
256              
257 0           for ( my $i = 0; $i < @{$dt_options}; $i++ ) {
  0            
258              
259 0           my $dt_option = $dt_options->[$i];
260              
261 0   0       $dt_params->{columns}[ $dt_option->{dt} ]->{database} = $dt_option->{db} || undef;
262 0   0       $dt_params->{columns}[ $dt_option->{dt} ]->{formatter} = $dt_option->{formatter} || undef;
263 0   0       $dt_params->{columns}[ $dt_option->{dt} ]->{label} = $dt_option->{label} || undef;
264              
265 0 0         if ( defined $dt_option->{searchable} ) {
266 0           $dt_params->{columns}[ $dt_option->{dt} ]->{searchable} = $dt_option->{searchable};
267             }
268 0 0         if ( defined $dt_option->{orderable} ) {
269 0           $dt_params->{columns}[ $dt_option->{dt} ]->{orderable} = $dt_option->{orderable};
270             }
271             }
272              
273 0           for ( my $i = 0; $i < @{ $dt_params->{columns} }; $i++ ) {
  0            
274 0           my $column = $dt_params->{columns}[$i];
275 0           $dt_params->{columns}[$i] = Mojolicious::Plugin::DataTables::SSP::Column->new( %{$column} );
  0            
276             }
277              
278 0           for ( my $i = 0; $i < @{ $dt_params->{order} }; $i++ ) {
  0            
279 0           $dt_params->{order}[$i]->{column} = $dt_params->{columns}[ $dt_params->{order}[$i]->{column} ];
280             }
281              
282             # $dt_params->{columns} = Mojo::Collection->new ( $dt_params->{columns} );
283             # $dt_params->{order} = Mojo::Collection->new ( $dt_params->{order} );
284              
285 0           return $dt_params;
286              
287             }
288              
289             1;
290              
291             =encoding utf8
292              
293             =head1 NAME
294              
295             Mojolicious::Plugin::DataTables - DataTables Plugin for Mojolicious
296              
297             =head1 SYNOPSIS
298              
299             # Mojolicious
300             $self->plugin('DataTables');
301              
302             # Mojolicious::Lite
303             plugin 'DataTables';
304              
305             [...]
306              
307             my $dt_ssp = $c->datatable->ssp(
308             table => 'users',
309             db => $db,
310             columns => qw/role create_date/,
311             debug => 1,
312             where => 'status = "active"'
313             options => [
314             {
315             label => 'UID',
316             db => 'uid',
317             dt => 0,
318             formatter => sub {
319             my ($value, $column) = @_;
320             return '' . $value . '';
321             }
322             },
323             {
324             label => 'e-Mail',
325             db => 'mail',
326             dt => 1,
327             },
328             {
329             label => 'Status',
330             db => 'status',
331             dt => 2,
332             },
333             ]
334             );
335              
336             return $c->render(json => $dt_ssp);
337              
338             =head1 DESCRIPTION
339              
340             L is a L plugin to add DataTables SSP (Server-Side Protocol) support in your Mojolicious application.
341              
342              
343             =head1 METHODS
344              
345             L implements the following methods.
346              
347             =head2 datatable_js
348              
349             Generate C
417              
418             Controller:
419              
420             $c->datatable->ssp(
421             table => 'users',
422             db => $db,
423             options => [
424             {
425             label => 'UID',
426             db => 'uid',
427             dt => 0,
428             },
429             {
430             label => 'e-Mail',
431             db => 'mail',
432             dt => 1,
433             },
434             {
435             label => 'Status',
436             db => 'status',
437             dt => 2,
438             },
439             ]
440             );
441              
442             =head2 Formatter
443              
444             The anonymous C sub accept this arguments:
445              
446             =over 4
447              
448             =item C<$value>: the column value
449              
450             =item C<$column>: A L instance
451              
452              
453             options => [
454             {
455             label => 'Name',
456             db => 'username',
457             dt => 0,
458             formatter => sub {
459             my ($value, $column) = @_;
460             my $row = $column->row;
461             return '' .$value . '';
462             }
463             },
464             {
465             ...
466             }
467             ]
468              
469             =head2 Search flag
470              
471             The C flag enable or disable a filter for specified column.
472              
473             options => [
474             {
475             label => 'Name',
476             db => 'username',
477             dt => 0,
478             searchable => 0,
479             },
480             {
481             ...
482             }
483             ]
484              
485             =head1 SEE ALSO
486              
487             L, L, L, L, L, L, L.
488              
489             =cut