1: <?php
2:
3: namespace Zippy\Html\DataList;
4:
5: use Zippy\Html\HtmlComponent;
6: use Zippy\Interfaces\Requestable;
7: use Zippy\WebApplication;
8:
9: /**
10: * Класс вывода табличных данных. Использует собственное формирование строк и столбцов
11: * по массиву строк данных.
12: * Предназначен для тэга TABLE. Может автоматически формировать **пагинатор и сортировку.
13: */
14: class DataTable extends AbstractList implements Requestable
15: {
16: private $columns = array();
17: private $datalist = array();
18: private $cellevent = null;
19: private $cellclickevent = null;
20: private $selectedrow = 0;
21: private $selectedclass = "";
22: private $maxbuttons = 10;
23: private $firstButton = 1;
24: private $header = true;
25: private $paginator = false;
26: private $useajax = false;
27:
28: public function __construct($id, $DataSource, $header = true, $paginator = false, $useajax = false) {
29: AbstractList::__construct($id, $DataSource);
30: $this->header = $header;
31: $this->paginator = $paginator;
32: $this->useajax = $useajax;
33: }
34:
35: /**
36: * @see HtmlComponent
37: */
38: final public function RenderImpl() {
39: $tag = $this->getTag();
40:
41: $tag->appendWith($this->renderHeader());
42: $tag->appendWith($this->renderData());
43: $tag->appendWith($this->renderFooter());
44: }
45:
46: /**
47: * Добавляет столбец к таблице.
48: */
49: final public function AddColumn(Column $column) {
50: $this->columns[$column->fieldname] = $column;
51: }
52:
53: /**
54: * Обновляет данные с провайдера
55: */
56: public function Reload($resetpage = true) {
57: parent::Reload($resetpage);
58:
59: $this->datalist = $this->getItems();
60: }
61:
62: /**
63: * @see Requestable
64: */
65: final public function RequestHandle() {
66: $p = WebApplication::$app->getRequest()->request_params[$this->id];
67: if ($p[0] == 'sort') {
68: $this->sortf = $p[1];
69: $this->sortd = $p[2];
70: $this->currentpage = 1;
71: $this->Reload();
72: }
73: if ($p[0] == 'pag') {
74: $this->currentpage = $p[1];
75: $this->Reload(false);
76: }
77: if ($p[0] == 'cellclick' && $this->cellclickevent instanceof \Zippy\Event) {
78: $items = array();
79: foreach($this->datalist as $it) {
80: $items[$it->getID()]=$it;
81: }
82:
83: // $items = array_values($this->datalist);
84: $this->cellclickevent->onEvent($this, array('dataitem' => $items[$p[2] ], 'field' => $p[1], 'rownumber' => $p[2]));
85: $this->setSelectedrow($p[2]);
86: }
87:
88: if ($this->useajax) {
89: WebApplication::$app->getResponse()->addAjaxResponse($this->AjaxAnswer());
90: }
91: }
92:
93: /**
94: * Устанавливает обработчик на событие прорисовки ячейки.
95: */
96: final public function setCellDrawEvent(\Zippy\Interfaces\EventReceiver $receiver, $handler) {
97: $this->cellevent = new \Zippy\Event($receiver, $handler);
98: }
99:
100: /**
101: * Устанавливает обработчик на click ячейки.
102: */
103: final public function setCellClickEvent(\Zippy\Interfaces\EventReceiver $receiver, $handler) {
104: $this->cellclickevent = new \Zippy\Event($receiver, $handler);
105: }
106:
107: /**
108: * Формирование заголовка таблицы
109: */
110: private function renderHeader() {
111: if (!$this->header) {
112: return "";
113: }
114:
115: $row = "<tr >";
116:
117: foreach ($this->columns as $column) {
118:
119: if (!$column->visible) {
120: continue;
121: }
122:
123: $css = strlen($column->headerclass) > 0 ? "class=\"{$column->headerclass}\"" : "";
124:
125:
126: if ($column->sortable) {
127: $sort = "";
128: if ($column->fieldname === $this->sortf) {
129: if ($this->sortd === 'asc') {
130: $sort = '&#8595;';
131: } else {
132: $sort = '&#8593;';
133: }
134: }
135: $url = $this->getURLNode() . ':sort:' . $column->fieldname . ':' . ($this->sortd === 'asc' ? 'desc' : 'asc');
136: $onclick = "window.location='{$url}'";
137: if ($this->useajax) {
138: $onclick = "getUpdate('{$url}&ajax=true');event.returnValue=false; return false;";
139: }
140: $row .= ("<th {$css} style=\"white-space: nowrap;cursor:pointer;\" onclick=\"{$onclick}\" ><span>{$column->title}</span> {$sort}</th>");
141: } else {
142:
143: $row .= ("<th {$css} ><span>{$column->title}</span></th>");
144: }
145: }
146: $row .= "</tr>";
147: return $row;
148: }
149:
150: /**
151: * Прорисовка строк с данными.
152: */
153: private function renderData() {
154:
155: if (count($this->datalist) == 0) {
156: return ""; //"<tr ><td align=\"center\" colspan=\"" . count($this->columns) . "\" >" . MSG_DATATABLE_NODATA . "</td></tr>";
157: }
158: //$rownumber = 0;
159: $rows = "";
160: foreach ($this->datalist as $item) { //цикл по строкам
161: $rownumber = $item->getID();
162:
163: if ($this->selectedrow == $rownumber && $this->selectedclass != "") {
164: $row = "<tr class=\"{$this->selectedclass}\" >";
165: } else {
166: $row = "<tr >";
167: }
168:
169:
170: foreach ($this->columns as $fieldname => $column) { //цикл по полям
171: if (!$column->visible) {
172: continue;
173: }
174:
175: $data = strlen($item->{$fieldname}) > 0 ? $item->{$fieldname} : $column->defaultdata;
176: $css = strlen($column->rowclass) > 0 ? "class=\"{$column->rowclass}\"" : "";
177: $onclick = "";
178: $style = "";
179:
180:
181: $url = $this->getURLNode() . ':cellclick:' . $fieldname . ':' . $rownumber;
182: if ($column->clickable) {
183:
184: $onclick = "window.location='{$url}'";
185: }
186: if ($column->clickable && $this->useajax) {
187: $onclick = "getUpdate('{$url}&ajax=true');event.returnValue=false; return false;";
188: }
189:
190: if (strlen($onclick) > 0) {
191: $onclick = " onclick=\"" . $onclick . "\" ";
192: $style = "style=\"cursor:pointer;\"";
193: }
194:
195:
196: if ($this->cellevent instanceof \Zippy\Event) {
197: $userdata = $this->cellevent->onEvent($this, array('dataitem' => $item, 'field' => $fieldname, 'rownumber' => $rownumber));
198: if ($userdata !== null && $userdata !== false) {
199: $data = $userdata;
200: }
201: }
202:
203: $row .= ("<td {$css} {$onclick} {$style}>{$data}</td>");
204: }
205: $row .= "</tr>";
206: $rows .= $row;
207: }
208: return $rows;
209: }
210:
211: /**
212: * Прорисовывает строку с пагинатором.
213: */
214: private function renderFooter() {
215: if (!$this->paginator) {
216: return "";
217: }
218: if (count($this->columns) == 0) {
219: return "";
220: }
221: $currentpage = $this->currentpage;
222:
223: $content = '<ul class="pagination">';
224: $pages = $this->getPageCount();
225: if ($pages <= 1) {
226: return '';
227: }
228:
229: if ($currentpage - $this->firstButton > $this->maxbuttons) {
230:
231: $this->firstButton = $currentpage - $this->maxbuttons;
232: }
233: if ($currentpage < $this->firstButton) {
234: $this->firstButton = $currentpage - 1;
235: }
236:
237: if ($this->firstButton > 1) {
238: $content .= "<li class=\"page-item\"><a class=\"page-link\" href='void(0);' onclick=\"" . $this->getPaginatorLink(1) . "\"><span aria-hidden=\"true\">&laquo;</span></a></li>";
239: $content .= "<li class=\"page-item\"><a class=\"page-link\" href='void(0);' onclick=\"" . $this->getPaginatorLink($currentpage - 1) . "\"><span aria-hidden=\"true\">&lsaquo;</span></a></li>";
240: $content .= "<li class=\"page-item\"><a class=\"page-link\" href=\"javascript:void(0);\" >&hellip;</a></li>";
241: }
242:
243:
244: for ($i = $this->firstButton; $i <= $this->firstButton + $this->maxbuttons; $i++) {
245: if ($i > $pages) {
246: break;
247: }
248: if ($currentpage == $i) {
249: $content .= "<li class=\"page-item active\"><a class=\"page-link\" href=\"javascript:void(0);\" > {$i} </a></li>";
250: } else {
251: $content .= "<li class=\"page-item\"><a class=\"page-link\" href=\"javascript:void(0);\" onclick=\"" . $this->getPaginatorLink($i) . "\"> {$i} </a></li>";
252: }
253: }
254:
255: if ($pages > $this->firstButton + $this->maxbuttons) {
256: $content .= "<li class=\"page-item\" ><a class=\"page-link\" href=\"javascript:void(0);\" >&hellip;</a></li>";
257: $content .= "<li class=\"page-item\"><a class=\"page-link\" href='void(0);' onclick=\"" . $this->getPaginatorLink($currentpage + 1) . "\"aria-label=\"Next\"> <span aria-hidden=\"true\">&rsaquo;</span></a></li>";
258: $content .= "<li class=\"page-item\"><a class=\"page-link\" href='void(0);' onclick=\"" . $this->getPaginatorLink($pages) . "\"aria-label=\"Next\"> <span aria-hidden=\"true\">&raquo;</span></a></li>";
259: }
260:
261: $countall = $this->getAllRowsCount();
262: $show = $currentpage * $this->pagesize;
263: if ($pages == $currentpage) {
264: $show = $countall;
265: }
266: if ($countall <= $this->pagesize) {
267: $show = $countall;
268: }
269:
270: $content = "<table ><tr><td valign='middle'>{$show} строк с {$countall} &nbsp;&nbsp;&nbsp;&nbsp;</td><td align='right'> {$content}</td></tr></table>";
271: return "<tr ><td class=\"footercell\" colspan=\"" . count($this->columns) . "\" >{$content}</ul></td></tr>";
272: }
273:
274: /**
275: * Возвращает ссылку для пагинатора.
276: */
277: private function getPaginatorLink($pageno) {
278: $url = $this->getURLNode() . ':pag:' . $pageno;
279: if ($this->useajax) {
280: $onclick = "getUpdate('{$url}&ajax=true');event.returnValue=false; return false;";
281: } else {
282: $onclick = "window.location='{$url}';event.returnValue=false; return false;";
283: }
284: return $onclick;
285: }
286:
287: /**
288: * Устанавливает выделеную строку.
289: * Строка выделяется добавлением CSS класса заданного
290: * методом setSelectedClass
291: *
292: * @param mixed $id ID выделяемой строки
293: */
294: public function setSelectedrow($number) {
295: $this->selectedrow = $number;
296: }
297:
298: /**
299: * Возвращает номер выделеной строки
300: *
301: */
302: public function getSelectedRow() {
303: return $this->selectedRow;
304: }
305:
306: /**
307: * Устанавливает CSS класс для выбранной строки
308: *
309: * @param mixed $selectedclass
310: */
311: public function setSelectedClass($selectedclass) {
312: $this->selectedclass = $selectedclass;
313: }
314:
315: /**
316: * Удаляет все столбцы
317: *
318: */
319: public function removeAllColumns() {
320: $this->columns = array();
321: }
322:
323: }
324:
325: // класс параметров столбца
326: class Column
327: {
328: public function __construct($fieldname, $title, $sortable = false, $visible = true, $clickable = false, $headerclass = "", $rowclass = "", $defaultdata = "") {
329: $this->fieldname = $fieldname;
330: $this->visible = $visible;
331: $this->sortable = $sortable;
332: $this->clickable = $clickable;
333: $this->title = $title;
334: $this->headerclass = $headerclass;
335: $this->defaultdata = $defaultdata;
336: $this->rowclass = $rowclass;
337: }
338:
339: public $visible = true;
340: public $sortable = false;
341: public $clickable = false;
342: public $fieldname = ""; // имя свойства выводимого итема
343: public $title = ""; //заголовок
344: public $headerclass = ""; // css класс заголовка
345: public $rowclass = ""; // css класс ячейки
346: public $defaultdata = " "; //значение по умолчанию
347:
348: }
349:
350: /**
351: * @todo вычисляемое поле ???
352: * @todo ссылку
353: * @todo изображение
354: *
355: */
356: