'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 ``, where each element may be one of the following: * - `string`: converts to `string` * - `$a["key"] = "value"`: converts to `key` * - `array('label' => 'url')`: converts to `label` (same as above, but in nested array with count 1) * - `array('label', 'class')`: converts to `label` * @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 `` * - attrs (array): array of attr => value for attributes to add to the `` * @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 = "$k"; } 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"; if($this->caption) $out .= "\n\t"; if(count($this->headerRow)) { $out .= "\n\t\n\t"; 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"; $maxCols++; } $out .= "\n\t\n\t"; } if(count($this->footerRow)) { $out .= "\n\t\n\t"; foreach($this->footerRow as $td) { $out .= "\n\t\t\t"; } $out .= "\n\t\n\t"; } $out .= "\n\t"; 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"; 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"; } $out .= "\n\t\t"; } $out .= "\n\t"; $out .= "\n
{$this->caption}
$td
"; if($this->responsive && strpos($this->settings('responsiveClass'), 'AdminDataTableResponsive') === 0) { $out .= "\n"; } } if(count($this->actions)) { $out .= "\n

"; 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

"; } 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; } }