data = $data; } public function setHeaders(array $headers) { $this->headers = $headers; return $this; } public function setColumnClasses(array $column_classes) { $this->columnClasses = $column_classes; return $this; } public function setRowClasses(array $row_classes) { $this->rowClasses = $row_classes; return $this; } public function setCellClasses(array $cell_classes) { $this->cellClasses = $cell_classes; return $this; } public function setNoDataString($no_data_string) { $this->noDataString = $no_data_string; return $this; } public function setClassName($class_name) { $this->className = $class_name; return $this; } public function setZebraStripes($zebra_stripes) { $this->zebraStripes = $zebra_stripes; return $this; } public function setColumnVisibility(array $visibility) { $this->columnVisibility = $visibility; return $this; } public function setDeviceVisibility(array $device_visibility) { $this->deviceVisibility = $device_visibility; return $this; } public function setDeviceReadyTable($ready) { $this->deviceReadyTable = $ready; return $this; } public function setShortHeaders(array $short_headers) { $this->shortHeaders = $short_headers; return $this; } /** * Parse a sorting parameter: * * list($sort, $reverse) = AphrontTableView::parseSortParam($sort_param); * * @param string Sort request parameter. * @return pair Sort value, sort direction. */ public static function parseSort($sort) { return array(ltrim($sort, '-'), preg_match('/^-/', $sort)); } public function makeSortable( PhutilURI $base_uri, $param, $selected, $reverse, array $sort_values) { $this->sortURI = $base_uri; $this->sortParam = $param; $this->sortSelected = $selected; $this->sortReverse = $reverse; $this->sortValues = array_values($sort_values); return $this; } public function render() { require_celerity_resource('aphront-table-view-css'); $table_class = $this->className; if ($this->deviceReadyTable) { $table_class .= ' aphront-table-view-device-ready'; } if ($table_class !== null) { $table_class = ' class="aphront-table-view '.$table_class.'"'; } else { $table_class = ' class="aphront-table-view"'; } $table = array(''); $col_classes = array(); foreach ($this->columnClasses as $key => $class) { if (strlen($class)) { $col_classes[] = $class; } else { $col_classes[] = null; } } $visibility = array_values($this->columnVisibility); $device_visibility = array_values($this->deviceVisibility); $headers = $this->headers; $short_headers = $this->shortHeaders; $sort_values = $this->sortValues; if ($headers) { while (count($headers) > count($visibility)) { $visibility[] = true; } while (count($headers) > count($device_visibility)) { $device_visibility[] = true; } while (count($headers) > count($short_headers)) { $short_headers[] = null; } while (count($headers) > count($sort_values)) { $sort_values[] = null; } $table[] = ''; foreach ($headers as $col_num => $header) { if (!$visibility[$col_num]) { continue; } $classes = array(); if (!empty($col_classes[$col_num])) { $classes[] = $col_classes[$col_num]; } if (empty($device_visiblity[$col_num])) { $classes[] = 'aphront-table-nodevice'; } if ($sort_values[$col_num] !== null) { $classes[] = 'aphront-table-view-sortable'; $sort_value = $sort_values[$col_num]; $sort_glyph_class = 'aphront-table-down-sort'; if ($sort_value == $this->sortSelected) { if ($this->sortReverse) { $sort_glyph_class = 'aphront-table-up-sort'; } else if (!$this->sortReverse) { $sort_value = '-'.$sort_value; } $classes[] = 'aphront-table-view-sortable-selected'; } $sort_glyph = phutil_tag( 'span', array( 'class' => $sort_glyph_class, ), ''); $header = phutil_render_tag( 'a', array( 'href' => $this->sortURI->alter($this->sortParam, $sort_value), 'class' => 'aphront-table-view-sort-link', ), $header.' '.$sort_glyph); } if ($classes) { $class = ' class="'.implode(' ', $classes).'"'; } else { $class = null; } if ($short_headers[$col_num] !== null) { $header_nodevice = phutil_render_tag( 'span', array( 'class' => 'aphront-table-view-nodevice', ), $header); $header_device = phutil_tag( 'span', array( 'class' => 'aphront-table-view-device', ), $short_headers[$col_num]); $header = $header_nodevice.$header_device; } $table[] = ''.$header.''; } $table[] = ''; } foreach ($col_classes as $key => $value) { if (($sort_values[$key] !== null) && ($sort_values[$key] == $this->sortSelected)) { $value = trim($value.' sorted-column'); } if ($value !== null) { $col_classes[$key] = $value; } } $data = $this->data; if ($data) { $row_num = 0; foreach ($data as $row) { while (count($row) > count($col_classes)) { $col_classes[] = null; } while (count($row) > count($visibility)) { $visibility[] = true; } $class = idx($this->rowClasses, $row_num); if ($this->zebraStripes && ($row_num % 2)) { if ($class !== null) { $class = 'alt alt-'.$class; } else { $class = 'alt'; } } if ($class !== null) { $class = ' class="'.$class.'"'; } $table[] = ''; // NOTE: Use of a separate column counter is to allow this to work // correctly if the row data has string or non-sequential keys. $col_num = 0; foreach ($row as $value) { if (!$visibility[$col_num]) { ++$col_num; continue; } $class = $col_classes[$col_num]; if (!empty($this->cellClasses[$row_num][$col_num])) { $class = trim($class.' '.$this->cellClasses[$row_num][$col_num]); } if ($class !== null) { $table[] = ''; } else { $table[] = ''; } $table[] = $value.''; ++$col_num; } ++$row_num; } } else { $colspan = max(count(array_filter($visibility)), 1); $table[] = ''. coalesce($this->noDataString, 'No data available.'). ''; } $table[] = ''; $html = implode('', $table); return '
'.$html.'
'; } public static function renderSingleDisplayLine($line) { // TODO: Is there a cleaner way to do this? We use a relative div with // overflow hidden to provide the bounds, and an absolute span with // white-space: pre to prevent wrapping. We need to append a character // (  -- nonbreaking space) afterward to give the bounds div height // (alternatively, we could hard-code the line height). This is gross but // it's not clear that there's a better appraoch. return phutil_tag( 'div', array( 'class' => 'single-display-line-bounds', ), phutil_render_tag( 'span', array( 'class' => 'single-display-line-content', ), $line).' '); } }