@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
at upstream/main 414 lines 11 kB view raw
1<?php 2 3final 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 Sort request parameter. 109 * @return array 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 (phutil_nonempty_string($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 { 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 approach. 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}