Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/view/control/AphrontTableView.php
12241 views
1
<?php
2
3
final class AphrontTableView extends AphrontView {
4
5
protected $data;
6
protected $headers;
7
protected $shortHeaders = array();
8
protected $rowClasses = array();
9
protected $columnClasses = array();
10
protected $cellClasses = array();
11
protected $zebraStripes = true;
12
protected $noDataString;
13
protected $className;
14
protected $notice;
15
protected $columnVisibility = array();
16
private $deviceVisibility = array();
17
18
private $columnWidths = array();
19
20
protected $sortURI;
21
protected $sortParam;
22
protected $sortSelected;
23
protected $sortReverse;
24
protected $sortValues = array();
25
private $deviceReadyTable;
26
27
private $rowDividers = array();
28
29
public function __construct(array $data) {
30
$this->data = $data;
31
}
32
33
public function setHeaders(array $headers) {
34
$this->headers = $headers;
35
return $this;
36
}
37
38
public function setColumnClasses(array $column_classes) {
39
$this->columnClasses = $column_classes;
40
return $this;
41
}
42
43
public function setRowClasses(array $row_classes) {
44
$this->rowClasses = $row_classes;
45
return $this;
46
}
47
48
public function setCellClasses(array $cell_classes) {
49
$this->cellClasses = $cell_classes;
50
return $this;
51
}
52
53
public function setColumnWidths(array $widths) {
54
$this->columnWidths = $widths;
55
return $this;
56
}
57
58
public function setRowDividers(array $dividers) {
59
$this->rowDividers = $dividers;
60
return $this;
61
}
62
63
public function setNoDataString($no_data_string) {
64
$this->noDataString = $no_data_string;
65
return $this;
66
}
67
68
public function setClassName($class_name) {
69
$this->className = $class_name;
70
return $this;
71
}
72
73
public function setNotice($notice) {
74
$this->notice = $notice;
75
return $this;
76
}
77
78
public function setZebraStripes($zebra_stripes) {
79
$this->zebraStripes = $zebra_stripes;
80
return $this;
81
}
82
83
public function setColumnVisibility(array $visibility) {
84
$this->columnVisibility = $visibility;
85
return $this;
86
}
87
88
public function setDeviceVisibility(array $device_visibility) {
89
$this->deviceVisibility = $device_visibility;
90
return $this;
91
}
92
93
public function setDeviceReadyTable($ready) {
94
$this->deviceReadyTable = $ready;
95
return $this;
96
}
97
98
public function setShortHeaders(array $short_headers) {
99
$this->shortHeaders = $short_headers;
100
return $this;
101
}
102
103
/**
104
* Parse a sorting parameter:
105
*
106
* list($sort, $reverse) = AphrontTableView::parseSortParam($sort_param);
107
*
108
* @param string Sort request parameter.
109
* @return pair Sort value, sort direction.
110
*/
111
public static function parseSort($sort) {
112
return array(ltrim($sort, '-'), preg_match('/^-/', $sort));
113
}
114
115
public function makeSortable(
116
PhutilURI $base_uri,
117
$param,
118
$selected,
119
$reverse,
120
array $sort_values) {
121
122
$this->sortURI = $base_uri;
123
$this->sortParam = $param;
124
$this->sortSelected = $selected;
125
$this->sortReverse = $reverse;
126
$this->sortValues = array_values($sort_values);
127
128
return $this;
129
}
130
131
public function render() {
132
require_celerity_resource('aphront-table-view-css');
133
134
$table = array();
135
136
$col_classes = array();
137
foreach ($this->columnClasses as $key => $class) {
138
if ($class !== null && strlen($class)) {
139
$col_classes[] = $class;
140
} else {
141
$col_classes[] = null;
142
}
143
}
144
145
$visibility = array_values($this->columnVisibility);
146
$device_visibility = array_values($this->deviceVisibility);
147
148
$column_widths = $this->columnWidths;
149
150
$headers = $this->headers;
151
$short_headers = $this->shortHeaders;
152
$sort_values = $this->sortValues;
153
if ($headers) {
154
while (count($headers) > count($visibility)) {
155
$visibility[] = true;
156
}
157
while (count($headers) > count($device_visibility)) {
158
$device_visibility[] = true;
159
}
160
while (count($headers) > count($short_headers)) {
161
$short_headers[] = null;
162
}
163
while (count($headers) > count($sort_values)) {
164
$sort_values[] = null;
165
}
166
167
$tr = array();
168
foreach ($headers as $col_num => $header) {
169
if (!$visibility[$col_num]) {
170
continue;
171
}
172
173
$classes = array();
174
175
if (!empty($col_classes[$col_num])) {
176
$classes[] = $col_classes[$col_num];
177
}
178
179
if (empty($device_visibility[$col_num])) {
180
$classes[] = 'aphront-table-view-nodevice';
181
}
182
183
if ($sort_values[$col_num] !== null) {
184
$classes[] = 'aphront-table-view-sortable';
185
186
$sort_value = $sort_values[$col_num];
187
$sort_glyph_class = 'aphront-table-down-sort';
188
if ($sort_value == $this->sortSelected) {
189
if ($this->sortReverse) {
190
$sort_glyph_class = 'aphront-table-up-sort';
191
} else if (!$this->sortReverse) {
192
$sort_value = '-'.$sort_value;
193
}
194
$classes[] = 'aphront-table-view-sortable-selected';
195
}
196
197
$sort_glyph = phutil_tag(
198
'span',
199
array(
200
'class' => $sort_glyph_class,
201
),
202
'');
203
204
$header = phutil_tag(
205
'a',
206
array(
207
'href' => $this->sortURI->alter($this->sortParam, $sort_value),
208
'class' => 'aphront-table-view-sort-link',
209
),
210
array(
211
$header,
212
' ',
213
$sort_glyph,
214
));
215
}
216
217
if ($classes) {
218
$class = implode(' ', $classes);
219
} else {
220
$class = null;
221
}
222
223
if ($short_headers[$col_num] !== null) {
224
$header_nodevice = phutil_tag(
225
'span',
226
array(
227
'class' => 'aphront-table-view-nodevice',
228
),
229
$header);
230
$header_device = phutil_tag(
231
'span',
232
array(
233
'class' => 'aphront-table-view-device',
234
),
235
$short_headers[$col_num]);
236
237
$header = hsprintf('%s %s', $header_nodevice, $header_device);
238
}
239
240
$style = null;
241
if (isset($column_widths[$col_num])) {
242
$style = 'width: '.$column_widths[$col_num].';';
243
}
244
245
$tr[] = phutil_tag(
246
'th',
247
array(
248
'class' => $class,
249
'style' => $style,
250
),
251
$header);
252
}
253
$table[] = phutil_tag('tr', array(), $tr);
254
}
255
256
foreach ($col_classes as $key => $value) {
257
258
if (isset($sort_values[$key]) &&
259
($sort_values[$key] == $this->sortSelected)) {
260
$value = trim($value.' sorted-column');
261
}
262
263
if ($value !== null) {
264
$col_classes[$key] = $value;
265
}
266
}
267
268
$dividers = $this->rowDividers;
269
270
$data = $this->data;
271
if ($data) {
272
$row_num = 0;
273
$row_idx = 0;
274
foreach ($data as $row) {
275
$is_divider = !empty($dividers[$row_num]);
276
277
$row_size = count($row);
278
while (count($row) > count($col_classes)) {
279
$col_classes[] = null;
280
}
281
while (count($row) > count($visibility)) {
282
$visibility[] = true;
283
}
284
while (count($row) > count($device_visibility)) {
285
$device_visibility[] = true;
286
}
287
$tr = array();
288
// NOTE: Use of a separate column counter is to allow this to work
289
// correctly if the row data has string or non-sequential keys.
290
$col_num = 0;
291
foreach ($row as $value) {
292
if (!$visibility[$col_num]) {
293
++$col_num;
294
continue;
295
}
296
$class = $col_classes[$col_num];
297
if (empty($device_visibility[$col_num])) {
298
$class = trim($class.' aphront-table-view-nodevice');
299
}
300
if (!empty($this->cellClasses[$row_num][$col_num])) {
301
$class = trim($class.' '.$this->cellClasses[$row_num][$col_num]);
302
}
303
304
if ($is_divider) {
305
$tr[] = phutil_tag(
306
'td',
307
array(
308
'class' => 'row-divider',
309
'colspan' => count($visibility),
310
),
311
$value);
312
$row_idx = -1;
313
break;
314
}
315
316
$tr[] = phutil_tag(
317
'td',
318
array(
319
'class' => $class,
320
),
321
$value);
322
++$col_num;
323
}
324
325
$class = idx($this->rowClasses, $row_num);
326
if ($this->zebraStripes && ($row_idx % 2)) {
327
if ($class !== null) {
328
$class = 'alt alt-'.$class;
329
} else {
330
$class = 'alt';
331
}
332
}
333
334
$table[] = phutil_tag('tr', array('class' => $class), $tr);
335
++$row_num;
336
++$row_idx;
337
}
338
} else {
339
$colspan = max(count(array_filter($visibility)), 1);
340
$table[] = phutil_tag(
341
'tr',
342
array('class' => 'no-data'),
343
phutil_tag(
344
'td',
345
array('colspan' => $colspan),
346
coalesce($this->noDataString, pht('No data available.'))));
347
}
348
349
$classes = array();
350
$classes[] = 'aphront-table-view';
351
if ($this->className !== null) {
352
$classes[] = $this->className;
353
}
354
355
if ($this->deviceReadyTable) {
356
$classes[] = 'aphront-table-view-device-ready';
357
}
358
359
if ($this->columnWidths) {
360
$classes[] = 'aphront-table-view-fixed';
361
}
362
363
$notice = null;
364
if ($this->notice) {
365
$notice = phutil_tag(
366
'div',
367
array(
368
'class' => 'aphront-table-notice',
369
),
370
$this->notice);
371
}
372
373
$html = phutil_tag(
374
'table',
375
array(
376
'class' => implode(' ', $classes),
377
),
378
$table);
379
380
return phutil_tag_div(
381
'aphront-table-wrap',
382
array(
383
$notice,
384
$html,
385
));
386
}
387
388
public static function renderSingleDisplayLine($line) {
389
390
// TODO: Is there a cleaner way to do this? We use a relative div with
391
// overflow hidden to provide the bounds, and an absolute span with
392
// white-space: pre to prevent wrapping. We need to append a character
393
// (&nbsp; -- nonbreaking space) afterward to give the bounds div height
394
// (alternatively, we could hard-code the line height). This is gross but
395
// it's not clear that there's a better appraoch.
396
397
return phutil_tag(
398
'div',
399
array(
400
'class' => 'single-display-line-bounds',
401
),
402
array(
403
phutil_tag(
404
'span',
405
array(
406
'class' => 'single-display-line-content',
407
),
408
$line),
409
"\xC2\xA0",
410
));
411
}
412
413
414
}
415
416