File Coverage

blib/lib/jQuery/DataTables.pm
Criterion Covered Total %
statement 20 99 20.2
branch 0 26 0.0
condition 0 18 0.0
subroutine 6 9 66.6
pod 2 3 66.6
total 28 155 18.0


line stmt bran cond sub pod time code
1             package jQuery::DataTables;
2 1     1   23897 use strict;
  1         2  
  1         32  
3 1     1   5 use warnings;
  1         1  
  1         25  
4 1     1   1007 use utf8;
  1         13  
  1         4  
5             #use Data::Dump qw(ddx dd pp);
6              
7             BEGIN {
8 1     1   34 use Exporter ();
  1         2  
  1         28  
9 1     1   5 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  1         1  
  1         128  
10 1     1   2 $VERSION = '0.906';
11 1         12 @ISA = qw(Exporter);
12             #Give a hoot don't pollute, do not export more than needed by default
13 1         2 @EXPORT = qw();
14 1         1 @EXPORT_OK = qw();
15 1         1141 %EXPORT_TAGS = ();
16             }
17              
18              
19             =head1 DESCRIPTION
20              
21             jQuery::DataTables - серверная часть для выполнения AJAX запросов DataTables
22              
23             =cut
24              
25             =head1 SYNOPSYS
26              
27             use strict;
28             use jQuery::DataTables;
29              
30             ...
31             my $dt = new jQuery::DataTables( cgi => $cgi, dbh => $c->app->dbh );
32             my $res = $dt->getTableData( 'SELECT DISTINCT id, col_int, col_text, col_real FROM datatable', [qw{id col_int col_text col_real}] );
33             $c->render( json => $res );
34              
35             =cut
36              
37             sub new {
38 0     0 0   my $class = shift;
39 0           my $self = {};
40 0           $self = {@_};
41              
42 0 0 0       die("Undefined (dbh) parameter") unless $self->{dbh} && UNIVERSAL::isa( $self->{dbh}, 'DBI::db' );
43 0 0         die("Undefined (cgi) parameter") unless $self->{cgi};
44              
45 0           bless $self, $class;
46 0           return $self;
47              
48             } ## end sub new
49              
50             =head2 prepareDataTableRequest()
51              
52             Подготавливает данные из DataTable запроса для удобного использования
53             и возвращает хэш с данными.
54             Делается это для упрощения использования странного формата, заложенного в DataTables изначально.
55              
56             =head3 DataTables param() - описания из документации
57              
58             Type Name Info
59             --------------------
60             int iDisplayStart Display start point in the current data set.
61             $iDisplayStart
62              
63             int iDisplayLength Number of records that the table can display in the current draw.
64             $iDisplayLength It is expected that the number of records returned will be equal to this number,
65             unless the server has fewer records to return.
66              
67             int iColumns Number of columns being displayed (useful for getting individual column search info)
68             $iColumns
69              
70             string sSearch Global search field
71             $sSearch
72              
73             bool bRegex True if the global filter should be treated as a regular expression for advanced filtering, false if not.
74             $bRegex
75              
76             bool bSearchable_(int) Indicator for if a column is flagged as searchable or not on the client-side
77             @abSearchable
78              
79             string sSearch_(int) Individual column filter
80             @asSearch
81              
82             bool bRegex_(int) True if the individual column filter should be treated as a regular expression
83             @abRegex for advanced filtering, false if not
84              
85             bool bSortable_(int) Indicator for if a column is flagged as sortable or not on the client-side
86             @abSortable
87              
88             int iSortingCols Number of columns to sort on
89             $iSortingCols
90              
91             int iSortCol_(int) Column being sorted on (you will need to decode this number for your database)
92             @aiSortCol
93              
94             string sSortDir_(int) Direction to be sorted - "desc" or "asc".
95             @asSortDir
96              
97             string mDataProp_(int) The value specified by mDataProp for each column. This can be useful
98             @amDataProp for ensuring that the processing of data is independent from the order of the columns.
99              
100             string sEcho Information for DataTables to use for rendering.
101             $sEcho
102              
103             =cut
104              
105             sub prepareDataTableRequest {
106 0     0 1   my $self = shift;
107 0           my $c = $self->{cgi}; # CGI-compatible by param()
108              
109             #ddx $c->req->params->to_hash;
110              
111             # соберём всё в более удобные объекты
112              
113             # int iDisplayStart Display start point in the current data set.
114 0           my $iDisplayStart = $c->param('iDisplayStart');
115              
116             # int iDisplayLength Number of records that the table can display in the current draw.
117             # It is expected that the number of records returned will be equal
118             # to this number, unless the server has fewer records to return.
119 0           my $iDisplayLength = $c->param('iDisplayLength');
120              
121             # int iColumns Number of columns being displayed
122             # (useful for getting individual column search info)
123             # Количество отображаемых столбцов
124             # (удобно для получения информации о поисковых запросах отдельных столбцов)
125 0           my $iColumns = $c->param('iColumns');
126              
127             # bool bRegex True if the global filter should be treated as a regular expression for advanced filtering, false if not.
128             # true если глобальный фильтр должен использоваться как регулярное выражение, false если нет.
129 0           my $bRegex = $c->param('bRegex');
130              
131             # bool bRegex_(int) True if the individual column filter should be treated
132             # as a regular expression for advanced filtering, false if not
133             # true если индивидуальный фильтр столбца должен использоваться
134             # как регулярное выражение, false если нет.
135 0           my @abRegex;
136              
137             # string sSearch Global search field
138             # Глобальный поисковый запрос
139 0           my $sSearch = $c->param('sSearch');
140              
141             # string sSearch_(int) Individual column filter
142             # Индивидуальные поисковые фильтры для столбцов
143 0           my @asSearch;
144              
145             # int iSortingCols Number of columns to sort on
146             # Количество столбцов для сортировки
147 0           my $iSortingCols = $c->param('iSortingCols');
148              
149             # string sEcho Information for DataTables to use for rendering.
150             # Этот параметр возвращается в ответе в неизменном виде.
151             # Сильно рекомендуется делать его целым числом, уникальным для запросов.
152 0           my $sEcho = $c->param('sEcho');
153              
154             # bool bSearchable_(int) Indicator for if a column is flagged as searchable or not on the client-side
155             # Индикатор того, что столбец используется или не
156             # используется в поисковом выражении на клиентской стороне
157 0           my @abSearchable;
158              
159             # bool bSortable_(int) Indicator for if a column is flagged as sortable or not on the client-side
160             # Индикатор того, что столбец используется или не используется
161             # для сортировки на клиентской стороне
162             my @abSortable;
163             # int iSortCol_(int) Column being sorted on (you will need to decode this number for your database)
164             # Столбец, по которому сортируем результат
165             #(надо превращать этот номер в название столбца в базе данных)
166 0           my @aiSortCol;
167              
168             # string sSortDir_(int) Direction to be sorted - "desc" or "asc".
169             # Направление сортировки - DESC или ASC
170 0           my @asSortDir;
171              
172             # string mDataProp_(int) The value specified by mDataProp for each column. This can be useful
173             # for ensuring that the processing of data is independent from the order of the columns.
174 0           my @amDataProp;
175              
176 0           foreach my $i ( 0 .. $iColumns - 1 ) {
177 0           push @abSearchable, $c->param("bSearchable_$i");
178 0           push @asSearch, $c->param("sSearch_$i");
179 0           push @abRegex, $c->param("bRegex_$i");
180 0           push @abSortable, $c->param("bSortable_$i");
181 0           push @aiSortCol, $c->param("iSortCol_$i");
182 0           push @asSortDir, $c->param("sSortDir_$i");
183 0           push @amDataProp, $c->param("mDataProp_$i");
184             } ## end foreach my $i ( 0 .. $iColumns...)
185              
186 0           my $req = {
187             # параметры общего назначения
188             iColumns => $iColumns, # количество отображаемых столбцов. Отображаемых в интерфейсе, не в запросе
189             sEcho => $sEcho, # уникальный тэг запроса
190             amDataProp => \@amDataProp,
191              
192             # LIMIT ... OFFSET
193             iDisplayStart => $iDisplayStart, # с какой стноки начинать выдачу
194             iDisplayLength => $iDisplayLength, # сколько строк выдаавть
195              
196             # WHERE общие для всех столбцов
197             bRegex => $bRegex, # true == общий поиск - regex поиск
198             sSearch => $sSearch, # общий поиск
199              
200             abSearchable => \@abSearchable, #Индикатор того, что столбец используется
201             #для сортировки на клиентской стороне.
202             # Что это значит я не знаю...
203              
204             # WHERE индивидуальные для столбцов
205             abRegex => \@abRegex, #true если индивидуальный фильтр столбца
206             #должен использоваться как регулярное выражение
207              
208             asSearch => \@asSearch, #Индивидуальные поисковые фильтры для столбцов
209              
210             # ORDER BY
211             iSortingCols => $iSortingCols, #Количество столбцов для сортировки
212             abSortable => \@abSortable, #Индикатор того, что столбец используется для сортировки на клиентской стороне
213             aiSortCol => \@aiSortCol, #Столбец, по которому сортируем результат
214             #(надо превращать этот номер в название столбца в базе данных)
215             asSortDir => \@asSortDir, #Направление сортировки - DESC или ASC
216              
217             };
218             #ddx $req;
219 0           $self->{req} = $req;
220 0           return $req;
221             } ## end sub prepareDataTableRequest
222              
223             =head2 getTableData ()
224              
225             Мы можем вызывать эту функцию с указанием запроса и какие столбцы он возвращает, например
226              
227             getTableData ('SELECT a,b,c FROM table', ['a', 'b', 'c'])
228              
229             Названия столбцов в фильтрах будут использоваться с указанием их в кавычках,
230             так что надо указывать имена столбцов так, чтобы база данных их правильно поняла. Многие СУБД позволяют имена столюцов
231             возвращать большими или маленькими буквами, по флагу FetchHashKeyName => 'NAME_lc' или FetchHashKeyName => 'NAME_uc'.
232              
233             К этому запросу добавляется поисковое выражение WHERE, сортировка ORDER BY и лимиты LIMIT ... OFFSET
234              
235             Поисковое выражение может быть одно на все столбцы, либо разные выражения на разные (некоторые!) столбцы.
236              
237             Поисковое выражение должно трактоваться как regexp либо для всех столбцов, либо regexp для некоторых столбцов.
238              
239             Сначала выполняется общее поисковое выражение (если указано), затем для полученного результата выполняются
240             индивидуальные для столбцов поисковые выражения. Ясно, что лучше использовать только индивидуальные выражения.
241              
242             Данная реализация не учитывает флаг bRegex - так как пока непонятно, как обеспечить исполнение этого флага для разных СУБД.
243              
244             =cut
245              
246             sub getTableData {
247 0     0 1   my $self = shift;
248 0           my $query = shift; # запрос, возвращающий данные до фильтрации и сортировки
249 0           my $columns = shift; # имена столбцов, возвращаемые запросом.
250             # Если не указано, то будет определено автоматически,
251             # выполняя запрос "$query LIMIT 0"
252              
253 0           my $c = $self->{cgi}; # CGI-compatible by param()
254 0           my $dbh = $self->{dbh};
255 0           my $r = $self->prepareDataTableRequest();
256              
257             # LIMIT ... OFFSET
258 0           my @limits;
259 0 0         push @limits, "LIMIT $r->{iDisplayLength}" if ( $r->{iDisplayLength} );
260 0 0 0       push @limits, "OFFSET $r->{iDisplayStart}" if ( $r->{iDisplayStart} && $r->{iDisplayStart} > 0 );
261 0           my $limit = join ' ', @limits;
262             # WHERE общие для всех столбцов
263              
264             # Ordering
265              
266 0           my @where;
267              
268 0           my $abSearchable = $r->{abSearchable};
269 0           my $sSearch = $r->{sSearch};
270 0 0 0       if ( defined $sSearch and $sSearch ne '' ) {
271              
272 0           for ( my $i = 0; $i < scalar(@$abSearchable); $i++ ) {
273 0 0 0       if ( $abSearchable->[$i] and $abSearchable->[$i] eq 'true' ) {
274              
275 0 0         if ( $r->{bRegex} eq 'true' ) {
276 0           push @where, qq{"$columns->[$i]" like '\%$sSearch\%'};
277              
278             } ## end if ( $r->{bRegex} eq 'true')
279             else {
280 0           push @where, qq{"$columns->[$i]" like '\%$sSearch\%'};
281             } ## end else [ if ( $r->{bRegex} eq 'true')]
282             } ## end if ( $abSearchable->[$i...])
283             } ## end for ( my $i = 0; $i < scalar...)
284             } ## end if ( defined $sSearch ...)
285              
286             # WHERE индивидуальные для столбцов
287              
288 0           for ( my $i = 0; $i < scalar(@$abSearchable); $i++ ) {
289 0           my $asSearch = $r->{asSearch};
290 0           my $abRegex = $r->{abRegex};
291              
292 0 0 0       if ( $abSearchable->[$i] and $abSearchable->[$i] eq 'true' and $asSearch->[$i] ne '' ) {
      0        
293 0 0         if ( $abRegex->[$i] eq 'true' ) {
294 0           push @where, qq{"$columns->[$i]" like '\%$asSearch->[$i]\%'};
295              
296             } ## end if ( $abRegex->[$i] eq...)
297             else {
298 0           push @where, qq{"$columns->[$i]" like '\%$asSearch->[$i]\%'};
299             } ## end else [ if ( $abRegex->[$i] eq...)]
300             } ## end if ( $abSearchable->[$i...])
301             } ## end for ( my $i = 0; $i < scalar...)
302              
303 0           my $where = join ' and ', @where;
304 0 0         $where = ' WHERE ' . $where if $where;
305              
306             # ORDER BY
307              
308 0           my @order;
309              
310 0           my $aiSortCol = $r->{aiSortCol};
311 0           my $asSortDir = $r->{asSortDir};
312 0 0         if (@$aiSortCol) {
313              
314 0           for ( my $i = 0; $i < scalar(@$aiSortCol); $i++ ) {
315 0           my $column = $columns->[ $aiSortCol->[$i] ];
316 0           my $direction = $asSortDir->[$i];
317 0 0         $direction = ( $direction eq 'asc' ) ? 'asc' : 'desc';
318              
319 0           push @order, qq{"$column" $direction};
320             } ## end for ( my $i = 0; $i < scalar...)
321             } ## end if (@$aiSortCol)
322              
323 0           my $order = join ' , ', @order;
324 0 0         $order = 'ORDER BY ' . $order if $order;
325              
326             # iTotalRecords - Total records, after filtering (not just the records on this page, all of them)
327             # iTotalDisplayRecords - Total records, before filtering
328              
329 0           my $query_iTotalRecords = <
330             select count(*) from(
331             $query
332             )
333             END
334              
335             #ddx $query_iTotalRecords;
336 0           my ($iTotalRecords) = $dbh->selectrow_array($query_iTotalRecords);
337              
338 0           my $query_iTotalDisplayRecords = <
339             select count(*) from(
340             $query
341             $where
342             )
343             END
344              
345             #ddx $query_iTotalDisplayRecords;
346 0           my ($iTotalDisplayRecords) = $dbh->selectrow_array($query_iTotalDisplayRecords);
347              
348 0           my $query_result = <
349             $query
350             $where
351             $order
352             $limit
353             END
354              
355             #ddx $query_result;
356 0           my $aaData = $dbh->selectall_arrayref( $query_result, {}, );
357             return {
358 0           "sEcho" => $r->{'sEcho'},
359             "iTotalRecords" => $iTotalRecords,
360             "iTotalDisplayRecords" => $iTotalDisplayRecords,
361             "aaData" => $aaData,
362             }
363              
364             } ## end sub getTableData
365              
366             =head1 AUTHORS
367              
368             Konstantin Tokar
369              
370             =cut
371              
372             1;