Overview
Packages
Classes
1: <?php
2:
3: /**
4: * Onion Framework - Zobrazenie dát v tabuľke s možnosťou radenia a filtrovania
5: *
6: * Copyright (c) 2011 Jano Gašpar (http://webstranky.net)
7: *
8: * @author Jano Gašpar
9: * @copyright Copyright (c) 2011 Jano Gašpar
10: * @package Onion::UI
11: **/
12: class Grid
13: {
14: private $auth;
15:
16:
17: /**
18: * @var string id na identifikáciu gridu
19: */
20: private $id;
21:
22:
23: /**
24: * @var string token na identifikáciu gridu
25: */
26: private $token;
27:
28:
29: /**
30: * @var string metóda na získavanie
31: */
32: private $model;
33:
34:
35: /**
36: * @var array stĺpce
37: */
38: private $columns = array();
39:
40:
41: /**
42: * @var array aktuálny sĺpec
43: */
44: private $current_column = array();
45:
46:
47: /**
48: * @var array akcie
49: */
50: private $actions = array();
51:
52:
53: /**
54: * @var array akcie na hromadné spracovanie dát
55: */
56: private $multi_actions = array();
57:
58:
59: /**
60: * @var int počet riadkov v gride, NULL zakáže stránkovanie
61: */
62: public $rows = 15;
63:
64:
65: /**
66: * @var int meno routy podľa ktorej sa generujú odkazy
67: */
68: public $route_name = NULL;
69:
70:
71: /**
72: * @var array stĺpec a smer radenia
73: */
74: public $default_order = array('id', 'ASC');
75:
76:
77: /**
78: * @var array texty
79: */
80: public $strings = array(
81: 'page' => 'Page: ',
82: 'selected items' => 'Selected items: ',
83: 'search' => 'Search',
84: 'show all' => 'Show all'
85: );
86:
87:
88: /**
89: * @var string CSS ID
90: */
91: public $css_id;
92:
93:
94:
95: /**
96: * Konštruktor
97: *
98: * @param string $form_id identifikátor gridu
99: * @param object $model inštancia modelu na manipuláciu s dátami
100: * @return object grid instance
101: */
102: public function __construct($app, $grid_id, $model)
103: {
104: $this->app = $app;
105:
106: $this->id = $grid_id;
107:
108: //token sa generuje nanovo pre každú reláciu (session)
109: $token = md5(uniqid('', TRUE));
110: if (isset($_SESSION['grid'][$grid_id]['token']) === TRUE) {
111: $token = $_SESSION['grid'][$grid_id]['token'];
112: }
113: $this->token = $_SESSION['grid'][$grid_id]['token'] = $token;
114:
115: $this->model = $model;
116:
117: return $this;
118: }
119:
120:
121: /**
122: * Nastavovanie atribútov stĺpcov gridu pomocuu preťažovania
123: *
124: * @param string $atribute meno atribútu
125: * @param string|array|bool $value hodnota atribútu
126: */
127: final public function __call($attribute, $value)
128: {
129: if (isset($value[0]) === FALSE) {
130: $value = '';
131:
132: } else {
133: $value = $value[0];
134: }
135:
136: $this->current_column[$attribute] = $value;
137:
138: return $this;
139: }
140:
141:
142: /**
143: * Metóda volaná pri prístupe k objektu ako k reťazcu
144: */
145: final public function __tostring()
146: {
147: return $this->render();
148: }
149:
150:
151: /**
152: * Metóda na pridanie stĺpca
153: *
154: * @param string meno stĺpca v databáze
155: * @param string meno stĺpca zobrazené v gride
156: * @param int orezať na počet znakov
157: * @return object grid instance
158: */
159: public function add_column($column, $title)
160: {
161: $this->save_prev_column();
162:
163: $this->current_column = array(
164: 'column' => $column,
165: 'title' => $title,
166: 'strip_to' => NULL,
167: 'filter' => NULL,
168: 'format' => NULL
169: );
170:
171: return $this;
172: }
173:
174:
175: /**
176: * Metóda na uloženie stĺpca a vytvorenie nového
177: *
178: * @param string identifikátor gridu
179: * @return object grid instance
180: */
181: private function save_prev_column()
182: {
183: if (empty($this->current_column) === FALSE) {
184: $alias = $this->current_column['column'];
185:
186: list($alias) = preg_split('/( as )/i', $alias);
187: $alias = str_replace('.', '_', $alias);
188: $this->current_column['alias'] = $alias;
189:
190: $this->columns[$alias] = $this->current_column;
191: $this->current_column = array();
192: }
193: }
194:
195:
196: /**
197: * Metóda na pridanie akcie
198: *
199: * @param string meno akcie
200: * @param string meno routy ktorá sa použije na vygenerovanie uri
201: * @param string cesta k ikone, ak je NULL použije sa iba text
202: * @return object grid instance
203: */
204: public function add_action($title, $route_name, $class = NULL)
205: {
206: $this->actions[] = array(
207: 'title' => $title,
208: 'route_name' => $route_name,
209: 'class' => $class
210: );
211:
212: return $this;
213: }
214:
215:
216: /**
217: * Metóda na pridanie akcie na hromadné spracovanie
218: *
219: * @param string meno akcie
220: * @param string meno funkcie ktorá má vykonať hromadnú akciu
221: * @return object grid instance
222: */
223: public function add_multi_action($title, $callback, $class = NULL)
224: {
225: $this->multi_actions[] = array(
226: 'title' => $title,
227: 'callback' => $callback,
228: 'class' => $class
229: );
230:
231: return $this;
232: }
233:
234:
235: /**
236: * Metóda na priradenie filtra k stĺpcu
237: *
238: * @param string typ filtra
239: * @param string|array parametre filtra
240: * @return object grid instance
241: */
242: public function add_filter($type = 'text', $parameters = NULL)
243: {
244: $this->current_column['filter'] = array(
245: 'type' => $type,
246: 'parameters' => $parameters
247: );
248:
249: return $this;
250: }
251:
252:
253: /**
254: * Metóda na priradenie formátovania k stĺpcu
255: *
256: * @param string|array metóda, pole = externá metóda, reťazec = interná metóda
257: * @param string|array parametre formátovania
258: * @return object grid instance
259: */
260: public function format()
261: {
262: $arguments = func_get_args();
263:
264: $this->current_column['format']['callback'] = array_shift($arguments);
265: $this->current_column['format']['arguments'] = $arguments;
266:
267: return $this;
268: }
269:
270:
271: /**
272: * Metóda na vykreslenie gridu
273: */
274: public function render()
275: {
276: $this->save_prev_column();
277:
278: $uri_data = $this->app->request->get;
279: $uri = $this->app->create_uri($this->route_name, array(), $uri_data);
280:
281: $grid = Html::element('form')
282: ->method('get')
283: ->action($uri)
284: ->id($this->css_id)
285: ->class('grid');
286:
287: $hiddens = $grid->create('p')
288: ->class('hiddens');
289:
290: $hiddens->create('input')
291: ->type('hidden')
292: ->name('token')
293: ->value($this->token);
294:
295: $table = $grid->create('table');
296: $header = $table->create('thead');
297: $body = $table->create('tbody');
298:
299: $tr_titles = $header->create('tr')
300: ->class('headers');
301: $tr_filters = Html::element('tr')
302: ->class('filters');
303:
304: // Ak sú nadefinované akcie na hromadné spracovanie
305: // budú pridané aj checkboxy, preto aby sedeli počty stĺpcov
306: // je treba vložiť stĺpce
307: if (empty($this->multi_actions) === FALSE) {
308: // do hlavičiek
309: $tr_titles->create('th')
310: ->set_text(' ', TRUE);
311:
312: // a do filtrov
313: $tr_filters->create('th')
314: ->set_text(' ', TRUE);
315: }
316:
317: $uri_data = array();
318: $uri_data['filters'] = array();
319: $uri_data['order'] = array($this->default_order[0] => $this->default_order[1]);
320: $uri_data['page'] = 1;
321: $uri_data['token'] = $this->token;
322:
323: if ($this->is_submitted() === TRUE) {
324: if (isset($this->app->request->get['filters_reset']) === FALSE) {
325: if (isset($this->app->request->get['filters']) === TRUE) {
326: $uri_data['filters'] = $this->app->request->get['filters'];
327: }
328:
329: if (isset($this->app->request->get['order']) === TRUE) {
330: $uri_data['order'] = $this->app->request->get['order'];
331: }
332:
333: $uri_data['page'] = max(Arrays::get_value($this->app->request->get, 'page'), 1);
334:
335: } else {
336: $uri = $this->app->create_uri($this->route_name, array(), $uri_data);
337: $this->app->response->redirect($uri, 303);
338: }
339:
340: } elseif (isset($_SESSION['grid'][$this->id]['uri_data']) === TRUE) {
341: $this->app->keep_flash();
342:
343: $uri_data = $_SESSION['grid'][$this->id]['uri_data'];
344: $uri = $this->app->create_uri($this->route_name, array(), $uri_data);
345: $this->app->response->redirect($uri, 303);
346: }
347:
348: $_SESSION['grid'][$this->id]['uri_data'] = $uri_data;
349:
350: if (empty($uri_data['filters']) === FALSE) {
351: foreach ($uri_data['filters'] as $column_name => $value) {
352: if (Validate::is_empty($value) === TRUE) {
353: continue;
354: }
355:
356: if ($value == -1) {
357: continue;
358: }
359:
360: $db_column = $this->columns[$column_name]['column'];
361:
362: if ($this->columns[$column_name]['filter']['type'] === 'text') {
363: $this->model->where($db_column, '%' . $value . '%', 'LIKE');
364:
365: } else {
366: $this->model->where($db_column, $value);
367: }
368: }
369: }
370:
371: if (empty($uri_data['order']) === FALSE) {
372: foreach ($uri_data['order'] as $col => $dir) {
373: $this->model->order($col, $dir);
374: }
375: }
376:
377: $columns = array();
378: $db_columns = array($this->model->table . '.id as id');
379:
380: // Príznak na vloženie filtrov
381: // zabezpečuje vykreslenie filtrov iba v prípade že sú definované
382: $add_filters = FALSE;
383:
384: // Vykreslenie hlavičiek
385: foreach ($this->columns as $alias => $column) {
386: $columns[] = $column['column'];
387: $db_columns[] = $column['column'] . ' AS ' . $alias;
388:
389: $uri_data_tmp = $uri_data;
390: $uri_data_tmp['order'] = array($alias => 'ASC');
391: $uri_data_tmp['page'] = 1;
392:
393: // Vytvorenie adresy na nastavenie radenia
394: // Ak sa triedi podľa tohoto stĺpca, je treba v adrese prehodiť smer radenia
395: if (isset($uri_data['order'][$alias]) === TRUE
396: AND $uri_data['order'][$alias] === 'ASC') {
397:
398: $uri_data_tmp['order'] = array($alias => 'DESC');
399: }
400:
401: $uri = $this->app->create_uri($this->route_name, array(), $uri_data_tmp);
402:
403: // Názov
404: $title = $tr_titles->create('th');
405: $title->class[] = $column['column'];
406: $title->create('a')
407: ->href($uri)
408: ->set_text($column['title']);
409:
410: if (isset($order[$alias]) === TRUE) {
411: $title->class[] = 'order-column';
412:
413: if ($order[$alias] === 'ASC') {
414: $title->class[] = 'order-asc';
415:
416: } else {
417: $title->class[] = 'order-desc';
418: }
419: }
420:
421: // Ak stĺpec nemá filter vloží sa iba
422: if ($column['filter'] === NULL) {
423: $tr_filters->create('th')
424: ->class($column['column'])
425: ->set_text(' ', TRUE);
426:
427: } else {
428: // v opačnom prípade sa vykreslí pole filtra
429: $value = NULL;
430:
431: if (isset($uri_data['filters'][$column['alias']]) === TRUE) {
432: $value = $uri_data['filters'][$column['alias']];
433: }
434:
435: $add_filters = TRUE;
436: $tr_filters->create('th')
437: ->class($column['alias'])
438: ->add($this->filter_item($column, $value));
439: }
440: }
441:
442: // Ak sú nadefinované akcie pre jednotlivé položky
443: // je treba vložiť stĺpce aby sedeli počty stĺpcov
444: if (empty($this->actions) === FALSE) {
445: // do hlavičiek
446: $tr_titles->create('th')
447: ->set_text(' ', TRUE);
448:
449: if ($add_filters === FALSE) {
450: // a do filtrov
451: $tr_filters->create('th')
452: ->set_text(' ', TRUE);
453:
454: } else {
455: $buttons = $tr_filters->create('th')
456: ->class('grid_actions');
457: }
458: }
459:
460: // Filtre sú definované, je treba ich vykresliť
461: if ($add_filters === TRUE) {
462: $header->add($tr_filters);
463:
464: if (isset($buttons) === FALSE) {
465: $colspan = $tr_filters->count();
466: $buttons = $header->create('tr')
467: ->create('td')
468: ->colspan($colspan);
469: }
470:
471: // a pridať tlačidlá na odoslanie filtra
472: $buttons->create('input')
473: ->type('submit')
474: ->name('filters_submit')
475: ->value($this->strings['search']);
476:
477: // a reset filtra
478: $buttons->create('input')
479: ->type('submit')
480: ->name('filters_reset')
481: ->value($this->strings['show all']);
482: }
483:
484: // Na vykreslenie je nutné vedieť aj ID položiek, preto sa pole ID pridá
485: // do zoznamu polí ktoré sa berú z databázy
486: //$db_columns = array_merge(array($this->model->table . '.id as id'), $columns);
487: $db_columns = array_unique($db_columns);
488:
489: $count = $this->model->count();
490: if (is_numeric($count) === FALSE) {
491: $count = $count->count();
492: }
493:
494: // Vytvorenie stránkovača
495: $paginator = new Paginator($this->app, $uri_data['page'], $count, $this->rows);
496: $paginator->route_name = $this->app->route['name'];
497: $paginator->uri_parameter_name = 'page';
498:
499: $hiddens->create('input')
500: ->type('hidden')
501: ->name('page')
502: ->value($uri_data['page']);
503:
504: if ($this->rows !== NULL) {
505: // Limit pre model sa berie zo stránkovača, kde sa vypočítajú správne čísla
506: $this->model->limit($paginator->limit);
507: }
508:
509: // Samotné vykreslenie riadkov
510: foreach ($this->model->get_rows($db_columns) as $row) {
511: $tr = $body->create('tr');
512:
513: // Ak sú definované hromadné akcie je treba pridať checkboxy
514: if (empty($this->multi_actions) === FALSE) {
515: $tr->create('td')
516: ->create('input')
517: ->type('checkbox')
518: ->name('items[' . $row['id'] . ']');
519: }
520:
521: // Vykreslenie a formátovanie údajov
522: foreach ($columns as $column) {
523: //prispôsobenie názvu stĺpca ak je v dopyte table.column_name as new_column_name
524: $column_name = $column;
525:
526: list($column_name) = preg_split('/( as )/i', $column_name);
527: $column_name = str_replace('.', '_', $column_name);
528:
529: $text = $row[$column_name];
530:
531: $strip_to = $this->columns[$column_name]['strip_to'];
532:
533: if ($strip_to !== NULL) {
534: $text = mb_substr($text, 0, $strip_to, 'UTF-8');
535: }
536:
537: if ($this->columns[$column_name]['format'] !== NULL) {
538: $callback = $this->columns[$column_name]['format']['callback'];
539: $arguments = $this->columns[$column_name]['format']['arguments'];
540: //$arguments = array_merge(array($text), $arguments, array($row));
541: $arguments = array_merge(array($text), $arguments, array($row));
542:
543: if (is_array($callback) === TRUE) {
544: $text = call_user_func_array($callback, $arguments);
545:
546: } else {
547: // Lokálne funkcie majú prefix "format_"
548: $callback = 'format_' . $callback;
549: $text = call_user_func_array(array($this, $callback), $arguments);
550: }
551: }
552:
553: $tr->create('td')
554: ->class(Strings::webalize($column))
555: ->add($text);
556: }
557:
558: // Vykreslenie akcií na jednotlivých položkách ak sú definované
559: if (empty($this->actions) === FALSE) {
560: $uri_data = $this->app->request->get;
561:
562: $actions = $tr->create('td')
563: ->class('grid_actions');
564:
565: foreach ($this->actions as $action) {
566: $uri = $this->app->create_uri($action['route_name'], array('id' => $row['id']), $uri_data);
567:
568: $actions->create('a')
569: ->href($uri)
570: ->set_text($action['title'])
571: ->class($action['class']);
572:
573: $actions->add(' ', TRUE);
574: }
575: }
576: }
577:
578: // Vykreslenie tlačidiel hromadných akcií
579: $footer = $grid->create('div')
580: ->class('footer');
581:
582: if (empty($this->multi_actions) === FALSE) {
583: $buttons = $footer->create('div');
584: $buttons->add($this->strings['selected items']);
585:
586: foreach($this->multi_actions as $key => $multi_action) {
587: if ($this->is_submitted() === TRUE
588: AND isset($this->app->request->get['multi_action'][$key]) === TRUE
589: AND isset($this->app->request->get['items']) === TRUE) {
590:
591: call_user_func($multi_action['callback'], array_keys($this->app->request->get['items']));
592: }
593:
594: $buttons->create('input')
595: ->type('submit')
596: ->value($multi_action['title'])
597: ->class($multi_action['class'])
598: ->name('multi_action[' . $key . ']');
599: }
600: }
601:
602: // Vykreslenie stránkovačov
603: $p = $footer->create('div')
604: ->class('paginator')
605: ->set_text($this->strings['page'])
606: ->add($paginator);
607:
608: $grid->insert(0, $p);
609:
610: return $grid->render();
611: }
612:
613:
614: /**
615: * Rozhodovacia metóda na rozhodnutie aké filtrovacie pole sa vykreslí
616: *
617: * @param array stĺpec ktorému je treba vykresliť filtrovacie pole
618: * @param string aktuálna hodnota vyhľadávacieho poľa
619: */
620: private function filter_item($column, $value)
621: {
622: if (is_string($column['filter']['type']) === TRUE) {
623: if ($column['filter']['type'] === 'select') {
624: return $this->filter_item_select($column, $value);
625:
626: } elseif ($column['filter']['type'] === 'text') {
627: return $this->filter_item_text($column, $value);
628: }
629: }
630:
631: return call_user_func($column['filter']['type'], $column, $value);
632: }
633:
634:
635: /**
636: * Metóda na vykreslenie textového filtra
637: *
638: * @param array stĺpec ktorému je treba vykresliť filtrovacie pole
639: * @param string aktuálna hodnota vyhľadávacieho poľa
640: */
641: private function filter_item_text($column, $value)
642: {
643: $filter = Html::element('input')
644: ->name('filters[' . $column['alias'] . ']')
645: ->type('text')
646: ->value($value);
647:
648: return $filter;
649: }
650:
651:
652: /**
653: * Metóda na vykreslenie výberového filtra
654: *
655: * @param array stĺpec ktorému je treba vykresliť filtrovacie pole
656: * @param string aktuálna hodnota vyhľadávacieho poľa
657: */
658: private function filter_item_select($column, $value)
659: {
660: if ($value === NULL) {
661: $value = -1;
662: }
663:
664: $options = $column['filter']['parameters'];
665:
666: if ($options === NULL) {
667: preg_match('/(.*)\.([^\ ]*)/i', $column['column'], $matches);
668: if (empty($matches) === FALSE) {
669: $table = $matches[1];
670: $column_name = $matches[2];
671:
672: } else {
673: $table = $this->model->table;
674: $column_name = $column['column'];
675: }
676:
677: $relations = $this->model->current_table_relations;
678: if (isset($relations[$table]) === TRUE) {
679: $table = $relations[$table];
680: }
681:
682: $table = $this->model->prefix . $table;
683:
684: $result = Database::query('SELECT `' . $column_name . '`, `' . $column_name . '` as `value` FROM `' . $table . '` ORDER BY `' . $column_name . '`');
685: $options = $result->fetch_pairs('value', $column_name);
686: $options = array('-1' => $this->strings['show all']) + $options;
687: }
688:
689: $name = 'filters[' . $column['alias'] . ']';
690: $filter = Html::element('select')
691: ->name($name);
692:
693: foreach ($options as $option_value => $text) {
694: $option = $filter->create('option')
695: ->value($option_value)
696: ->set_text($text);
697:
698: if ($value == $option_value) {
699: $option->selected('selected');
700: }
701: }
702:
703: return $filter;
704: }
705:
706:
707: /**
708: * Metóda na zistenie či prijaté dáta patria aktuálnemu gridu
709: */
710: public function is_submitted()
711: {
712: return (Arrays::get_value($this->app->request->get, 'token') === $this->token);
713: }
714:
715:
716: /**
717: * Metóda na formátovanie hodnoty ako adresy - web, alebo mail
718: *
719: * @param string $uri adresa
720: */
721: private function format_uri($uri)
722: {
723: $uri_html = Html::element('a')
724: ->set_text($uri);
725:
726: if (strpos($uri, '@') == TRUE) {
727: $uri_html->href('mailto: ' . $uri);
728:
729: } else {
730: if (substr($uri, 0, 4) !== 'http') {
731: $uri = 'http://' . $uri;
732: }
733:
734: $uri_html->href($uri);
735: }
736:
737: return $uri_html;
738: }
739:
740:
741: /**
742: * Metóda na formátovanie hodnoty funkciou date
743: *
744: * @param string dátum a čas
745: * @param string formátovací reťazec pre funkciu date
746: */
747: private function format_datetime($datetime, $format = 'd. m. Y / H:i')
748: {
749: return date($format, strtotime($datetime));
750: }
751:
752:
753: /**
754: * Metóda na prevod boolean hodnoty na reťazec
755: *
756: * @param bool dátum a čas
757: * @param string reťazec ktorý sa má vrátiť pri FALSE
758: * @param string reťazec ktorý sa má vrátiť pri TRUE
759: */
760: private function format_bool($value, $no = 'No', $yes = 'Yes')
761: {
762: if ($value == TRUE) {
763: return $yes;
764: }
765:
766: return $no;
767: }
768: }