File Coverage

blib/lib/Mojolicious/Plugin/DataTables.pm
Criterion Covered Total %
statement 39 169 23.0
branch 0 56 0.0
condition 2 32 6.2
subroutine 12 16 75.0
pod 1 1 100.0
total 54 274 19.7


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