2011-01-16 22:51:39 +01:00
|
|
|
<?php
|
|
|
|
|
2012-03-14 00:21:04 +01:00
|
|
|
final class AphrontTableView extends AphrontView {
|
2011-01-16 22:51:39 +01:00
|
|
|
|
|
|
|
protected $data;
|
|
|
|
protected $headers;
|
2012-12-18 00:16:44 +01:00
|
|
|
protected $shortHeaders;
|
2011-01-16 22:51:39 +01:00
|
|
|
protected $rowClasses = array();
|
|
|
|
protected $columnClasses = array();
|
2012-08-20 23:05:46 +02:00
|
|
|
protected $cellClasses = array();
|
2011-01-16 22:51:39 +01:00
|
|
|
protected $zebraStripes = true;
|
|
|
|
protected $noDataString;
|
|
|
|
protected $className;
|
[Redesign] Put all ApplicationSearch results in an ObjectBox
Summary:
Ref T8099. In most cases we return either an ObjectList or AphrontTable, and can pretty up the UI in ApplicationSearch. There are a few edge cases, like PeopleUserLog, that can be cleanup up individually in the future, but look fine for now.
Also added 'setNotice' for AphrontTable for a few cases where we want to convey addtional information.
TODO: Seems we always pass a Pager Object, which tries to get displayed, I'll redesign that interaction in the future, probably by passing the Pager to the ObjectBox
Test Plan: Went throught most/all ApplicationSearch panels I could find, even edge cases look better.
Reviewers: btrahan, epriestley
Reviewed By: epriestley
Subscribers: Korvin, epriestley
Maniphest Tasks: T8099
Differential Revision: https://secure.phabricator.com/D12989
2015-05-24 18:13:58 +02:00
|
|
|
protected $notice;
|
2011-05-17 19:59:26 +02:00
|
|
|
protected $columnVisibility = array();
|
2012-12-18 00:16:44 +01:00
|
|
|
private $deviceVisibility = array();
|
2011-01-16 22:51:39 +01:00
|
|
|
|
2016-02-29 15:26:01 +01:00
|
|
|
private $columnWidths = array();
|
|
|
|
|
2012-03-20 03:48:22 +01:00
|
|
|
protected $sortURI;
|
|
|
|
protected $sortParam;
|
|
|
|
protected $sortSelected;
|
|
|
|
protected $sortReverse;
|
|
|
|
protected $sortValues;
|
2012-12-18 00:16:44 +01:00
|
|
|
private $deviceReadyTable;
|
2012-03-20 03:48:22 +01:00
|
|
|
|
2011-01-16 22:51:39 +01:00
|
|
|
public function __construct(array $data) {
|
|
|
|
$this->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;
|
|
|
|
}
|
|
|
|
|
2012-08-20 23:05:46 +02:00
|
|
|
public function setCellClasses(array $cell_classes) {
|
|
|
|
$this->cellClasses = $cell_classes;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2016-02-29 15:26:01 +01:00
|
|
|
public function setColumnWidths(array $widths) {
|
|
|
|
$this->columnWidths = $widths;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-01-16 22:51:39 +01:00
|
|
|
public function setNoDataString($no_data_string) {
|
|
|
|
$this->noDataString = $no_data_string;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setClassName($class_name) {
|
|
|
|
$this->className = $class_name;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
[Redesign] Put all ApplicationSearch results in an ObjectBox
Summary:
Ref T8099. In most cases we return either an ObjectList or AphrontTable, and can pretty up the UI in ApplicationSearch. There are a few edge cases, like PeopleUserLog, that can be cleanup up individually in the future, but look fine for now.
Also added 'setNotice' for AphrontTable for a few cases where we want to convey addtional information.
TODO: Seems we always pass a Pager Object, which tries to get displayed, I'll redesign that interaction in the future, probably by passing the Pager to the ObjectBox
Test Plan: Went throught most/all ApplicationSearch panels I could find, even edge cases look better.
Reviewers: btrahan, epriestley
Reviewed By: epriestley
Subscribers: Korvin, epriestley
Maniphest Tasks: T8099
Differential Revision: https://secure.phabricator.com/D12989
2015-05-24 18:13:58 +02:00
|
|
|
public function setNotice($notice) {
|
|
|
|
$this->notice = $notice;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-01-16 22:51:39 +01:00
|
|
|
public function setZebraStripes($zebra_stripes) {
|
|
|
|
$this->zebraStripes = $zebra_stripes;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2011-05-17 19:59:26 +02:00
|
|
|
public function setColumnVisibility(array $visibility) {
|
|
|
|
$this->columnVisibility = $visibility;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2012-12-18 00:16:44 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-03-20 03:48:22 +01:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2011-01-16 22:51:39 +01:00
|
|
|
public function render() {
|
2011-01-25 20:31:40 +01:00
|
|
|
require_celerity_resource('aphront-table-view-css');
|
|
|
|
|
2013-02-13 23:50:15 +01:00
|
|
|
$table = array();
|
2011-01-16 22:51:39 +01:00
|
|
|
|
|
|
|
$col_classes = array();
|
|
|
|
foreach ($this->columnClasses as $key => $class) {
|
|
|
|
if (strlen($class)) {
|
2012-03-20 03:48:22 +01:00
|
|
|
$col_classes[] = $class;
|
2011-01-16 22:51:39 +01:00
|
|
|
} else {
|
|
|
|
$col_classes[] = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-05-17 19:59:26 +02:00
|
|
|
$visibility = array_values($this->columnVisibility);
|
2012-12-18 00:16:44 +01:00
|
|
|
$device_visibility = array_values($this->deviceVisibility);
|
2015-05-09 19:05:21 +02:00
|
|
|
|
2016-02-29 15:26:01 +01:00
|
|
|
$column_widths = $this->columnWidths;
|
|
|
|
|
2011-01-16 22:51:39 +01:00
|
|
|
$headers = $this->headers;
|
2012-12-18 00:16:44 +01:00
|
|
|
$short_headers = $this->shortHeaders;
|
2012-03-20 03:48:22 +01:00
|
|
|
$sort_values = $this->sortValues;
|
2011-01-16 22:51:39 +01:00
|
|
|
if ($headers) {
|
2011-05-17 19:59:26 +02:00
|
|
|
while (count($headers) > count($visibility)) {
|
|
|
|
$visibility[] = true;
|
|
|
|
}
|
2012-12-18 00:16:44 +01:00
|
|
|
while (count($headers) > count($device_visibility)) {
|
|
|
|
$device_visibility[] = true;
|
|
|
|
}
|
|
|
|
while (count($headers) > count($short_headers)) {
|
|
|
|
$short_headers[] = null;
|
|
|
|
}
|
2012-03-20 03:48:22 +01:00
|
|
|
while (count($headers) > count($sort_values)) {
|
|
|
|
$sort_values[] = null;
|
|
|
|
}
|
2013-02-13 23:50:15 +01:00
|
|
|
|
|
|
|
$tr = array();
|
2011-01-16 22:51:39 +01:00
|
|
|
foreach ($headers as $col_num => $header) {
|
2011-05-17 19:59:26 +02:00
|
|
|
if (!$visibility[$col_num]) {
|
|
|
|
continue;
|
|
|
|
}
|
2012-03-20 03:48:22 +01:00
|
|
|
|
|
|
|
$classes = array();
|
|
|
|
|
|
|
|
if (!empty($col_classes[$col_num])) {
|
|
|
|
$classes[] = $col_classes[$col_num];
|
|
|
|
}
|
|
|
|
|
2014-03-12 19:39:43 +01:00
|
|
|
if (empty($device_visibility[$col_num])) {
|
|
|
|
$classes[] = 'aphront-table-view-nodevice';
|
2012-12-18 00:16:44 +01:00
|
|
|
}
|
|
|
|
|
2012-03-20 03:48:22 +01:00
|
|
|
if ($sort_values[$col_num] !== null) {
|
|
|
|
$classes[] = 'aphront-table-view-sortable';
|
|
|
|
|
|
|
|
$sort_value = $sort_values[$col_num];
|
2013-01-11 20:24:35 +01:00
|
|
|
$sort_glyph_class = 'aphront-table-down-sort';
|
2012-03-20 03:48:22 +01:00
|
|
|
if ($sort_value == $this->sortSelected) {
|
|
|
|
if ($this->sortReverse) {
|
2013-01-11 20:24:35 +01:00
|
|
|
$sort_glyph_class = 'aphront-table-up-sort';
|
2012-03-20 03:48:22 +01:00
|
|
|
} else if (!$this->sortReverse) {
|
|
|
|
$sort_value = '-'.$sort_value;
|
|
|
|
}
|
|
|
|
$classes[] = 'aphront-table-view-sortable-selected';
|
|
|
|
}
|
|
|
|
|
2013-01-18 03:57:09 +01:00
|
|
|
$sort_glyph = phutil_tag(
|
2012-03-20 03:48:22 +01:00
|
|
|
'span',
|
|
|
|
array(
|
2013-01-11 20:24:35 +01:00
|
|
|
'class' => $sort_glyph_class,
|
2012-03-20 03:48:22 +01:00
|
|
|
),
|
2013-01-11 20:24:35 +01:00
|
|
|
'');
|
2012-03-20 03:48:22 +01:00
|
|
|
|
2013-01-25 14:50:50 +01:00
|
|
|
$header = phutil_tag(
|
2012-03-20 03:48:22 +01:00
|
|
|
'a',
|
|
|
|
array(
|
|
|
|
'href' => $this->sortURI->alter($this->sortParam, $sort_value),
|
|
|
|
'class' => 'aphront-table-view-sort-link',
|
|
|
|
),
|
2013-01-25 14:50:50 +01:00
|
|
|
array(
|
|
|
|
$header,
|
|
|
|
' ',
|
|
|
|
$sort_glyph,
|
|
|
|
));
|
2012-03-20 03:48:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($classes) {
|
2013-02-13 23:50:15 +01:00
|
|
|
$class = implode(' ', $classes);
|
2012-03-20 03:48:22 +01:00
|
|
|
} else {
|
|
|
|
$class = null;
|
|
|
|
}
|
|
|
|
|
2012-12-18 00:16:44 +01:00
|
|
|
if ($short_headers[$col_num] !== null) {
|
2013-01-25 14:50:50 +01:00
|
|
|
$header_nodevice = phutil_tag(
|
2012-12-18 00:16:44 +01:00
|
|
|
'span',
|
|
|
|
array(
|
|
|
|
'class' => 'aphront-table-view-nodevice',
|
|
|
|
),
|
|
|
|
$header);
|
2013-01-18 03:43:35 +01:00
|
|
|
$header_device = phutil_tag(
|
2012-12-18 00:16:44 +01:00
|
|
|
'span',
|
|
|
|
array(
|
|
|
|
'class' => 'aphront-table-view-device',
|
|
|
|
),
|
2013-01-18 03:43:35 +01:00
|
|
|
$short_headers[$col_num]);
|
2012-12-18 00:16:44 +01:00
|
|
|
|
2013-02-13 23:50:15 +01:00
|
|
|
$header = hsprintf('%s %s', $header_nodevice, $header_device);
|
2012-12-18 00:16:44 +01:00
|
|
|
}
|
|
|
|
|
2016-02-29 15:26:01 +01:00
|
|
|
$style = null;
|
|
|
|
if (isset($column_widths[$col_num])) {
|
|
|
|
$style = 'width: '.$column_widths[$col_num].';';
|
|
|
|
}
|
|
|
|
|
|
|
|
$tr[] = phutil_tag(
|
|
|
|
'th',
|
|
|
|
array(
|
|
|
|
'class' => $class,
|
|
|
|
'style' => $style,
|
|
|
|
),
|
|
|
|
$header);
|
2011-01-16 22:51:39 +01:00
|
|
|
}
|
2013-02-13 23:50:15 +01:00
|
|
|
$table[] = phutil_tag('tr', array(), $tr);
|
2011-01-16 22:51:39 +01:00
|
|
|
}
|
|
|
|
|
2012-03-20 03:48:22 +01:00
|
|
|
foreach ($col_classes as $key => $value) {
|
|
|
|
|
|
|
|
if (($sort_values[$key] !== null) &&
|
|
|
|
($sort_values[$key] == $this->sortSelected)) {
|
|
|
|
$value = trim($value.' sorted-column');
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($value !== null) {
|
2012-08-20 23:05:46 +02:00
|
|
|
$col_classes[$key] = $value;
|
2012-03-20 03:48:22 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-01-16 22:51:39 +01:00
|
|
|
$data = $this->data;
|
|
|
|
if ($data) {
|
|
|
|
$row_num = 0;
|
|
|
|
foreach ($data as $row) {
|
2015-05-09 19:05:21 +02:00
|
|
|
$row_size = count($row);
|
2011-01-16 22:51:39 +01:00
|
|
|
while (count($row) > count($col_classes)) {
|
|
|
|
$col_classes[] = null;
|
|
|
|
}
|
2011-05-17 19:59:26 +02:00
|
|
|
while (count($row) > count($visibility)) {
|
|
|
|
$visibility[] = true;
|
|
|
|
}
|
2015-05-09 19:05:21 +02:00
|
|
|
while (count($row) > count($device_visibility)) {
|
|
|
|
$device_visibility[] = true;
|
|
|
|
}
|
2013-02-13 23:50:15 +01:00
|
|
|
$tr = array();
|
2011-05-17 19:59:26 +02:00
|
|
|
// NOTE: Use of a separate column counter is to allow this to work
|
|
|
|
// correctly if the row data has string or non-sequential keys.
|
2011-01-16 22:51:39 +01:00
|
|
|
$col_num = 0;
|
|
|
|
foreach ($row as $value) {
|
2011-05-17 19:59:26 +02:00
|
|
|
if (!$visibility[$col_num]) {
|
|
|
|
++$col_num;
|
|
|
|
continue;
|
|
|
|
}
|
2011-01-16 22:51:39 +01:00
|
|
|
$class = $col_classes[$col_num];
|
2014-03-12 19:39:43 +01:00
|
|
|
if (empty($device_visibility[$col_num])) {
|
|
|
|
$class = trim($class.' aphront-table-view-nodevice');
|
|
|
|
}
|
2012-08-20 23:05:46 +02:00
|
|
|
if (!empty($this->cellClasses[$row_num][$col_num])) {
|
|
|
|
$class = trim($class.' '.$this->cellClasses[$row_num][$col_num]);
|
|
|
|
}
|
2016-02-29 15:26:01 +01:00
|
|
|
|
|
|
|
$tr[] = phutil_tag(
|
|
|
|
'td',
|
|
|
|
array(
|
|
|
|
'class' => $class,
|
|
|
|
),
|
|
|
|
$value);
|
2013-02-13 23:50:15 +01:00
|
|
|
++$col_num;
|
|
|
|
}
|
|
|
|
|
|
|
|
$class = idx($this->rowClasses, $row_num);
|
|
|
|
if ($this->zebraStripes && ($row_num % 2)) {
|
2011-01-16 22:51:39 +01:00
|
|
|
if ($class !== null) {
|
2013-02-13 23:50:15 +01:00
|
|
|
$class = 'alt alt-'.$class;
|
2011-01-16 22:51:39 +01:00
|
|
|
} else {
|
2013-02-13 23:50:15 +01:00
|
|
|
$class = 'alt';
|
2011-01-16 22:51:39 +01:00
|
|
|
}
|
|
|
|
}
|
2013-02-13 23:50:15 +01:00
|
|
|
|
|
|
|
$table[] = phutil_tag('tr', array('class' => $class), $tr);
|
2011-01-16 22:51:39 +01:00
|
|
|
++$row_num;
|
|
|
|
}
|
|
|
|
} else {
|
General Herald refactoring pass
Summary:
**Who can delete global rules?**: I discussed this with @jungejason. The current behavior is that the rule author or any administrator can delete a global rule, but this
isn't consistent with who can edit a rule (anyone) and doesn't really make much sense (it's an artifact of the global/personal split). I proposed that anyone can delete a
rule but we don't actually delete them, and log the deletion. However, when it came time to actually write the code for this I backed off a bit and continued actually
deleting the rules -- I think this does a reasonable job of balancing accountability with complexity. So the new impelmentation is:
- Personal rules can be deleted only by their owners.
- Global rules can be deleted by any user.
- All deletes are logged.
- Logs are more detailed.
- All logged actions can be viewed in aggregate.
**Minor Cleanup**
- Merged `HomeController` and `AllController`.
- Moved most queries to Query classes.
- Use AphrontFormSelectControl::renderSelectTag() where appropriate (this is a fairly recent addition).
- Use an AphrontErrorView to render the dry run notice (this didn't exist when I ported).
- Reenable some transaction code (this works again now).
- Removed the ability for admins to change rule authors (this was a little buggy, messy, and doesn't make tons of sense after the personal/global rule split).
- Rules which depend on other rules now display the right options (all global rules, all your personal rules for personal rules).
- Fix a bug in AphrontTableView where the "no data" cell would be rendered too wide if some columns are not visible.
- Allow selectFilter() in AphrontNavFilterView to be called without a 'default' argument.
Test Plan:
- Browsed, created, edited, deleted personal and gules.
- Verified generated logs.
- Did some dry runs.
- Verified transcript list and transcript details.
- Created/edited all/any rules; created/edited once/every time rules.
- Filtered admin views by users.
Reviewers: jungejason, btrahan
Reviewed By: btrahan
CC: aran, epriestley
Differential Revision: https://secure.phabricator.com/D2040
2012-03-30 19:49:55 +02:00
|
|
|
$colspan = max(count(array_filter($visibility)), 1);
|
2013-11-09 19:48:19 +01:00
|
|
|
$table[] = phutil_tag(
|
|
|
|
'tr',
|
|
|
|
array('class' => 'no-data'),
|
|
|
|
phutil_tag(
|
|
|
|
'td',
|
|
|
|
array('colspan' => $colspan),
|
|
|
|
coalesce($this->noDataString, pht('No data available.'))));
|
2013-02-13 23:50:15 +01:00
|
|
|
}
|
|
|
|
|
2015-03-26 21:16:09 +01:00
|
|
|
$classes = array();
|
|
|
|
$classes[] = 'aphront-table-view';
|
2013-02-13 23:50:15 +01:00
|
|
|
if ($this->className !== null) {
|
2015-03-26 21:16:09 +01:00
|
|
|
$classes[] = $this->className;
|
|
|
|
}
|
2016-02-29 15:26:01 +01:00
|
|
|
|
2013-02-13 23:50:15 +01:00
|
|
|
if ($this->deviceReadyTable) {
|
2015-03-26 21:16:09 +01:00
|
|
|
$classes[] = 'aphront-table-view-device-ready';
|
2013-02-13 23:50:15 +01:00
|
|
|
}
|
|
|
|
|
2016-02-29 15:26:01 +01:00
|
|
|
if ($this->columnWidths) {
|
|
|
|
$classes[] = 'aphront-table-view-fixed';
|
|
|
|
}
|
|
|
|
|
2016-02-29 18:53:46 +01:00
|
|
|
$notice = null;
|
|
|
|
if ($this->notice) {
|
|
|
|
$notice = phutil_tag(
|
|
|
|
'div',
|
|
|
|
array(
|
|
|
|
'class' => 'aphront-table-notice',
|
|
|
|
),
|
|
|
|
$this->notice);
|
|
|
|
}
|
|
|
|
|
2015-03-26 21:16:09 +01:00
|
|
|
$html = phutil_tag(
|
|
|
|
'table',
|
|
|
|
array(
|
2015-04-05 14:29:39 +02:00
|
|
|
'class' => implode(' ', $classes),
|
|
|
|
),
|
2015-03-26 21:16:09 +01:00
|
|
|
$table);
|
2016-02-29 18:53:46 +01:00
|
|
|
|
|
|
|
return phutil_tag_div(
|
|
|
|
'aphront-table-wrap',
|
|
|
|
array(
|
|
|
|
$notice,
|
|
|
|
$html,
|
|
|
|
));
|
2011-01-16 22:51:39 +01:00
|
|
|
}
|
2011-07-31 17:38:06 +02:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
2013-01-18 09:32:58 +01:00
|
|
|
return phutil_tag(
|
2011-07-31 17:38:06 +02:00
|
|
|
'div',
|
|
|
|
array(
|
|
|
|
'class' => 'single-display-line-bounds',
|
|
|
|
),
|
2013-01-29 03:41:43 +01:00
|
|
|
array(
|
|
|
|
phutil_tag(
|
|
|
|
'span',
|
|
|
|
array(
|
|
|
|
'class' => 'single-display-line-content',
|
|
|
|
),
|
|
|
|
$line),
|
|
|
|
"\xC2\xA0",
|
|
|
|
));
|
2011-07-31 17:38:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-01-16 22:51:39 +01:00
|
|
|
}
|