Overview

Namespaces

  • Rundiz
    • DataTable

Classes

  • Rundiz\DataTable\Database
  • Rundiz\DataTable\DataTable
  • Overview
  • Namespace
  • Class
  1: <?php
  2: /**
  3:  * @package Data Table
  4:  * @version 0.1
  5:  * @author Vee W.
  6:  * @license http://opensource.org/licenses/MIT MIT
  7:  */
  8: 
  9: 
 10: namespace Rundiz\DataTable;
 11: 
 12: /**
 13:  * Data table class for generate table with header, footer, data list, bulk actions, pagination.<br>
 14:  * This class is copied from the WordPress list table class.
 15:  *
 16:  * @author Vee W.
 17:  */
 18: class DataTable
 19: {
 20: 
 21: 
 22:     /**
 23:      * @var integer Columns count.
 24:      */
 25:     protected $columnsCount = 0;
 26: 
 27: 
 28:     /**
 29:      * @var \Rundiz\DataTable\Database The database class connector.
 30:      */
 31:     protected $Database;
 32: 
 33: 
 34:     /**
 35:      * @var array Store data items fetched from database.
 36:      */
 37:     protected $dataItems = [];
 38: 
 39: 
 40:     /**
 41:      * @var integer Number of items to display per page.
 42:      */
 43:     protected $itemsPerPage = 20;
 44: 
 45: 
 46:     /**
 47:      * @var string The order query string name. Its value is column name in the db table. Override this if you have many data table in a page.
 48:      */
 49:     protected $orderQueryName = 'order';
 50: 
 51: 
 52:     /**
 53:      * @var \Rundiz\Pagination\Pagination The pagination class connector.
 54:      */
 55:     protected $Pagination;
 56: 
 57: 
 58:     /**
 59:      * @var string Pagination query string name. Example: pagination query string like this (?paged=3) the pagination query string name is "paged". You can change this value in case that you have many data table in a page.
 60:      */
 61:     protected $paginationQueryName = 'paged';
 62: 
 63: 
 64:     /**
 65:      * @var boolean Show footer column or not. (By default it is set to false means not show.)
 66:      */
 67:     protected $showFooterColumn = true;
 68: 
 69: 
 70:     /**
 71:      * @var string The sort query string name. Its value basically is ASC, or DESC. Override this if you have many data table in a page.
 72:      */
 73:     protected $sortQueryName = 'sort';
 74: 
 75: 
 76:     /**
 77:      * @var mixed Temporary default properties for restore their values when call reset() method.
 78:      */
 79:     protected $tempProperties;
 80: 
 81: 
 82:     /**
 83:      * @var integer Total items count from database.
 84:      */
 85:     protected $totalItems = 0;
 86: 
 87: 
 88:     /**
 89:      * DataTable class constructor.
 90:      * 
 91:      * @param array $config Available config key: [pdoconfig [dsn], [username], [password], [options]] (see more at http://php.net/manual/en/pdo.construct.php)
 92:      */
 93:     public function __construct(array $config = [])
 94:     {
 95:         $class_properties = get_class_vars(__CLASS__);
 96:         $this->tempProperties = $class_properties;
 97:         unset($class_properties);
 98: 
 99:         $this->Database = new Database((isset($config['pdoconfig']) ? $config['pdoconfig'] : []));
100:         $this->Pagination = new \Rundiz\Pagination\Pagination();
101:         $this->Pagination->page_number_type = 'page_num';
102:     }// __construct
103: 
104: 
105:     /**
106:      * DataTable class de-constructor.<br>
107:      * This method will be called reset() method to clear everything to its default values.
108:      */
109:     public function __destruct()
110:     {
111:         $this->reset();
112:     }// __destruct
113: 
114: 
115:     /**
116:      * Column checkbox.
117:      * 
118:      * @param object $row Data from PDO in each row from fetchAll() method.
119:      * @return string Return checkbox element with value. Use return, not echo.
120:      */
121:     protected function columnCheckbox($row)
122:     {
123:         
124:     }// columnCheckbox
125: 
126: 
127:     /**
128:      * Column default.<br>
129:      * Return content for columns by default (no special, the special columns need to create their own column with unique name).<br>
130:      * For special column method. Example: You have unique name as "time". The special column method name will be columnTime($row).
131:      * 
132:      * @param object $row Data from PDO in each row from fetchAll() method.
133:      * @param string $column_unique_name The column unique name.
134:      * @return string Return column content. Use return, not echo.
135:      */
136:     protected function columnDefault($row, $column_unique_name)
137:     {
138:         
139:     }// columnDefault
140: 
141: 
142:     /**
143:      * Display the data table.
144:      */
145:     public function display()
146:     {
147:         // working about pagination before echo out.
148:         $this->preparePagination();
149: 
150:         echo "\n";
151:         $this->printBeforeTableControlsTop();
152:         $this->printTableControls('top');
153:         $this->printElementBeforeTable();
154:         echo '<table class="rundiz-data-table ' . implode(' ', $this->getTableClasses()) . '"' . $this->renderAttributes($this->getTableAttributes()) . '>'."\n";
155: 
156:         echo '    <thead' . $this->renderAttributes($this->getTheadAttributes()) . '>'."\n";
157:         echo '        <tr' . $this->renderAttributes($this->getTheadTrAttributes()) . '>'."\n";
158:         echo '            ';
159:         $this->printColumnHeaders();
160:         echo '        </tr>'."\n";
161:         echo '    </thead>'."\n";
162: 
163:         echo '    <tbody' . $this->renderAttributes($this->getTbodyAttributes()) . '>'."\n";
164:         $this->printRowsOrPlaceholder();
165:         echo '    </tbody>'."\n";
166: 
167:         if ($this->showFooterColumn === true) {
168:             echo '    <tfoot' . $this->renderAttributes($this->getTfootAttributes()) . '>'."\n";
169:             echo '        <tr' . $this->renderAttributes($this->getTfootTrAttributes()) . '>'."\n";
170:             echo '            ';
171:             $this->printColumnHeaders(true);
172:             echo '        </tr>'."\n";
173:             echo '    </tfoot>'."\n";
174:         }
175: 
176:         echo '</table>'."\n";
177:         $this->printElementAfterTable();
178:         $this->printTableControls('bottom');
179:     }// display
180: 
181: 
182:     /**
183:      * Get bulk actions.
184:      * 
185:      * @return array Return array of bulk action value and text to display in the select box. Example: ['delete' => 'Delete'].
186:      */
187:     protected function getBulkActions()
188:     {
189:         return [];
190:     }// getBulkActions
191: 
192: 
193:     /**
194:      * Get column classes.<br>
195:      * This class will be add in to <code>th</code> or <code>td</code> in the <code>tbody</code>.
196:      * 
197:      * @param object $row Data from PDO in each row from fetchAll() method.
198:      * @param string $column_unique_name Column unique name for check.
199:      * @return array Return classes in array value. [class_name1, class_name2].
200:      */
201:     protected function getColumnClasses($row, $column_unique_name = '')
202:     {
203:         return [];
204:     }// getColumnClasses
205: 
206: 
207:     /**
208:      * Get column footer classes.<br>
209:      * This class will be add in to <code>th</code> or <code>td</code> in the <code>tfoot</code>.
210:      * 
211:      * @param string $column_unique_name Column unique name for check.
212:      * @return array Return classes in array value. [class_name1, class_name2].
213:      */
214:     protected function getColumnFooterClasses($column_unique_name = '')
215:     {
216:         return [];
217:     }// getColumnFooterClasses
218: 
219: 
220:     /**
221:      * Get column header classes.<br>
222:      * This class will be add in to <code>th</code> or <code>td</code> in the <code>thead</code>.
223:      * 
224:      * @param string $column_unique_name Column unique name for check.
225:      * @return array Return classes in array value. [class_name1, class_name2].
226:      */
227:     protected function getColumnHeaderClasses($column_unique_name = '')
228:     {
229:         return [];
230:     }// getColumnHeaderClasses
231: 
232: 
233:     /**
234:      * Get all the table columns to display.
235:      * 
236:      * @return array Return array key that is unique name and text display in array value. [key (unique name, no need to refer to table field) => text display].
237:      */
238:     protected function getColumns()
239:     {
240:         die('The required getColumns() method is missing from sub class.');
241:     }// getColumns
242: 
243: 
244:     /**
245:      * Get current pagination page number.
246:      * 
247:      * @return integer Return pagination page number.
248:      */
249:     protected function getCurrentPage()
250:     {
251:         if (isset($_GET[$this->paginationQueryName])) {
252:             $output = abs(intval($_GET[$this->paginationQueryName]));
253:         } else {
254:             $output = 1;
255:         }
256: 
257:         return max(1, $output);
258:     }// getCurrentPage
259: 
260: 
261:     /**
262:      * Get primary column from unique name.<br>
263:      * If there is "checkbox" unique name in the getColumns() method, it will return the second unique name.<br>
264:      * If there is no "checkbox" unique name in the getColumns() method, it will return the first unique name.<br>
265:      * You can override this method by set the primary column from unique name from getColumns() method.
266:      * 
267:      * @return string Return unique name that was set in the getColumns() method.
268:      */
269:     protected function getPrimaryColumn()
270:     {
271:         $columns = $this->getColumns();
272: 
273:         if (is_array($columns) && array_key_exists('checkbox', $columns)) {
274:             // there is "checkbox" unique name in the getColumns() method.
275:             while (key($columns) !== 'checkbox') {
276:                 next($columns);
277:             }
278: 
279:             // get next unique name next to the checkbox column.
280:             $next_checkbox_column_display = next($columns);
281:             $next_checkbox_column_uniquename = key($columns);
282: 
283:             if ($next_checkbox_column_display !== false) {
284:                 // if there is unique name next to the checkbox column. return it as primary column.
285:                 unset($columns, $next_checkbox_column_display);
286:                 return $next_checkbox_column_uniquename;
287:             }
288:         }
289: 
290:         // clear unused variables.
291:         unset($next_checkbox_column_display, $next_checkbox_column_uniquename);
292: 
293:         // come to this means there is no "checkbox" unique name in the getColumns() method or no column next to checkbox column.
294:         // return the first column as primary column.
295:         if (is_array($columns)) {
296:             reset($columns);
297:             return key($columns);
298:         } else {
299:             throw new \Exception('Wrong value type for getColumns() method. The value type for getColumns() method should be array.');
300:         }
301:     }// getPrimaryColumn
302: 
303: 
304:     /**
305:      * Get row actions.<br>
306:      * Override this method to set actions for each data row.
307:      * 
308:      * @param object $row Data from PDO in each row from fetchAll() method.
309:      * @param string $column_unique_name Column unique name for check.
310:      * @param string $primary_column_name Column name that is primary.
311:      * @return array Return row actions as array. Example: [action key => html link, action key2 => html link2]
312:      */
313:     protected function getRowActions($row, $column_unique_name, $primary_column_name)
314:     {
315:         return [];
316:     }// getRowActions
317: 
318: 
319:     /**
320:      * Get sortable columns.<br>
321:      * Override this method to set sortable columns.
322:      * 
323:      * @return array Return array key that is unique name from getColumns() method and array of order by and pre-sorted as ascending (false) or descending (true) in the array value. Example: [key (unique name, no need to refer to table field) => [order_by (refer to table field), false]].
324:      */
325:     protected function getSortableColumns()
326:     {
327:         return [];
328:     }// getSortableColumns
329: 
330: 
331:     /**
332:      * Get <code>table</code> attributes (not including class).
333:      * 
334:      * @return array Return array of attributes in key => value pairs. example: ['data-sortable' => 'true', 'data-sortcolumn' => 'name'] will becomes (<code>data-sortable="true" data-sortcolumn="name"</code>).
335:      */
336:     protected function getTableAttributes()
337:     {
338:         return [];
339:     }// getTableAttributes
340: 
341: 
342:     /**
343:      * Get <code>table</code> classes.
344:      * 
345:      * @return array Return array of table classes. [class1, class2, ...]
346:      */
347:     protected function getTableClasses()
348:     {
349:         return ['data-table', 'list-table'];
350:     }// getTableClasses
351: 
352: 
353:     /**
354:      * Get table body (<code>tbody</code>) attributes (including class).
355:      * 
356:      * @return array Return array of <code>tbody</code> attributes. example: ['data-name' => 'true', 'class' => 'my-tbody'] will becomes (<code>data-name="true" class="my-tbody"</code>).
357:      */
358:     protected function getTbodyAttributes()
359:     {
360:         return [];
361:     }// getTbodyAttributes
362: 
363: 
364:     /**
365:      * Get <code>tbody</code> > <code>tr</code> > <code>td</code> attributes where it has no data (including class but do not including colspan).
366:      * 
367:      * @return array Return array of <code>tbody</code> > <code>tr</code> > <code>td</code> attributes. example: ['data-name' => 'true', 'class' => 'my-tbody'] will becomes (<code>data-name="true" class="my-tbody"</code>).
368:      */
369:     protected function getTbodyTrTdNodataAttributes()
370:     {
371:         return ['class' => 'no-item no-data'];
372:     }// getTbodyTrTdNodataAttributes
373: 
374: 
375:     /**
376:      * Get text message.<br>
377:      * You can override this method to make it translatable.
378:      * 
379:      * @param string $text String key of text message.
380:      * @return string Return translated from string key to readable message.
381:      */
382:     protected function getText($text = '')
383:     {
384:         switch ($text) {
385:             case 'apply':
386:                 return 'Apply';
387:             case 'bulk_actions':
388:                 return 'Bulk actions';
389:             case 'current_page':
390:                 return 'Current page';
391:             case 'delete':
392:                 return 'Delete';
393:             case 'no_items_found':
394:                 return 'No items found.';
395:             case 'page_of_x':
396:                 return ' of %d';
397:             case 'page_x_of_x':
398:                 return '%d of %d';
399:             case 'select_all':
400:                 return 'Select all';
401:             case 'select_bulk_action':
402:                 return 'Select bulk action';
403:             case 'show_more_details':
404:                 return 'Show more details';
405:             case 'x_items':
406:                 return '%d items';
407:             default:
408:                 return $text;
409:         }
410:     }// getText
411: 
412: 
413:     /**
414:      * Get table foot (<code>tfoot</code>) attributes (including class).
415:      * 
416:      * @return array Return array of <code>tfoot</code> attributes. example: ['data-name' => 'true', 'class' => 'my-tfoot'] will becomes (<code>data-name="true" class="my-tfoot"</code>).
417:      */
418:     protected function getTfootAttributes()
419:     {
420:         return [];
421:     }// getTfootAttributes
422: 
423: 
424:     /**
425:      * Get table row of <code>tfoot<code> attributes (including class).
426:      * 
427:      * @return array Return array of <code>tfoot</code> > <code>tr</code> attributes. example: ['data-name' => 'true', 'class' => 'my-tfoot'] will becomes (<code>data-name="true" class="my-tfoot"</code>).
428:      */
429:     protected function getTfootTrAttributes()
430:     {
431:         return [];
432:     }// getTfootTrAttributes
433: 
434: 
435:     /**
436:      * Get table head (<code>thead</code>) attributes (including class).
437:      * 
438:      * @return array Return array of <code>thead</code> attributes. example: ['data-name' => 'true', 'class' => 'my-thead'] will becomes (<code>data-name="true" class="my-thead"</code>).
439:      */
440:     protected function getTheadAttributes()
441:     {
442:         return [];
443:     }// getTheadAttributes
444: 
445: 
446:     /**
447:      * Get table row of <code>thead</code> attributes (including class).
448:      * 
449:      * @return array Return array of <code>thead</code> > <code>tr</code> attributes. example: ['data-name' => 'true', 'class' => 'my-thead'] will becomes (<code>data-name="true" class="my-thead"</code>).
450:      */
451:     protected function getTheadTrAttributes()
452:     {
453:         return [];
454:     }// getTheadTrAttributes
455: 
456: 
457:     /**
458:      * Prepare items for listing.
459:      */
460:     public function prepareData()
461:     {
462:         die('The required prepareData() method is missing from sub class.');
463:     }// prepareData
464: 
465: 
466:     /**
467:      * Prepare pagination. Any customizable of pagination can override by this method.<br>
468:      * This data table pagination use Rundiz/Pagination class ( https://github.com/Rundiz/pagination ).
469:      * 
470:      * @see rundiz/pagination See more document at https://github.com/Rundiz/pagination or http://rundiz.com/web-resources/pagination-v3
471:      * @throws \Exception Throw exception if pagination class is missing.
472:      */
473:     protected function preparePagination()
474:     {
475:         if (!class_exists('\\Rundiz\\Pagination\\Pagination') || !is_object($this->Pagination)) {
476:             throw new \Exception('The class \\Rundiz\\Pagination\\Pagination is not exists, please install rundiz/pagination class.');
477:         }
478: 
479:         // prepare pagination values (total items, items per page, current page).
480:         $this->Pagination->total_records = $this->totalItems;
481:         $this->Pagination->items_per_page = $this->itemsPerPage;
482:         $this->Pagination->page_number_value = $this->getCurrentPage();
483: 
484:         // customize how pagination display.
485:         $this->Pagination->first_page_always_show = true;
486:         $this->Pagination->first_page_text = '&laquo;';
487:         $this->Pagination->previous_page_always_show = true;
488:         $this->Pagination->previous_page_text = '&lsaquo;';
489:         $this->Pagination->number_display = false;
490:         $this->Pagination->next_page_always_show = true;
491:         $this->Pagination->next_page_text = '&rsaquo;';
492:         $this->Pagination->last_page_always_show = true;
493:         $this->Pagination->last_page_text = '&raquo;';
494:     }// preparePagination
495: 
496: 
497:     /**
498:      * Print out before table controls top.
499:      */
500:     protected function printBeforeTableControlsTop()
501:     {
502:         
503:     }// printBeforeTableControlsTop
504: 
505: 
506:     /**
507:      * Print out bulk actions (select box).
508:      * 
509:      * @param string $which Which position of table controls (top or bottom).
510:      */
511:     protected function printBulkActions($which)
512:     {
513:         $bulk_actions = $this->getBulkActions();
514: 
515:         if (empty($bulk_actions)) {
516:             return null;
517:         }
518: 
519:         echo '        <label class="sr-only screen-reader-only" for="bulk-action-' . $which . '">' . $this->getText('select_bulk_action') . '</label>'."\n";
520:         echo '        <select id="bulk-action-' . $which . '" name="action' . ($which == 'bottom' ? '2' : '') . '">'."\n";
521:         echo '            <option value="">' . $this->getText('bulk_actions') . '</option>'."\n";
522: 
523:         if (is_array($bulk_actions)) {
524:             foreach ($bulk_actions as $value => $title) {
525:                 echo '            <option value="' . $value . '">' . $title . '</option>'."\n";
526:             }// endforeach;
527:             unset($title, $value);
528:         }
529:         unset($bulk_actions);
530: 
531:         echo '        </select>'."\n";
532:         echo '        <button id="submit-button-action' . ($which == 'bottom' ? '2' : '') . '" class="button action" type="submit">' . $this->getText('apply') . '</button>';
533:         echo "\n";
534:     }// printBulkActions
535: 
536: 
537:     /**
538:      * Display column headers for table. (Echo out.)
539:      * 
540:      * @param boolean $is_footer Set to true if this column headers is in footer of the table, set to false if it is header of the table.
541:      */
542:     protected function printColumnHeaders($is_footer = false)
543:     {
544:         $columns = $this->getColumns();
545:         $sortable = $this->getSortableColumns();
546:         $primary = $this->getPrimaryColumn();
547: 
548:         if (!is_array($columns)) {
549:             throw new \Exception('Wrong value type for getColumns() method. The value type for getColumns() method should be array.');
550:         }
551:         if (!is_array($sortable)) {
552:             throw new \Exception('Wrong value type for getSortableColumns() method. The value type for getSortableColumns() method should be array.');
553:         }
554: 
555:         if (array_key_exists('checkbox', $columns)) {
556:             static $checkbox_count = 1;
557:             $columns['checkbox'] = '<label class="sr-only screen-reader-only" for="checkbox-select-all-' . $checkbox_count . '">' . $this->getText('select_all') . '</label>'
558:                 . '<input id="checkbox-select-all-' . $checkbox_count . '" class="checkbox" type="checkbox">';
559:             $checkbox_count++;
560:         }
561: 
562:         $this->columnsCount = 0;
563:         foreach ($columns as $column_unique_name => $column_display_name) {
564:             $class = ['column-' . $column_unique_name];
565: 
566:             if ($column_unique_name == $primary) {
567:                 $class[] = 'column-primary';
568:             }
569: 
570:             if ($is_footer === false) {
571:                 $class = array_merge($class, $this->getColumnHeaderClasses($column_unique_name));
572:             } else {
573:                 $class = array_merge($class, $this->getColumnFooterClasses($column_unique_name));
574:             }
575: 
576:             if ($column_unique_name == 'checkbox') {
577:                 $table_cell = 'td';
578:             } else {
579:                 $table_cell = 'th';
580:             }
581: 
582:             // sortable column
583:             if (is_array($sortable)) {
584:                 $current_url = (isset($_SERVER['HTTPS']) ? 'https://' : 'http://') . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost') . $_SERVER['PHP_SELF'] . '?';
585:                 if (!empty($_GET)) {
586:                     foreach ($_GET as $get_key => $get_val) {
587:                         if ($get_key != $this->paginationQueryName && $get_key != $this->orderQueryName && $get_key != $this->sortQueryName) {
588:                             $current_url .= $get_key . '=' . urlencode($get_val) . '&amp;';
589:                         }
590:                     }// endforeach;
591:                     unset($get_key, $get_val);
592:                 }
593:                 $current_order = (isset($_GET[$this->orderQueryName]) ? $_GET[$this->orderQueryName] : '');
594:                 $current_sort = (isset($_GET[$this->sortQueryName]) ? $_GET[$this->sortQueryName] : '');
595: 
596:                 if (array_key_exists($column_unique_name, $sortable)) {
597:                     list($order, $descending_first) = $sortable[$column_unique_name];
598: 
599:                     if ($current_order == $order) {
600:                         $sort = ($current_sort === 'asc' ? 'desc' : 'asc');
601:                         $class[] = 'sorted';
602:                         $class[] = $current_sort;
603:                     } else {
604:                         $sort = ($descending_first === true ? 'desc' : 'asc');
605:                         $class[] = 'sortable';
606:                         $class[] = $sort;
607:                     }
608:                     $current_url .= $this->orderQueryName . '=' . urlencode($order) . '&amp;' . $this->sortQueryName . '=' . $sort;
609: 
610:                     unset($descending_first, $order, $sort);
611:                     $column_display_name = '<a href="' . $current_url . '"><span>' . $column_display_name . '</span><span class="sorting-indicator"></span></a>';
612:                 }
613: 
614:                 unset($current_order, $current_sort, $current_url);
615:             }
616: 
617:             if ($is_footer === false) {
618:                 $id = ' id="table-column-' . $column_unique_name . '"';
619:             } else {
620:                 $id = ' id="table-column-' . $column_unique_name . '-footer"';
621:             }
622: 
623:             $class = ' class="' . implode(' ', $class) . '"';
624: 
625:             if ($table_cell == 'th') {
626:                 $scope = ' scope="col"';
627:             } else {
628:                 $scope = '';
629:             }
630: 
631:             $this->columnsCount++;
632: 
633:             echo '<' . $table_cell . $id . $class . $scope . '>' . $column_display_name . '</' . $table_cell . '>';
634:         }// endforeach;
635:         unset($class, $column_display_name, $column_unique_name, $id, $scope, $table_cell);
636: 
637:         unset($columns, $primary, $sortable);
638:         echo "\n";
639:     }// printColumnHeaders
640: 
641: 
642:     /**
643:      * Display element after end of the table element.
644:      */
645:     protected function printElementAfterTable()
646:     {
647:         
648:     }// printElementAfterTable
649: 
650: 
651:     /**
652:      * Display element before begins of the table element.
653:      */
654:     protected function printElementBeforeTable()
655:     {
656:         
657:     }// printElementBeforeTable
658: 
659: 
660:     /**
661:      * Print extra table controls next to bulk actions but before pagination.
662:      * 
663:      * @param string $which Which position of table controls (top or bottom).
664:      */
665:     protected function printExtraTableControls($which)
666:     {
667:         
668:     }// printExtraTableControls
669: 
670: 
671:     /**
672:      * Print pagination.<br>
673:      * To display different pagination style more than customize via pagination's properties, overwrite this method in the sub class.
674:      * 
675:      * @param string $which Which position of table controls (top or bottom).
676:      */
677:     protected function printPagination($which)
678:     {
679:         if (!is_object($this->Pagination)) {
680:             return null;
681:         }
682: 
683:         echo '    <div class="align-right table-controls-pagination">'."\n";
684:         echo '        <span class="align-left total-items-found">' . sprintf($this->getText('x_items'), $this->totalItems) . '</span>'."\n";
685: 
686:         $pagination_data = $this->Pagination->getPaginationData();
687:         $current_page = $this->getCurrentPage();
688: 
689:         if (is_array($pagination_data) && array_key_exists('generated_pages', $pagination_data)) {
690:             echo '        <div class="align-right pagination-links">'."\n";
691:             foreach ($pagination_data['generated_pages'] as $page_key => $page_item) {
692:                 if (is_array($page_item)) {
693:                     if (array_key_exists('link', $page_item) && array_key_exists('page_value', $page_item) && array_key_exists('text', $page_item)) {
694:                         echo '            ';
695: 
696:                         if ($current_page == $page_item['page_value']) {
697:                             echo '<span class="pagination-page disabled ' . str_replace('_', '-', $page_key) . '" data-pageValue="' . $page_item['page_value'] . '" aria-hidden="true">' . $page_item['text'] . '</span>';
698:                         } else {
699:                             echo '<a class="pagination-page ' . str_replace('_', '-', $page_key) . '" href="' . $page_item['link'] . '" data-pageValue="' . $page_item['page_value'] . '">' . $page_item['text'] . '</a>';
700:                         }
701: 
702:                         if ($page_key === 'previous_page') {
703:                             // next to previous page button, display the current page of total page.
704:                             echo "\n";
705:                             echo '            ';
706:                             if ($which == 'top') {
707:                                 echo '<span class="pagination-input">'."\n";
708:                                 echo '                <label class="sr-only screen-reader-only" for="current-pagination-input">' . $this->getText('current_page') . '</label>'."\n";
709:                                 echo '                <input id="current-pagination-input" class="current-page" type="number" name="' . $this->paginationQueryName . '" value="' . $current_page . '" step="1" min="1">'."\n";
710:                                 echo '            </span>'."\n";
711:                                 echo '            <span class="pagination-text">' . sprintf($this->getText('page_of_x'), (isset($pagination_data['total_pages']) ? $pagination_data['total_pages'] : 0)) . '</span>';
712:                             } else {
713:                                 echo '<span class="pagination-text">' . sprintf($this->getText('page_x_of_x'), $current_page, (isset($pagination_data['total_pages']) ? $pagination_data['total_pages'] : 0)) . '</span>';
714:                             }
715:                         }
716: 
717:                         echo "\n";
718:                     }
719:                 }
720:             }// endforeach;
721:             unset($page_item, $page_key);
722:             echo '        </div>'."\n";// .pagination-links
723:         }
724: 
725:         unset($current_page, $pagination_data);
726: 
727:         echo '        <div class="clearfix"></div>'."\n";
728:         echo '    </div>';// .table-controls-pagination
729:         echo "\n";
730:     }// printPagination
731: 
732: 
733:     /**
734:      * Loop through data items (rows) and call to print each row.
735:      */
736:     protected function printRows()
737:     {
738:         foreach ($this->dataItems as $row) {
739:             $this->printSingleRow($row);
740:         }
741:     }// printRows
742: 
743: 
744:     /**
745:      * Display table rows inside <code>tbody</code> or no items place holder if there is no item found.
746:      */
747:     protected function printRowsOrPlaceholder()
748:     {
749:         if (is_array($this->dataItems) && !empty($this->dataItems)) {
750:             // there are data.
751:             $this->printRows();
752:         } else {
753:             // there is no data.
754:             echo '        <tr class="no-item">'."\n";
755:             echo '            <td' . $this->renderAttributes($this->getTbodyTrTdNodataAttributes()) . ' colspan="' . $this->columnsCount . '">'."\n";
756:             echo '                ' . $this->getText('no_items_found') . "\n";
757:             echo '            </td>'."\n";
758:             echo '        </tr>'."\n";
759:         }
760:     }// printRowsOrPlaceholder
761: 
762: 
763:     /**
764:      * Print each row and call to print single row columns.
765:      * 
766:      * @param object $row Data from PDO in each row from fetchAll() method.
767:      */
768:     protected function printSingleRow($row)
769:     {
770:         echo '        <tr>'."\n";
771:         $this->printSingleRowColumns($row);
772:         echo '        </tr>'."\n";
773:     }// printSingleRow
774: 
775: 
776:     /**
777:      * Print row columns.
778:      * 
779:      * @param object $row Data from PDO in each row from fetchAll() method.
780:      */
781:     protected function printSingleRowColumns($row)
782:     {
783:         $columns = $this->getColumns();
784:         $primary = $this->getPrimaryColumn();
785: 
786:         foreach ($columns as $column_unique_name => $column_display_name)
787:         {
788:             $attributes = [];
789:             $class = [$column_unique_name, 'column-' . $column_unique_name];
790: 
791:             if ($column_unique_name == $primary) {
792:                 $class[] = 'column-primary';
793:             }
794: 
795:             $class = array_merge($class, $this->getColumnClasses($row, $column_unique_name));
796: 
797:             if ($column_unique_name === 'checkbox') {
798:                 $table_cell = 'th';
799:                 $scope = 'row';
800:                 $table_cell_content = $this->columnCheckbox($row);
801:             } elseif (method_exists($this, 'column' . ucfirst($column_unique_name))) {
802:                 $table_cell = 'td';
803:                 $table_cell_content = call_user_func([$this, 'column' . ucfirst($column_unique_name)], $row);
804:                 $table_cell_content .= $this->renderRowActions($row, $column_unique_name, $primary);
805:             } else {
806:                 $table_cell = 'td';
807:                 $table_cell_content = $this->columnDefault($row, $column_unique_name);
808:                 $table_cell_content .= $this->renderRowActions($row, $column_unique_name, $primary);
809:             }
810: 
811:             $attributes['class'] = implode(' ', $class);
812:             if (isset($scope)) {
813:                 $attributes['scope'] = $scope;
814:                 unset($scope);
815:             }
816:             if (strip_tags($column_display_name) != null) {
817:                 $attributes['data-colname'] = strip_tags($column_display_name);
818:             }
819: 
820:             echo '            <' . $table_cell . $this->renderAttributes($attributes) . '>'."\n";
821:             echo '                ' . $table_cell_content . "\n";
822:             echo '            </' . $table_cell . '>'."\n";
823: 
824:         }// endforeach;
825:         unset($attributes, $class, $column_display_name, $column_unique_name, $scope, $table_cell, $table_cell_content);
826: 
827:         unset($columns, $primary);
828:     }// printSingleRowColumn
829: 
830: 
831:     /**
832:      * Print table controls such as bulk actions, pagination or also know as table nav in WordPress.
833:      * 
834:      * @param string $which Which position of table controls (top or bottom).
835:      */
836:     protected function printTableControls($which = 'top')
837:     {
838:         echo '<div class="table-controls ' . $which . '">'."\n";
839:         if (!empty($this->dataItems)) {
840:             echo '    <div class="align-left control-actions bulk-actions">'."\n";
841:             $this->printBulkActions($which);
842:             echo '    </div>'."\n";
843:         }
844:         $this->printExtraTableControls($which);
845:         $this->printPagination($which);
846:         echo '    <div class="clearfix"></div>'."\n";
847:         echo '</div>'."\n";
848:     }// printTableControls
849: 
850: 
851:     /**
852:      * Render attributes from get***Attributes() methods. (Not echo out just render as string.)
853:      * 
854:      * @param array $attributes The attributes as array key => value pairs. Example: ['class' => 'table-cell column-pk', 'data-colname' => 'full name'].
855:      * @return string Return rendered attributes. Example: <code> class="table-cell column-pk" data-colname="full name"</code>.
856:      */
857:     protected function renderAttributes(array $attributes = [])
858:     {
859:         $output = '';
860:         if (is_array($attributes) && !empty($attributes)) {
861:             foreach ($attributes as $attribute => $value) {
862:                 if (!is_object($attribute) && !is_array($attribute) && !is_object($value) && !is_array($value)) {
863:                     $output .= ' ' . $attribute . '="' . $value . '"';
864:                 }
865:             }// endforeach;
866:             unset($attribute, $value);
867:         }
868: 
869:         return $output;
870:     }// renderAttributes
871: 
872: 
873:     /**
874:      * Render row actions.
875:      * 
876:      * @param object $row Data from PDO in each row from fetchAll() method.
877:      * @param string $column_unique_name Column unique name for check.
878:      * @param string $primary_column_name Column name that is primary.
879:      */
880:     protected function renderRowActions($row, $column_unique_name, $primary_column_name)
881:     {
882:         if ($column_unique_name != $primary_column_name) {
883:             return null;
884:         }
885: 
886:         $output = "\n";
887: 
888:         $actions = $this->getRowActions($row, $column_unique_name, $primary_column_name);
889:         if (is_array($actions)) {
890:             $action_count = count($actions);
891:             if ($action_count > 0) {
892:                 $output .= '                <div class="row-actions">'."\n";
893:                 foreach ($actions as $action_key => $action_link) {
894:                     $output .= '                    <span class="action-' . $action_key . '">' . $action_link . '</span>'."\n";
895:                 }// endforeach;
896:                 unset($action_key, $action_link);
897:                 $output .= '                </div>'."\n";
898:             }
899:             unset($action_count);
900:         }
901:         unset($actions);
902: 
903:         $output .= '                <button class="toggle-row" type="button"><span class="sr-only screen-reader-only">' . $this->getText('show_more_details') . '</span></button>'."\n";
904: 
905:         return $output;
906:     }// renderRowActions
907: 
908: 
909:     /**
910:      * Reset all properties including database configuration to start data table class from the beginnings.
911:      */
912:     public function reset()
913:     {
914:         $this->Pagination->clear();
915: 
916:         if (is_array($this->tempProperties)) {
917:             foreach ($this->tempProperties as $key => $value) {
918:                 if (property_exists($this, $key)) {
919:                     $this->{$key} = $value;
920:                 }
921:             }// endforeach;
922:             unset($key, $value);
923:         }
924: 
925:         $this->tempProperties = null;
926:     }// reset
927: 
928: 
929: }
930: 
API documentation generated by ApiGen