1: <?php
2: 3: 4: 5: 6: 7:
8:
9:
10: namespace Rundiz\DataTable;
11:
12: 13: 14: 15: 16: 17:
18: class DataTable
19: {
20:
21:
22: 23: 24:
25: protected $columnsCount = 0;
26:
27:
28: 29: 30:
31: protected $Database;
32:
33:
34: 35: 36:
37: protected $dataItems = [];
38:
39:
40: 41: 42:
43: protected $itemsPerPage = 20;
44:
45:
46: 47: 48:
49: protected $orderQueryName = 'order';
50:
51:
52: 53: 54:
55: protected $Pagination;
56:
57:
58: 59: 60:
61: protected $paginationQueryName = 'paged';
62:
63:
64: 65: 66:
67: protected $showFooterColumn = true;
68:
69:
70: 71: 72:
73: protected $sortQueryName = 'sort';
74:
75:
76: 77: 78:
79: protected $tempProperties;
80:
81:
82: 83: 84:
85: protected $totalItems = 0;
86:
87:
88: 89: 90: 91: 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: }
103:
104:
105: 106: 107: 108:
109: public function __destruct()
110: {
111: $this->reset();
112: }
113:
114:
115: 116: 117: 118: 119: 120:
121: protected function columnCheckbox($row)
122: {
123:
124: }
125:
126:
127: 128: 129: 130: 131: 132: 133: 134: 135:
136: protected function columnDefault($row, $column_unique_name)
137: {
138:
139: }
140:
141:
142: 143: 144:
145: public function display()
146: {
147:
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: }
180:
181:
182: 183: 184: 185: 186:
187: protected function getBulkActions()
188: {
189: return [];
190: }
191:
192:
193: 194: 195: 196: 197: 198: 199: 200:
201: protected function getColumnClasses($row, $column_unique_name = '')
202: {
203: return [];
204: }
205:
206:
207: 208: 209: 210: 211: 212: 213:
214: protected function getColumnFooterClasses($column_unique_name = '')
215: {
216: return [];
217: }
218:
219:
220: 221: 222: 223: 224: 225: 226:
227: protected function getColumnHeaderClasses($column_unique_name = '')
228: {
229: return [];
230: }
231:
232:
233: 234: 235: 236: 237:
238: protected function getColumns()
239: {
240: die('The required getColumns() method is missing from sub class.');
241: }
242:
243:
244: 245: 246: 247: 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: }
259:
260:
261: 262: 263: 264: 265: 266: 267: 268:
269: protected function getPrimaryColumn()
270: {
271: $columns = $this->getColumns();
272:
273: if (is_array($columns) && array_key_exists('checkbox', $columns)) {
274:
275: while (key($columns) !== 'checkbox') {
276: next($columns);
277: }
278:
279:
280: $next_checkbox_column_display = next($columns);
281: $next_checkbox_column_uniquename = key($columns);
282:
283: if ($next_checkbox_column_display !== false) {
284:
285: unset($columns, $next_checkbox_column_display);
286: return $next_checkbox_column_uniquename;
287: }
288: }
289:
290:
291: unset($next_checkbox_column_display, $next_checkbox_column_uniquename);
292:
293:
294:
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: }
302:
303:
304: 305: 306: 307: 308: 309: 310: 311: 312:
313: protected function getRowActions($row, $column_unique_name, $primary_column_name)
314: {
315: return [];
316: }
317:
318:
319: 320: 321: 322: 323: 324:
325: protected function getSortableColumns()
326: {
327: return [];
328: }
329:
330:
331: 332: 333: 334: 335:
336: protected function getTableAttributes()
337: {
338: return [];
339: }
340:
341:
342: 343: 344: 345: 346:
347: protected function getTableClasses()
348: {
349: return ['data-table', 'list-table'];
350: }
351:
352:
353: 354: 355: 356: 357:
358: protected function getTbodyAttributes()
359: {
360: return [];
361: }
362:
363:
364: 365: 366: 367: 368:
369: protected function getTbodyTrTdNodataAttributes()
370: {
371: return ['class' => 'no-item no-data'];
372: }
373:
374:
375: 376: 377: 378: 379: 380: 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: }
411:
412:
413: 414: 415: 416: 417:
418: protected function getTfootAttributes()
419: {
420: return [];
421: }
422:
423:
424: 425: 426: 427: 428:
429: protected function getTfootTrAttributes()
430: {
431: return [];
432: }
433:
434:
435: 436: 437: 438: 439:
440: protected function getTheadAttributes()
441: {
442: return [];
443: }
444:
445:
446: 447: 448: 449: 450:
451: protected function getTheadTrAttributes()
452: {
453: return [];
454: }
455:
456:
457: 458: 459:
460: public function prepareData()
461: {
462: die('The required prepareData() method is missing from sub class.');
463: }
464:
465:
466: 467: 468: 469: 470: 471: 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:
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:
485: $this->Pagination->first_page_always_show = true;
486: $this->Pagination->first_page_text = '«';
487: $this->Pagination->previous_page_always_show = true;
488: $this->Pagination->previous_page_text = '‹';
489: $this->Pagination->number_display = false;
490: $this->Pagination->next_page_always_show = true;
491: $this->Pagination->next_page_text = '›';
492: $this->Pagination->last_page_always_show = true;
493: $this->Pagination->last_page_text = '»';
494: }
495:
496:
497: 498: 499:
500: protected function printBeforeTableControlsTop()
501: {
502:
503: }
504:
505:
506: 507: 508: 509: 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: }
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: }
535:
536:
537: 538: 539: 540: 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:
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) . '&';
589: }
590: }
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) . '&' . $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: }
635: unset($class, $column_display_name, $column_unique_name, $id, $scope, $table_cell);
636:
637: unset($columns, $primary, $sortable);
638: echo "\n";
639: }
640:
641:
642: 643: 644:
645: protected function printElementAfterTable()
646: {
647:
648: }
649:
650:
651: 652: 653:
654: protected function printElementBeforeTable()
655: {
656:
657: }
658:
659:
660: 661: 662: 663: 664:
665: protected function printExtraTableControls($which)
666: {
667:
668: }
669:
670:
671: 672: 673: 674: 675: 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:
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: }
721: unset($page_item, $page_key);
722: echo ' </div>'."\n";
723: }
724:
725: unset($current_page, $pagination_data);
726:
727: echo ' <div class="clearfix"></div>'."\n";
728: echo ' </div>';
729: echo "\n";
730: }
731:
732:
733: 734: 735:
736: protected function printRows()
737: {
738: foreach ($this->dataItems as $row) {
739: $this->printSingleRow($row);
740: }
741: }
742:
743:
744: 745: 746:
747: protected function printRowsOrPlaceholder()
748: {
749: if (is_array($this->dataItems) && !empty($this->dataItems)) {
750:
751: $this->printRows();
752: } else {
753:
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: }
761:
762:
763: 764: 765: 766: 767:
768: protected function printSingleRow($row)
769: {
770: echo ' <tr>'."\n";
771: $this->printSingleRowColumns($row);
772: echo ' </tr>'."\n";
773: }
774:
775:
776: 777: 778: 779: 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: }
825: unset($attributes, $class, $column_display_name, $column_unique_name, $scope, $table_cell, $table_cell_content);
826:
827: unset($columns, $primary);
828: }
829:
830:
831: 832: 833: 834: 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: }
849:
850:
851: 852: 853: 854: 855: 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: }
866: unset($attribute, $value);
867: }
868:
869: return $output;
870: }
871:
872:
873: 874: 875: 876: 877: 878: 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: }
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: }
907:
908:
909: 910: 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: }
922: unset($key, $value);
923: }
924:
925: $this->tempProperties = null;
926: }
927:
928:
929: }
930: