457 lines
12 KiB
Text
457 lines
12 KiB
Text
|
<?php namespace ProcessWire;
|
||
|
|
||
|
/**
|
||
|
* Class MarkupAdminDataTable
|
||
|
*
|
||
|
* @method string render()
|
||
|
*
|
||
|
* @property-read array $headerRow
|
||
|
* @property-read array $footerRow
|
||
|
* @property-read array $rows
|
||
|
* @property-read array $rowClasses
|
||
|
* @property-read array $rowAttrs
|
||
|
* @property-read array $actions
|
||
|
* @property bool $encodeEntities
|
||
|
* @property bool $sortable
|
||
|
* @property bool $resizable
|
||
|
* @property string $class
|
||
|
* @property string $caption
|
||
|
* @property bool $responsive
|
||
|
* @property array $settings
|
||
|
* @property string $id
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
class MarkupAdminDataTable extends ModuleJS {
|
||
|
|
||
|
public static function getModuleInfo() {
|
||
|
return array(
|
||
|
'title' => 'Admin Data Table',
|
||
|
'summary' => 'Generates markup for data tables used by ProcessWire admin',
|
||
|
'version' => 107,
|
||
|
'permanent' => true,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
const responsiveNo = 0; // responsive off
|
||
|
const responsiveYes = 1; // each td becomes 1-row, each 2 columns with th + td side-by-side
|
||
|
const responsiveAlt = 2; // each td becomes 1-row, with th + td stacked on top of each other
|
||
|
|
||
|
/**
|
||
|
* Number of table instances, for unique id attributes
|
||
|
*
|
||
|
* @var int
|
||
|
*
|
||
|
*/
|
||
|
static protected $instanceCnt = 0;
|
||
|
|
||
|
/**
|
||
|
* Table rows
|
||
|
*
|
||
|
* @var array
|
||
|
*
|
||
|
*/
|
||
|
protected $rows = array();
|
||
|
|
||
|
protected $headerRow = array();
|
||
|
|
||
|
protected $footerRow = array();
|
||
|
|
||
|
protected $rowClasses = array();
|
||
|
|
||
|
protected $rowAttrs = array();
|
||
|
|
||
|
protected $actions = array();
|
||
|
|
||
|
/**
|
||
|
* Initialize module and default settings
|
||
|
*
|
||
|
*/
|
||
|
public function init() {
|
||
|
|
||
|
|
||
|
// defaults for settings that are typically set globally for all tables
|
||
|
$defaults = array(
|
||
|
'class' => 'AdminDataTable AdminDataList',
|
||
|
'addClass' => '',
|
||
|
'responsiveClass' => 'AdminDataTableResponsive',
|
||
|
'responsiveAltClass' => 'AdminDataTableResponsiveAlt',
|
||
|
'sortableClass' => 'AdminDataTableSortable',
|
||
|
'resizableClass' => 'AdminDataTableResizable',
|
||
|
'loadStyles' => true,
|
||
|
'loadScripts' => true,
|
||
|
);
|
||
|
|
||
|
// settings set globally for all tables (when present)
|
||
|
$settings = $this->wire('config')->MarkupAdminDataTable;
|
||
|
|
||
|
if(empty($settings)) {
|
||
|
$settings = $defaults;
|
||
|
} else {
|
||
|
$settings = array_merge($defaults, $settings);
|
||
|
}
|
||
|
|
||
|
if(!empty($settings['sortableClass'])) {
|
||
|
$this->modules->get("JqueryTableSorter");
|
||
|
}
|
||
|
|
||
|
$this->loadStyles = $settings['loadStyles'];
|
||
|
$this->loadScripts = $settings['loadScripts'];
|
||
|
|
||
|
$this->set('encodeEntities', true);
|
||
|
$this->set('sortable', true);
|
||
|
$this->set('resizable', false);
|
||
|
$this->set('class', ''); // extra class(es), populated by addClass() method
|
||
|
$this->set('caption', '');
|
||
|
$this->set('responsive', self::responsiveYes);
|
||
|
$this->set('settings', $settings);
|
||
|
$this->set('id', '');
|
||
|
|
||
|
parent::init();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get property
|
||
|
*
|
||
|
* @param string $key
|
||
|
* @return mixed|null
|
||
|
*
|
||
|
*/
|
||
|
public function get($key) {
|
||
|
if($key === 'rows' || $key === 'headerRow' || $key === 'footerRow') return $this->$key;
|
||
|
if($key === 'rowClasses' || $key === 'rowAttrs' || $key === 'actions') return $this->$key;
|
||
|
return parent::get($key);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the header row for the table
|
||
|
*
|
||
|
* Given $a array may be either array of labels (strings) or any of the array items may
|
||
|
* be an array, in which case index 0 is the label and index 1 is a class attribute to use
|
||
|
* for the th element.
|
||
|
*
|
||
|
* @param array $a Array of header row labels
|
||
|
* @return $this
|
||
|
*
|
||
|
*/
|
||
|
public function headerRow(array $a) {
|
||
|
$this->headerRow = $a;
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the footer row for the table
|
||
|
*
|
||
|
* @param array $a Array of footer row labels (strings)
|
||
|
* @return $this
|
||
|
*
|
||
|
*/
|
||
|
public function footerRow(array $a) {
|
||
|
$this->footerRow = $this->setupRow($a);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add a row to the table
|
||
|
*
|
||
|
* @param array $a Array of columns that will each be a `<td>`, where each element may be one of the following:
|
||
|
* - `string`: converts to `<td>string</td>`
|
||
|
* - `$a["key"] = "value"`: converts to `<td><a href='value'>key</a></td>`
|
||
|
* - `array('label' => 'url')`: converts to `<td><a href='url'>label</a></td>` (same as above, but in nested array with count 1)
|
||
|
* - `array('label', 'class')`: converts to `<td class='class'>label</td>`
|
||
|
* @param array $options Optionally specify any one of the following:
|
||
|
* - separator (bool): specify true to show a stronger visual separator above the column
|
||
|
* - class (string): specify one or more class names to apply to the `<tr>`
|
||
|
* - attrs (array): array of attr => value for attributes to add to the `<tr>`
|
||
|
* @return $this
|
||
|
*
|
||
|
*/
|
||
|
public function row(array $a, array $options = array()) {
|
||
|
$defaults = array(
|
||
|
'separator' => false,
|
||
|
'class' => '',
|
||
|
'attrs' => array(),
|
||
|
);
|
||
|
$options = array_merge($defaults, $options);
|
||
|
$n = count($this->rows);
|
||
|
if($options['separator']) {
|
||
|
$options['class'] .= ($options['class'] ? ' ' : '') . 'AdminDataListSeparator';
|
||
|
}
|
||
|
$this->rows[$n] = $this->setupRow($a);
|
||
|
if(!empty($options['class'])) $this->rowClasses[$n] = $options['class'];
|
||
|
if(!empty($options['attrs'])) $this->rowAttrs[$n] = $options['attrs'];
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Setup/prepare a table row for rendering (internal)
|
||
|
*
|
||
|
* @param array $a
|
||
|
* @return array
|
||
|
*
|
||
|
*/
|
||
|
protected function setupRow(array $a) {
|
||
|
$row = array();
|
||
|
|
||
|
foreach($a as $k => $v) {
|
||
|
if(is_array($v) && count($v) === 1 && is_int($k)) {
|
||
|
// array('label' => 'href')
|
||
|
$key = key($v);
|
||
|
$v = reset($v);
|
||
|
if(is_string($key)) $k = $key;
|
||
|
}
|
||
|
if(is_string($k)) {
|
||
|
// Associative arrays get converted to:
|
||
|
// Anchor Text => URL
|
||
|
$v = $this->encode($v);
|
||
|
$k = $this->encode($k);
|
||
|
$v = "<a href='$v'>$k</a>";
|
||
|
} else if(is_array($v)) {
|
||
|
// class name is specified in $v[1]
|
||
|
foreach($v as $kk => $vv) {
|
||
|
$v[$kk] = $this->encode($vv);
|
||
|
}
|
||
|
} else {
|
||
|
$v = $this->encode($v);
|
||
|
}
|
||
|
$row[] = $v;
|
||
|
}
|
||
|
return $row;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add action(s) button underneath the table
|
||
|
*
|
||
|
* @param array $action Array in format array('button-label' => 'url')
|
||
|
* @return $this
|
||
|
*
|
||
|
*/
|
||
|
public function action(array $action) {
|
||
|
foreach($action as $label => $url) {
|
||
|
$this->actions[$label] = $url;
|
||
|
}
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render the table
|
||
|
*
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
public function ___render() {
|
||
|
|
||
|
$tableClass = trim($this->settings('class') . ' ' . $this->class);
|
||
|
if($this->responsive == self::responsiveYes) {
|
||
|
$tableClass .= ' ' . $this->settings('responsiveClass');
|
||
|
} else if($this->responsive == self::responsiveAlt) {
|
||
|
$tableClass .= ' ' . $this->settings('responsiveClass') . ' ' . $this->settings('responsiveAltClass');
|
||
|
}
|
||
|
if($this->sortable) $tableClass .= ' ' . $this->settings('sortableClass');
|
||
|
if($this->resizable) {
|
||
|
$tableClass .= ' ' . $this->settings('resizableClass');
|
||
|
/** @var JqueryTableSorter $tableSorter */
|
||
|
$tableSorter = $this->modules->get('JqueryTableSorter');
|
||
|
$tableSorter->use('widgets');
|
||
|
}
|
||
|
if($this->settings('addClass')) $tableClass .= ' ' . $this->settings('addClass');
|
||
|
$out = '';
|
||
|
$maxCols = 0;
|
||
|
$id = $this->id ? $this->id : "AdminDataTable" . (++self::$instanceCnt);
|
||
|
|
||
|
if(count($this->rows)) {
|
||
|
$out = "\n<table id='$id' class='$tableClass'>";
|
||
|
|
||
|
if($this->caption) $out .= "\n\t<caption>{$this->caption}</caption>";
|
||
|
|
||
|
if(count($this->headerRow)) {
|
||
|
$out .= "\n\t<thead>\n\t<tr>";
|
||
|
foreach($this->headerRow as $th) {
|
||
|
$class = '';
|
||
|
if(is_array($th)) list($th, $class) = $th;
|
||
|
$th = $this->encode($th);
|
||
|
if($class) $class = " class='" . $this->encode($class) . "'";
|
||
|
$out .= "\n\t\t<th$class>$th</th>";
|
||
|
$maxCols++;
|
||
|
}
|
||
|
$out .= "\n\t</tr>\n\t</thead>";
|
||
|
}
|
||
|
|
||
|
if(count($this->footerRow)) {
|
||
|
$out .= "\n\t<tfoot>\n\t<tr>";
|
||
|
foreach($this->footerRow as $td) {
|
||
|
$out .= "\n\t\t\t<td>$td</td>";
|
||
|
}
|
||
|
$out .= "\n\t</tr>\n\t</tfoot>";
|
||
|
}
|
||
|
|
||
|
$out .= "\n\t<tbody>";
|
||
|
foreach($this->rows as $n => $row) {
|
||
|
$cols = count($row);
|
||
|
if($cols > $maxCols) $maxCols = $cols;
|
||
|
if($cols < $maxCols) for(; $cols < $maxCols; $cols++) $row[] = ' ';
|
||
|
$attrs = isset($this->rowAttrs[$n]) ? $this->rowAttrs[$n] : array();
|
||
|
if(isset($this->rowClasses[$n])) $attrs['class'] = $this->rowClasses[$n];
|
||
|
$attrStr = '';
|
||
|
foreach($attrs as $attrName => $attrVal) {
|
||
|
$attrStr .= " $attrName='" . $this->wire('sanitizer')->entities($attrVal) . "'";
|
||
|
}
|
||
|
$out .= "\n\t\t<tr$attrStr>";
|
||
|
foreach($row as $td) {
|
||
|
$class = '';
|
||
|
if(is_array($td)) list($td, $class) = $td;
|
||
|
if(strlen($td) == 0 || $td === ' ') $class .= ($class ? ' ' : '') . 'blank';
|
||
|
if($class) $class = " class='$class'";
|
||
|
$out .= "\n\t\t\t<td$class>$td</td>";
|
||
|
}
|
||
|
$out .= "\n\t\t</tr>";
|
||
|
}
|
||
|
$out .= "\n\t</tbody>";
|
||
|
$out .= "\n</table>";
|
||
|
|
||
|
if($this->responsive && strpos($this->settings('responsiveClass'), 'AdminDataTableResponsive') === 0) {
|
||
|
$out .= "\n<script>if(typeof AdminDataTable != 'undefined') AdminDataTable.initTable($('#$id'));</script>";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(count($this->actions)) {
|
||
|
$out .= "\n<p>";
|
||
|
foreach($this->actions as $label => $url) {
|
||
|
/** @var InputfieldButton $button */
|
||
|
$button = $this->modules->get("InputfieldButton");
|
||
|
$button->href = $url;
|
||
|
$button->value = $label;
|
||
|
$out .= $button->render();
|
||
|
}
|
||
|
$out .= "\n</p>";
|
||
|
}
|
||
|
|
||
|
return $out;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Entity encode string (when entity encoding enabled)
|
||
|
*
|
||
|
* @param string $str
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
protected function encode($str) {
|
||
|
if(!$this->encodeEntities) return $str;
|
||
|
return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set whether or not entity encoding is enabled
|
||
|
*
|
||
|
* @param bool $encodeEntities
|
||
|
*
|
||
|
*/
|
||
|
public function setEncodeEntities($encodeEntities = true) {
|
||
|
$this->encodeEntities = $encodeEntities ? true : false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set class(es) to add to table
|
||
|
*
|
||
|
* @param string $class
|
||
|
*
|
||
|
*/
|
||
|
public function setClass($class) {
|
||
|
$this->class = $this->encode($class);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add a class to the table (without replacing existing ones)
|
||
|
*
|
||
|
* @param string $class
|
||
|
*
|
||
|
*/
|
||
|
public function addClass($class) {
|
||
|
$this->class = trim($this->class . " " . $this->encode($class));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set whether or not table is sortable
|
||
|
*
|
||
|
* @param bool $sortable
|
||
|
*
|
||
|
*/
|
||
|
public function setSortable($sortable) {
|
||
|
$this->sortable = $sortable ? true : false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set whether or not table is resizable
|
||
|
*
|
||
|
* @param bool $resizable
|
||
|
*
|
||
|
*/
|
||
|
public function setResizable($resizable) {
|
||
|
$this->resizable = $resizable ? true : false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set table caption
|
||
|
*
|
||
|
* @param string $caption
|
||
|
*
|
||
|
*/
|
||
|
public function setCaption($caption) {
|
||
|
$this->caption = $this->encode($caption);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set table id attribute
|
||
|
*
|
||
|
* @param string $id
|
||
|
*
|
||
|
*/
|
||
|
public function setID($id) {
|
||
|
$this->id = $id;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the responsive mode of this table
|
||
|
*
|
||
|
* Default behavior is responsiveYes. Specify false or 0 to disable responsive.
|
||
|
* Or specify MarkupAdminDataTable::responsiveAlt for stacked th + td.
|
||
|
*
|
||
|
* @param int|bool $responsive
|
||
|
*
|
||
|
*/
|
||
|
public function setResponsive($responsive = true) {
|
||
|
$this->responsive = (int) $responsive;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get or set an internal setting
|
||
|
*
|
||
|
* @pw-internal
|
||
|
*
|
||
|
* @param string $key Setting to get or set
|
||
|
* @param mixed $value Optional value to set
|
||
|
* @return string|int|array|null|MarkupAdminDataTable
|
||
|
*
|
||
|
*/
|
||
|
public function settings($key, $value = null) {
|
||
|
$settings = parent::get('settings');
|
||
|
if(is_null($value)) {
|
||
|
return isset($settings[$key]) ? $settings[$key] : null;
|
||
|
} else {
|
||
|
$settings[$key] = $value;
|
||
|
parent::set('settings', $settings);
|
||
|
return $this;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function isSingular() {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public function isAutoload() {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
}
|