1 <?php
2
3 /*
4 * This file is part of the Icybee package.
5 *
6 * (c) Olivier Laviale <olivier.laviale@gmail.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Brickrouge;
13
14 /**
15 * A listview element.
16 */
17 class ListView extends Element
18 {
19 const COLUMNS = '#listview-columns';
20 const ENTRIES = '#listview-entries';
21
22 /**
23 * Columns use to display the data of the records.
24 *
25 * @var array[string]ListViewColumn
26 */
27 protected $columns;
28
29 public function __construct(array $attributes=array())
30 {
31 unset($this->columns);
32
33 parent::__construct('div', $attributes);
34 }
35
36 /**
37 * Adds the following class names:
38 *
39 * - `listview`
40 */
41 protected function alter_class_names(array $class_names)
42 {
43 return parent::alter_class_names($class_names) + array
44 (
45 'listview' => true
46 );
47 }
48
49 /**
50 * Returns the columns of the listview.
51 *
52 * @return \Icybee\ListView\Column
53 */
54 protected function get_columns()
55 {
56 $columns = $this[self::COLUMNS];
57 $columns = $this->resolve_columns($columns);
58
59 return $columns;
60 }
61
62 protected function resolve_columns(array $columns)
63 {
64 $resolved_columns = $columns;
65
66 foreach ($resolved_columns as $id => &$column)
67 {
68 if (is_string($column))
69 {
70 $construct = $column;
71 $column = new $construct($this, $id, array());
72 }
73 }
74
75 return $resolved_columns;
76 }
77
78 /**
79 * Returns the entries to display.
80 *
81 * @return array[]mixed
82 */
83 protected function get_entries()
84 {
85 return $this[self::ENTRIES];
86 }
87
88 protected function render_inner_html()
89 {
90 $head = $this->render_head();
91 $foot = $this->render_foot();
92 $body = $this->render_body();
93
94 return <<<EOT
95 <table>
96 $head
97 $foot
98 $body
99 </table>
100 EOT;
101 }
102
103 protected function render_head()
104 {
105 $html = '';
106
107 foreach ($this->columns as $column)
108 {
109 $th = new Element('th');
110 $th[Element::INNER_HTML] = '<div>' . $column->render_header($th) . '</div>';
111
112 $html .= $th;
113 }
114
115 return <<<EOT
116 <thead>
117 $html
118 </thead>
119 EOT;
120 }
121
122 protected function render_foot()
123 {
124
125 }
126
127 /**
128 * Renders body.
129 *
130 * @return Element An {@link Element} instance representing a `tbody` element. Its children
131 * are the rendered rows returned by {@link render_rows()}.
132 */
133 protected function render_body()
134 {
135 $rendered_cells = $this->render_cells($this->columns);
136 $rendered_cells = $this->alter_rendered_cells($rendered_cells);
137 $rows = $this->columns_to_rows($rendered_cells);
138 $rendered_rows = $this->render_rows($rows);
139
140 return new Element('tbody', array(Element::CHILDREN => $rendered_rows));
141 }
142
143 /**
144 * Renders the cells of the columns.
145 *
146 * The method returns an array with the following layout:
147 *
148 * [<column_id>][] => <cell_content>
149 *
150 * @param array $columns The columns to render.
151 *
152 * @return array[string]mixed
153 */
154 protected function render_cells(array $columns)
155 {
156 $rendered_cells = array();
157
158 foreach ($columns as $id => $column)
159 {
160 foreach ($this->entries as $entry)
161 {
162 try
163 {
164 $content = (string) $column->render_cell($entry);
165 }
166 catch (\Exception $e)
167 {
168 $content = render_exception($e);
169 }
170
171 $rendered_cells[$id][] = $content;
172 }
173 }
174
175 return $rendered_cells;
176 }
177
178 /**
179 * Alters the rendering cells.
180 *
181 * Note: The method returns the rendered cells as is.
182 *
183 * @param array $rendered_cells
184 *
185 * @return array[string]mixed
186 */
187 protected function alter_rendered_cells(array $rendered_cells)
188 {
189 return $rendered_cells;
190 }
191
192 /**
193 * Convert rendered cells to rows.
194 *
195 * @param array $rendered_cells
196 *
197 * @return array[]array
198 */
199 protected function columns_to_rows(array $rendered_cells)
200 {
201 $rows = array();
202
203 foreach ($rendered_cells as $column_id => $cells)
204 {
205 foreach ($cells as $i => $cell)
206 {
207 $rows[$i][$column_id] = $cell;
208 }
209 }
210
211 return $rows;
212 }
213
214 /**
215 * Renders the specified rows.
216 *
217 * The rows are rendered as an array of {@link Element} instances representing `TR` elements.
218 *
219 * @param array $rows
220 *
221 * @return array[]Element
222 */
223 protected function render_rows(array $rows)
224 {
225 $columns = $this->columns;
226 $rendered_rows = array();
227
228 foreach ($rows as $i => $cells)
229 {
230 $html = '';
231
232 foreach ($cells as $column_id => $cell)
233 {
234 $cell = $cell ?: ' ';
235 $class = trim('cell--' . normalize($column_id) . ' ' . $columns[$column_id]->class);
236
237 $html .= <<<EOT
238 <td class="{$class}">$cell</td>
239 EOT;
240 }
241
242 $rendered_rows[] = new Element('tr', array(Element::INNER_HTML => $html));
243 }
244
245 return $rendered_rows;
246 }
247 }
248
249 /**
250 * Representation of a listview column.
251 */
252 class ListViewColumn extends \ICanBoogie\Object
253 {
254 protected $id;
255 protected $options;
256 protected $listview;
257
258 public function __construct(ListView $listview, $id, array $options=array())
259 {
260 $this->id = $id;
261 $this->listview = $listview;
262 $this->options = $options + array
263 (
264 'class' => null,
265 'title' => null
266 );
267 }
268
269 protected function get_class()
270 {
271 return $this->options['class'];
272 }
273
274 public function render_cell($entry)
275 {
276 return $entry->{ $this->id };
277 }
278
279 public function render_header(Element $container)
280 {
281 $container['class'] .= 'header--' . normalize($this->id) . ' ' . $this->class;
282
283 return $this->options['title'];
284 }
285 }