artabro/wire/modules/Markup/MarkupAdminDataTable/MarkupAdminDataTable.module
2024-08-27 11:35:37 +02:00

471 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();
protected $colsNotSortable = 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;
if(isset($this->colsNotSortable[$maxCols])) $class = trim("$class sorter-false");
$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[] = '&nbsp;';
$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 === '&nbsp;') $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 a column as not sortable (first column is 0)
*
* @param int $index
* @since 3.0.215
*
*/
public function setColNotSortable($index) {
$index = (int) $index;
$this->colsNotSortable[$index] = $index;
}
/**
* 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;
}
}