Initial xentral_oss_20.3.c9ffacf
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable;
|
||||
|
||||
use Xentral\Core\DependencyInjection\ContainerInterface;
|
||||
use Xentral\Widgets\DataTable\Service\DataTableRequestHandler;
|
||||
use Xentral\Widgets\DataTable\Service\DataTableService;
|
||||
|
||||
final class Bootstrap
|
||||
{
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public static function registerServices()
|
||||
{
|
||||
return [
|
||||
'DataTableService' => 'onInitDataTableService',
|
||||
'DataTableRequestHandler' => 'onInitDataTableRequestHandler',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container
|
||||
*
|
||||
* @return DataTableService
|
||||
*/
|
||||
public static function onInitDataTableService(ContainerInterface $container)
|
||||
{
|
||||
$factory = new DataTableFactory($container);
|
||||
|
||||
return $factory->createDataTableService();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container
|
||||
*
|
||||
* @return DataTableRequestHandler
|
||||
*/
|
||||
public static function onInitDataTableRequestHandler(ContainerInterface $container)
|
||||
{
|
||||
$factory = new DataTableFactory($container);
|
||||
|
||||
return $factory->createDataTableRequestHandler();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,443 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Column;
|
||||
|
||||
use JsonSerializable;
|
||||
use Xentral\Widgets\DataTable\Exception\InvalidArgumentException;
|
||||
use Xentral\Widgets\DataTable\Feature\ResponsiveFeature;
|
||||
|
||||
final class Column implements JsonSerializable
|
||||
{
|
||||
/** @var string ALIGN_LEFT */
|
||||
const ALIGN_LEFT = 'left';
|
||||
|
||||
/** @var string ALIGN_RIGHT */
|
||||
const ALIGN_RIGHT = 'right';
|
||||
|
||||
/** @var string ALIGN_CENTER */
|
||||
const ALIGN_CENTER = 'center';
|
||||
|
||||
/** @var string ALIGN_JUSTIFY */
|
||||
const ALIGN_JUSTIFY = 'justify';
|
||||
|
||||
/** @var array $validAlignments */
|
||||
public static $validAlignments = [
|
||||
self::ALIGN_LEFT,
|
||||
self::ALIGN_RIGHT,
|
||||
self::ALIGN_CENTER,
|
||||
self::ALIGN_JUSTIFY,
|
||||
];
|
||||
|
||||
/** @var string $name */
|
||||
private $name;
|
||||
|
||||
/** @var string $title */
|
||||
private $title;
|
||||
|
||||
/** @var bool $visible */
|
||||
private $visible;
|
||||
|
||||
/** @var bool $sortable */
|
||||
private $sortable;
|
||||
|
||||
/** @var bool $searchable */
|
||||
private $searchable;
|
||||
|
||||
/** @var bool $exportable */
|
||||
private $exportable;
|
||||
|
||||
/** @var bool $fixed */
|
||||
private $fixed;
|
||||
|
||||
/** @var string $alignment */
|
||||
private $alignment;
|
||||
|
||||
/** @var string|null $dbColumn */
|
||||
private $dbColumn;
|
||||
|
||||
/** @var string|null $width */
|
||||
private $width;
|
||||
|
||||
/** @var callable|null $formatter */
|
||||
private $formatter;
|
||||
|
||||
/** @var array $properties */
|
||||
private $properties = [];
|
||||
|
||||
/** @var array $cssClasses CSS classes */
|
||||
private $cssClasses = [];
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $title
|
||||
* @param string $align [left|right|center|justify]
|
||||
* @param string|null $width Column width as CSS value (e.g 20%, 3em, 55px)
|
||||
* @param bool $visible Is column currently visible? Visibility can be changed at runtime
|
||||
* @param bool $sortable
|
||||
* @param bool $searchable
|
||||
* @param bool $exportable
|
||||
* @param bool $fixed If true, column is always visible and visibility can not be changed at runtime
|
||||
* * Fixed columns can't be hidden (ResponsiveFeature, ColumnVisibilityFeature)
|
||||
* * Fixed columns can't be reordered (ColumnReorderFeature)
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(
|
||||
$name,
|
||||
$title,
|
||||
$align = 'left',
|
||||
$width = null,
|
||||
$visible = true,
|
||||
$sortable = false,
|
||||
$searchable = false,
|
||||
$exportable = false,
|
||||
$fixed = false
|
||||
) {
|
||||
if (empty($name)) {
|
||||
throw new InvalidArgumentException('Column name can not be empty.');
|
||||
}
|
||||
$cleanedName = (string)preg_replace('#[^a-z0-9_]#', '', trim($name));
|
||||
if ($cleanedName !== $name) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Name "%s" contains illegal characters. Valid characters are: a-z, 0-9 and underscore.',
|
||||
$name
|
||||
));
|
||||
}
|
||||
|
||||
$this->name = (string)$name;
|
||||
$this->title = (string)$title;
|
||||
$this->visible = (bool)$visible;
|
||||
$this->sortable = (bool)$sortable;
|
||||
$this->searchable = (bool)$searchable;
|
||||
$this->exportable = (bool)$exportable;
|
||||
$this->fixed = (bool)$fixed;
|
||||
$this->alignment = (string)$align;
|
||||
$this->width = $width !== null ? (string)$width : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently hidden column; can be unhidden
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $title
|
||||
* @param string $align [left|right|center|justify]
|
||||
* @param string|null $width Column width as CSS value (e.g 20%, 3em, 55px)
|
||||
*
|
||||
* @return Column
|
||||
*/
|
||||
public static function hidden($name, $title, $align = 'left', $width = null)
|
||||
{
|
||||
return new static($name, $title, $align, $width, false, false, false, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Visible column; not sortable and not searchable
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $title
|
||||
* @param string $align [left|right|center|justify]
|
||||
* @param string|null $width Column width as CSS value (e.g 20%, 3em, 55px)
|
||||
*
|
||||
* @return Column
|
||||
*/
|
||||
public static function visible($name, $title, $align = 'left', $width = null)
|
||||
{
|
||||
return new static($name, $title, $align, $width, true, false, false, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Visible and sortable column; not searchable
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $title
|
||||
* @param string $align [left|right|center|justify]
|
||||
* @param string|null $width Column width as CSS value (e.g 20%, 3em, 55px)
|
||||
*
|
||||
* @return Column
|
||||
*/
|
||||
public static function sortable($name, $title, $align = 'left', $width = null)
|
||||
{
|
||||
return new static($name, $title, $align, $width, true, true, false, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Visible, sortable und searchable column
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $title
|
||||
* @param string $align [left|right|center|justify]
|
||||
* @param string|null $width Column width as CSS value (e.g 20%, 3em, 55px)
|
||||
*
|
||||
* @return Column
|
||||
*/
|
||||
public static function searchable($name, $title, $align = 'left', $width = null)
|
||||
{
|
||||
return new static($name, $title, $align, $width, true, true, true, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Always visible and with fixed position (for Menu and Selection columns)
|
||||
*
|
||||
* - Always visible; Can't be hidden (ColumnVisibilityFeature)
|
||||
* - Fixed position; Can't be reordered (ColumnReorderFeature)
|
||||
* - Not searchable
|
||||
* - Not sortable
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $title
|
||||
*
|
||||
* @return Column
|
||||
*/
|
||||
public static function fixed($name, $title = '', $align = 'center', $width = null)
|
||||
{
|
||||
$fixed = new static($name, $title, $align, $width, true, false, false, false, true);
|
||||
$fixed->set('responsivePriority', ResponsiveFeature::PRIO_HIGHER);
|
||||
|
||||
return $fixed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dbColumn
|
||||
*/
|
||||
public function setDbColumn($dbColumn)
|
||||
{
|
||||
$this->dbColumn = (string)$dbColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDbColumn()
|
||||
{
|
||||
return $this->dbColumn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAlignment()
|
||||
{
|
||||
return $this->alignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see $validAlignments
|
||||
*
|
||||
* @param string $alignment [left|right|center|justify]
|
||||
*/
|
||||
public function setAlignment($alignment)
|
||||
{
|
||||
if (!in_array($alignment, self::$validAlignments, true)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Alignment "%s" is not valid. Valid alignments: %s',
|
||||
$alignment,
|
||||
implode(', ', self::$validAlignments)
|
||||
));
|
||||
}
|
||||
|
||||
$this->alignment = $alignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return callable|null
|
||||
*/
|
||||
public function getFormatter()
|
||||
{
|
||||
return $this->formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $formatter
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setFormatter(callable $formatter)
|
||||
{
|
||||
$this->formatter = $formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isFixed()
|
||||
{
|
||||
return $this->fixed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isVisible()
|
||||
{
|
||||
return $this->visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isSortable()
|
||||
{
|
||||
return $this->sortable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isExportable()
|
||||
{
|
||||
return $this->exportable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isSearchable()
|
||||
{
|
||||
return $this->searchable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has($property)
|
||||
{
|
||||
if (isset($this->{$property})) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isset($this->properties[$property]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function get($property)
|
||||
{
|
||||
if (isset($this->{$property})) {
|
||||
return $this->{$property};
|
||||
}
|
||||
|
||||
if (isset($this->properties[$property])) {
|
||||
return $this->properties[$property];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set($property, $value)
|
||||
{
|
||||
if (isset($this->{$property})) {
|
||||
$this->{$property} = $value;
|
||||
}
|
||||
|
||||
$this->properties[$property] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasCssClass($className)
|
||||
{
|
||||
return in_array($className, $this->cssClasses, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addCssClass($className)
|
||||
{
|
||||
$this->cssClasses[] = trim($className);
|
||||
$this->cssClasses = array_unique($this->cssClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function removeCssClass($className)
|
||||
{
|
||||
$classKey = array_search($className, $this->cssClasses, true);
|
||||
if ($classKey !== false) {
|
||||
unset($this->cssClasses[$classKey]);
|
||||
$this->cssClasses = array_values($this->cssClasses);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
$result = $this->properties;
|
||||
$result['data'] = isset($result['data']) ? $result['data'] : $this->name;
|
||||
$result['name'] = $this->name;
|
||||
$result['title'] = $this->title;
|
||||
$result['exportable'] = $this->exportable;
|
||||
$result['searchable'] = $this->searchable;
|
||||
$result['orderable'] = $this->sortable;
|
||||
$result['visible'] = $this->visible;
|
||||
$result['fixed'] = $this->fixed;
|
||||
if ($this->fixed === true) {
|
||||
$result['visible'] = true;
|
||||
}
|
||||
|
||||
// Spalte hat keine Daten; z.B. Menü-Spalte
|
||||
if ($this->dbColumn === null) {
|
||||
//$result['data'] = null;
|
||||
$result['defaultContent'] = isset($result['defaultContent']) ? $result['defaultContent'] : '';
|
||||
$result['orderable'] = false;
|
||||
$result['searchable'] = false;
|
||||
// $result['data'] = $this->name;
|
||||
// $result['searchable'] = $this->searchable;
|
||||
}
|
||||
|
||||
$cssClasses = $this->cssClasses;
|
||||
$cssClasses[] = 'dt-' . $this->alignment;
|
||||
$result['className'] = implode(' ', $cssClasses);
|
||||
|
||||
if ($this->width !== null) {
|
||||
$result['width'] = $this->width;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Column;
|
||||
|
||||
use ArrayIterator;
|
||||
use IteratorAggregate;
|
||||
use JsonSerializable;
|
||||
use Traversable;
|
||||
use Xentral\Widgets\DataTable\Exception\ColumnNameAssignedException;
|
||||
use Xentral\Widgets\DataTable\Exception\InvalidArgumentException;
|
||||
|
||||
class ColumnCollection implements JsonSerializable, IteratorAggregate
|
||||
{
|
||||
/** @var array|Column[] $columns */
|
||||
protected $columns = [];
|
||||
|
||||
/**
|
||||
* @param array|Column[] $columns
|
||||
*/
|
||||
public function __construct(array $columns = [])
|
||||
{
|
||||
foreach ($columns as $column) {
|
||||
$this->add($column);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $columnName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has($columnName)
|
||||
{
|
||||
return $this->getByName($columnName) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Column $column
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add(Column $column)
|
||||
{
|
||||
$this->ensureUniqueColumnName($column->getName());
|
||||
$this->columns[] = $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Column $newColumn
|
||||
* @param string $columnNameBefore
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addAfter(Column $newColumn, $columnNameBefore)
|
||||
{
|
||||
$this->ensureUniqueColumnName($newColumn->getName());
|
||||
$offset = $this->getColumnIndexByName($columnNameBefore) + 1;
|
||||
|
||||
$columnsBefore = array_slice($this->columns, 0, $offset, false);
|
||||
$columnsAfter = array_slice($this->columns, $offset, null, false);
|
||||
|
||||
$this->columns = array_merge($columnsBefore, [$newColumn], $columnsAfter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Column $newColumn
|
||||
* @param string $columnNameAfter
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addBefore(Column $newColumn, $columnNameAfter)
|
||||
{
|
||||
$this->ensureUniqueColumnName($newColumn->getName());
|
||||
$offset = $this->getColumnIndexByName($columnNameAfter);
|
||||
|
||||
$columnsBefore = array_slice($this->columns, 0, $offset, false);
|
||||
$columnsAfter = array_slice($this->columns, $offset, null, false);
|
||||
|
||||
$this->columns = array_merge($columnsBefore, [$newColumn], $columnsAfter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $columnName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function remove($columnName)
|
||||
{
|
||||
foreach ($this->columns as $index => $column) {
|
||||
if ($column->getName() !== $columnName) {
|
||||
unset($this->columns[$index]);
|
||||
$this->columns = array_values($this->columns);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return Column|null
|
||||
*/
|
||||
public function getByName($name)
|
||||
{
|
||||
foreach ($this->columns as $column) {
|
||||
if ($column->getName() === $name) {
|
||||
return $column;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $index
|
||||
*
|
||||
* @return Column|null
|
||||
*/
|
||||
public function getByIndex($index)
|
||||
{
|
||||
return isset($this->columns[(int)$index]) ? $this->columns[(int)$index] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $columnName
|
||||
*
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getColumnIndexByName($columnName)
|
||||
{
|
||||
$offset = false;
|
||||
$this->columns = array_values($this->columns);
|
||||
foreach ($this->columns as $index => $column) {
|
||||
if ($column->getName() === $columnName) {
|
||||
$offset = $index;
|
||||
}
|
||||
}
|
||||
|
||||
if ($offset === false) {
|
||||
throw new InvalidArgumentException(sprintf('Column name "%s" does not exists.', $columnName));
|
||||
}
|
||||
|
||||
return $offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|Column[]
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
return $this->columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getSearchableDbColumns()
|
||||
{
|
||||
$searchable = [];
|
||||
|
||||
foreach ($this->columns as $column) {
|
||||
if ($column->isSearchable()) {
|
||||
$searchable[] = $column->getDbColumn();
|
||||
}
|
||||
}
|
||||
|
||||
return $searchable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|callable[] Array with callables, indexed by column name;
|
||||
* Empty array if there aren't any formatters
|
||||
*/
|
||||
public function getFormatters()
|
||||
{
|
||||
$formatters = [];
|
||||
|
||||
foreach ($this->columns as $column) {
|
||||
$colName = $column->getName();
|
||||
$formatter = $column->getFormatter();
|
||||
if (!empty($colName) && $formatter !== null) {
|
||||
$formatters[$colName] = $formatter;
|
||||
}
|
||||
}
|
||||
|
||||
return $formatters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this->columns as $column) {
|
||||
$result[] = $column->toArray();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayIterator|Traversable
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new ArrayIterator($this->columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep copy object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach ($this->columns as $index => $column) {
|
||||
$this->columns[$index] = clone $column;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $columnName
|
||||
*
|
||||
* @throws ColumnNameAssignedException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function ensureUniqueColumnName($columnName)
|
||||
{
|
||||
if ($this->has($columnName)) {
|
||||
throw new ColumnNameAssignedException(sprintf('Column name "%s" is already assigend.', $columnName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Column;
|
||||
|
||||
use Closure;
|
||||
use DateTime;
|
||||
use Exception;
|
||||
|
||||
class ColumnFormatter
|
||||
{
|
||||
/**
|
||||
* @param mixed $ifEmpty
|
||||
*
|
||||
* @return Closure
|
||||
*/
|
||||
public static function ifEmpty($ifEmpty)
|
||||
{
|
||||
return static function ($value) use ($ifEmpty) {
|
||||
if (empty($value)) {
|
||||
return $ifEmpty;
|
||||
}
|
||||
|
||||
return $value;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @example Format::sprintf('row_id_%s') %s will be replaced with the current value
|
||||
*
|
||||
* @param mixed $sprintf
|
||||
*
|
||||
* @return Closure
|
||||
*/
|
||||
public static function sprintf($sprintf)
|
||||
{
|
||||
return static function ($value) use ($sprintf) {
|
||||
return sprintf($sprintf, $value);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $template
|
||||
*
|
||||
* @return Closure
|
||||
*/
|
||||
public static function template($template)
|
||||
{
|
||||
return static function ($value, $rowAssoc) use ($template) {
|
||||
$templateVars = [];
|
||||
foreach ($rowAssoc as $assocKey => $assocValue) {
|
||||
$templateVar = '{' . strtoupper($assocKey) . '}';
|
||||
$templateVar = str_replace('-', '_', $templateVar);
|
||||
$templateVars[$templateVar] = $assocValue;
|
||||
}
|
||||
|
||||
return strtr($template, $templateVars);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $decimals
|
||||
* @param string $decimalSeperator
|
||||
* @param string $thousandsSeperator
|
||||
*
|
||||
* @return Closure
|
||||
*/
|
||||
public static function number($decimals = 2, $decimalSeperator = ',', $thousandsSeperator = '.')
|
||||
{
|
||||
return static function ($value) use ($decimals, $decimalSeperator, $thousandsSeperator) {
|
||||
return number_format($value, $decimals, $decimalSeperator, $thousandsSeperator);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $decimals
|
||||
* @param string $decimalSeperator
|
||||
* @param string $thousandsSeperator
|
||||
*
|
||||
* @return Closure
|
||||
*/
|
||||
public static function bytes($decimals = 1, $decimalSeperator = ',', $thousandsSeperator = '.')
|
||||
{
|
||||
return static function ($bytes) use ($decimals, $decimalSeperator, $thousandsSeperator) {
|
||||
$bytes = (float)$bytes;
|
||||
$base = log($bytes, 1024);
|
||||
$suffixes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
$suffixIndex = (int)floor($base);
|
||||
$suffix = $suffixes[$suffixIndex];
|
||||
$number = pow(1024, $base - floor($base));
|
||||
|
||||
return number_format($number, $decimals, $decimalSeperator, $thousandsSeperator) . ' ' . $suffix;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dateFormat https://www.php.net/manual/de/function.date.php
|
||||
*
|
||||
* @return Closure
|
||||
*/
|
||||
public static function date($dateFormat)
|
||||
{
|
||||
return static function ($dateString) use ($dateFormat) {
|
||||
try {
|
||||
$date = new DateTime($dateString);
|
||||
|
||||
return $date->format($dateFormat);
|
||||
} catch (Exception $exception) {
|
||||
return $exception->getMessage();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Closure
|
||||
*/
|
||||
public static function htmlEscape()
|
||||
{
|
||||
return static function ($value) {
|
||||
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Fixen
|
||||
*
|
||||
* @return Closure
|
||||
*/
|
||||
public static function dump()
|
||||
{
|
||||
return static function ($value, $row) {
|
||||
$data = [
|
||||
'value' => $value,
|
||||
'row' => $row,
|
||||
];
|
||||
|
||||
return sprintf(
|
||||
'<pre class="dump">%s</pre>',
|
||||
htmlspecialchars(var_export($data, true))
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable;
|
||||
|
||||
use Xentral\Widgets\DataTable\Exception\InvalidArgumentException;
|
||||
use Xentral\Widgets\DataTable\Type\DataTableTypeInterface;
|
||||
|
||||
final class DataTableBuildConfig
|
||||
{
|
||||
/** @var string $tableName */
|
||||
private $tableName;
|
||||
|
||||
/** @var string $tableClass */
|
||||
private $tableClass;
|
||||
|
||||
/** @var string $ajaxUrl */
|
||||
private $ajaxUrl;
|
||||
|
||||
/** @var string $ajaxMethod */
|
||||
private $ajaxMethod;
|
||||
|
||||
/** @var array $ajaxParams Additional AJAX parameter */
|
||||
private $ajaxParams;
|
||||
|
||||
/** @var bool $autoInit */
|
||||
private $autoInit;
|
||||
|
||||
/**
|
||||
* Available DataTable classes: display, compact, hover, order-column, row-border, cell-border, stripe, nowrap
|
||||
*
|
||||
* display = Short-hand for stripe, hover, row-border and order-column.
|
||||
*
|
||||
* @see https://datatables.net/manual/styling/classes#Table-classes
|
||||
*
|
||||
* @var array $cssClasses
|
||||
*/
|
||||
private $cssClasses = [];
|
||||
|
||||
/**
|
||||
* @param string $tableName Unique table name; Will be used as id-attribute on <table> element
|
||||
* @param string $tableClass FQCN of DataTable class that implements DataTableTypeInterface
|
||||
* @param string $ajaxUrl
|
||||
* @param bool $autoInit
|
||||
*/
|
||||
public function __construct($tableName, $tableClass, $ajaxUrl, $autoInit = true)
|
||||
{
|
||||
if (!class_exists($tableClass, true)) {
|
||||
throw new InvalidArgumentException(sprintf('DataTable class "%s" not found', $tableClass));
|
||||
}
|
||||
$interfaces = class_implements($tableClass, true);
|
||||
if (!in_array(DataTableTypeInterface::class, $interfaces, true)) {
|
||||
throw new InvalidArgumentException('DataTable class does not implement %s', DataTableTypeInterface::class);
|
||||
}
|
||||
$tableNameCleaned = preg_replace('/[^a-z0-9_-]+/', '', $tableName);
|
||||
if ($tableNameCleaned !== $tableName) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Table name "%s" contains illegal characters. ' .
|
||||
'Valid characters are: a-z, 0-9, hyphens and underscores.',
|
||||
$tableName
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
$this->tableName = $tableName;
|
||||
$this->tableClass = $tableClass;
|
||||
$this->ajaxUrl = $ajaxUrl;
|
||||
$this->ajaxMethod = 'GET';
|
||||
$this->ajaxParams = ['tablename' => $tableName];
|
||||
$this->autoInit = $autoInit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTableName()
|
||||
{
|
||||
return $this->tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTableClass()
|
||||
{
|
||||
return $this->tableClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAjaxUrl()
|
||||
{
|
||||
return $this->ajaxUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getAjaxMethod()
|
||||
{
|
||||
return $this->ajaxMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method [GET|POST]
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setAjaxMethod($method)
|
||||
{
|
||||
$method = strtoupper($method);
|
||||
if (!in_array($method, ['GET', 'POST'], true)) {
|
||||
throw new InvalidArgumentException('AJAX method "%s" is invalid.', $method);
|
||||
}
|
||||
|
||||
$this->ajaxMethod = $method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAjaxParams()
|
||||
{
|
||||
return $this->ajaxParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $param
|
||||
* @param mixed $value
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setAjaxParam($param, $value)
|
||||
{
|
||||
$cleanedName = (string)preg_replace('#[^A-Za-z0-9_-]#', '', trim($param));
|
||||
if ($cleanedName !== $param) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'AJAX parameter name "%s" contains illegal characters. ' .
|
||||
'Valid characters are: a-z, 0-9, hyphens and underscores.',
|
||||
$param
|
||||
));
|
||||
}
|
||||
if ($param === 'tablename') {
|
||||
throw new InvalidArgumentException('AJAX parameter "tablename" is reserved.');
|
||||
}
|
||||
|
||||
$this->ajaxParams[$param] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isAutoInit()
|
||||
{
|
||||
return $this->autoInit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCssClassesString()
|
||||
{
|
||||
return implode(' ', $this->getCssClasses());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getCssClasses()
|
||||
{
|
||||
return $this->cssClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasCssClass($className)
|
||||
{
|
||||
return in_array($className, $this->cssClasses, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addCssClass($className)
|
||||
{
|
||||
$cleanedName = (string)preg_replace('#[^a-z0-9_-]#', '', trim($className));
|
||||
if ($cleanedName !== $className) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'CSS class name "%s" contains illegal characters. ' .
|
||||
'Valid characters are: a-z, 0-9, hyphens and underscores.',
|
||||
$className
|
||||
));
|
||||
}
|
||||
|
||||
$this->cssClasses[] = $cleanedName;
|
||||
$this->cssClasses = array_unique($this->cssClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function removeCssClass($className)
|
||||
{
|
||||
$classKey = array_search($className, $this->cssClasses, true);
|
||||
if ($classKey !== false) {
|
||||
unset($this->cssClasses[$classKey]);
|
||||
$this->cssClasses = array_values($this->cssClasses);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable;
|
||||
|
||||
use Xentral\Core\DependencyInjection\ContainerInterface;
|
||||
use Xentral\Widgets\DataTable\Request\DataTableRequest;
|
||||
use Xentral\Widgets\DataTable\Service\DataTableBuilder;
|
||||
use Xentral\Widgets\DataTable\Service\DataTableFetcher;
|
||||
use Xentral\Widgets\DataTable\Service\DataTableRenderer;
|
||||
use Xentral\Widgets\DataTable\Service\DataTableRequestHandler;
|
||||
use Xentral\Widgets\DataTable\Service\DataTableService;
|
||||
|
||||
final class DataTableFactory
|
||||
{
|
||||
/** @var ContainerInterface */
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function __construct(ContainerInterface $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DataTableRequestHandler
|
||||
*/
|
||||
public function createDataTableRequestHandler()
|
||||
{
|
||||
return new DataTableRequestHandler($this->createDataTableService(), $this->createDataTableRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DataTableService
|
||||
*/
|
||||
public function createDataTableService()
|
||||
{
|
||||
return new DataTableService(
|
||||
$this->createDataTableBuilder(),
|
||||
$this->createDataTableRenderer(),
|
||||
$this->createDataTableFetcher()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DataTableBuilder
|
||||
*/
|
||||
private function createDataTableBuilder()
|
||||
{
|
||||
return new DataTableBuilder($this->container->get('Database'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DataTableRenderer
|
||||
*/
|
||||
private function createDataTableRenderer()
|
||||
{
|
||||
return new DataTableRenderer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DataTableFetcher
|
||||
*/
|
||||
private function createDataTableFetcher()
|
||||
{
|
||||
return new DataTableFetcher($this->container->get('Database'), $this->createDataTableRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DataTableRequest
|
||||
*/
|
||||
private function createDataTableRequest()
|
||||
{
|
||||
return DataTableRequest::fromRequest($this->container->get('Request'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable;
|
||||
|
||||
use Closure;
|
||||
use Xentral\Components\Database\SqlQuery\SelectQuery;
|
||||
use Xentral\Widgets\DataTable\Column\ColumnCollection;
|
||||
use Xentral\Widgets\DataTable\Options\DataTableOptions;
|
||||
use Xentral\Widgets\DataTable\Feature\FeatureCollection;
|
||||
use Xentral\Widgets\DataTable\Filter\FilterCollection;
|
||||
|
||||
interface DataTableInterface
|
||||
{
|
||||
/**
|
||||
* @return DataTableBuildConfig
|
||||
*/
|
||||
public function getConfig();
|
||||
|
||||
/**
|
||||
* @return DataTableOptions
|
||||
*/
|
||||
public function getOptions();
|
||||
|
||||
/**
|
||||
* @return ColumnCollection
|
||||
*/
|
||||
public function getColumns();
|
||||
|
||||
/**
|
||||
* @return FeatureCollection
|
||||
*/
|
||||
public function getFeatures();
|
||||
|
||||
/**
|
||||
* @return SelectQuery
|
||||
*/
|
||||
public function getBaseQuery();
|
||||
|
||||
/**
|
||||
* @return Closure|null
|
||||
*/
|
||||
public function getCustomSearch();
|
||||
|
||||
/**
|
||||
* @return FilterCollection
|
||||
*/
|
||||
public function getFilters();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class BuildFailedException extends RuntimeException implements DataTableExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Wenn bereits eine Column mit diesem Name existiert; Namen wüssen einmalig sein
|
||||
*/
|
||||
class ColumnNameAssignedException extends RuntimeException implements DataTableExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class ColumnNotFoundException extends RuntimeException implements DataTableExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class DataTableException extends RuntimeException implements DataTableExceptionInterface
|
||||
{
|
||||
/**
|
||||
* @param string $columnName
|
||||
*
|
||||
* @return DataTableException
|
||||
*/
|
||||
/*public static function columnNotFound($columnName)
|
||||
{
|
||||
return new self(sprintf('Column "%s" not found.', $columnName));
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Exception;
|
||||
|
||||
use Xentral\Core\Exception\WidgetExceptionInterface;
|
||||
|
||||
interface DataTableExceptionInterface extends WidgetExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Exception;
|
||||
|
||||
class FeatureExistsException extends \RuntimeException implements DataTableExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Exception;
|
||||
|
||||
use LogicException;
|
||||
|
||||
class FeatureIncompatibleException extends LogicException implements DataTableExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Exception;
|
||||
|
||||
class FeatureNotFoundException extends \InvalidArgumentException implements DataTableExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Exception;
|
||||
|
||||
use LogicException;
|
||||
|
||||
class FeatureNotImplementedException extends LogicException implements DataTableExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Exception;
|
||||
|
||||
use InvalidArgumentException as SplInvalidArgumentException;
|
||||
|
||||
class InvalidArgumentException extends SplInvalidArgumentException implements DataTableExceptionInterface
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Feature;
|
||||
|
||||
use Xentral\Widgets\DataTable\Options\DataTableOptions;
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
use Xentral\Widgets\DataTable\Exception\FeatureNotImplementedException;
|
||||
|
||||
/**
|
||||
* @todo
|
||||
*
|
||||
* @example https://datatables.net/reference/api/columns().footer()#Example
|
||||
*/
|
||||
final class ColumnAggregateFeature implements DataTableFeatureInterface
|
||||
{
|
||||
/**
|
||||
* @throws FeatureNotImplementedException
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
throw new FeatureNotImplementedException('Feature is not implemented yet.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function modifyTable(DataTableInterface $table)
|
||||
{
|
||||
$this->modifyOptions($table->getOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableOptions $options
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function modifyOptions(DataTableOptions $options)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Feature;
|
||||
|
||||
use Xentral\Widgets\DataTable\Column\Column;
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
|
||||
/**
|
||||
* @todo Fertigstellen
|
||||
*/
|
||||
final class ColumnFilterFeature implements DataTableFeatureInterface
|
||||
{
|
||||
/** @var string TYPE_NONE Filter deactivated */
|
||||
const TYPE_NONE = 'none'; // Filter deactivated
|
||||
|
||||
/** @var string TYPE_TEXT Default filter */
|
||||
const TYPE_TEXT = 'text'; // Default
|
||||
|
||||
/** @var string TYPE_TEXT_MULTI @todo Mehrere Wörter mit ODER suchen */
|
||||
const TYPE_TEXT_MULTI = 'text_multi';
|
||||
|
||||
/** @var string TYPE_SELECT @todo Dropdown */
|
||||
const TYPE_SELECT = 'select';
|
||||
|
||||
/** @var string TYPE_NUMBER */
|
||||
const TYPE_NUMBER = 'number';
|
||||
|
||||
/** @var string TYPE_NUMBER_RANGE */
|
||||
const TYPE_NUMBER_RANGE = 'number_range';
|
||||
|
||||
/** @var string TYPE_DATE @todo */
|
||||
const TYPE_DATE = 'date';
|
||||
|
||||
/** @var string TYPE_DATE_RANGE @todo */
|
||||
const TYPE_DATE_RANGE = 'date_range';
|
||||
|
||||
/** @var array $columnSettings */
|
||||
private $columnSettings = [];
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function modifyTable(DataTableInterface $table)
|
||||
{
|
||||
$this->modifyOptions($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $columnName
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addNumberRangeFilter($columnName)
|
||||
{
|
||||
$this->columnSettings[$columnName] = [
|
||||
'name' => $columnName,
|
||||
'type' => self::TYPE_NUMBER_RANGE,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo
|
||||
*
|
||||
* @param string $columnName
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addMultiWordFilter($columnName)
|
||||
{
|
||||
$this->columnSettings[$columnName] = [
|
||||
'name' => $columnName,
|
||||
'type' => self::TYPE_TEXT,
|
||||
'multi_word' => true,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo
|
||||
*
|
||||
* @param string $columnName
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
// public function addDropdownFilter($columnName)
|
||||
// {
|
||||
// $this->columnSettings[$columnName] = [
|
||||
// 'name' => $columnName,
|
||||
// 'type' => self::TYPE_SELECT,
|
||||
// ];
|
||||
// }
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function modifyOptions(DataTableInterface $table)
|
||||
{
|
||||
$result = [];
|
||||
/** @var Column $column */
|
||||
foreach ($table->getColumns() as $index => $column) {
|
||||
$columnName = $column->getName();
|
||||
if (isset($this->columnSettings[$columnName])) {
|
||||
$result[$index] = $this->columnSettings[$columnName];
|
||||
} else {
|
||||
// Default
|
||||
$defaultType = $column->isSearchable() ? self::TYPE_TEXT : self::TYPE_NONE;
|
||||
$result[$index] = [
|
||||
'name' => $column->getName(),
|
||||
'type' => $defaultType,
|
||||
];
|
||||
}
|
||||
if ($columnName === 'id' || $columnName === 'menu') {
|
||||
$result[$index] = [
|
||||
'name' => $column->getName(),
|
||||
'type' => self::TYPE_NONE, // Column filtering inactive on menu and id column
|
||||
];
|
||||
}
|
||||
if (strpos($columnName, '_') === 0) {
|
||||
$result[$index] = [
|
||||
'name' => $column->getName(),
|
||||
'type' => self::TYPE_NONE, // @todo Wird benötigt?
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$table->getOptions()->setOption('columnFilter', $result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Feature;
|
||||
|
||||
use Xentral\Widgets\DataTable\Options\DataTableOptions;
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
use Xentral\Widgets\DataTable\Exception\FeatureIncompatibleException;
|
||||
|
||||
/**
|
||||
* @deprecated Nicht verwenden; Momentan inkompatibel mit ColumnFilter! Filter-Eingabefelder werden falsch zugeordnet.
|
||||
*
|
||||
* @see https://datatables.net/extensions/colreorder/
|
||||
*/
|
||||
final class ColumnReorderFeature implements DataTableFeatureInterface
|
||||
{
|
||||
/**
|
||||
* @throws FeatureIncompatibleException
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
throw new FeatureIncompatibleException('DataTable feature "ColumnReorder" is incompatible.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function modifyTable(DataTableInterface $table)
|
||||
{
|
||||
$this->modifyOptions($table->getOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableOptions $options
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function modifyOptions(DataTableOptions $options)
|
||||
{
|
||||
/** @see https://datatables.net/reference/option/colReorder */
|
||||
$options->setOption('colReorder', ['enable' => true, 'realtime' => false]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Feature;
|
||||
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
use Xentral\Widgets\DataTable\Exception\DataTableExceptionInterface;
|
||||
|
||||
interface DataTableFeatureInterface
|
||||
{
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @throws DataTableExceptionInterface
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function modifyTable(DataTableInterface $table);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Feature;
|
||||
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
|
||||
final class DebugFeature implements DataTableFeatureInterface
|
||||
{
|
||||
/** @var bool $enabled */
|
||||
private $enabled;
|
||||
|
||||
/**
|
||||
* @param bool $enabled
|
||||
*/
|
||||
public function __construct($enabled = true)
|
||||
{
|
||||
$this->enabled = (bool)$enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function modifyTable(DataTableInterface $table)
|
||||
{
|
||||
if ($this->enabled === true) {
|
||||
$table->getConfig()->addCssClass('datatable-debug');
|
||||
} else {
|
||||
$table->getConfig()->removeCssClass('datatable-debug');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function enable()
|
||||
{
|
||||
$this->enabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function disable()
|
||||
{
|
||||
$this->enabled = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Feature;
|
||||
|
||||
use ArrayIterator;
|
||||
use IteratorAggregate;
|
||||
use Traversable;
|
||||
use Xentral\Widgets\DataTable\Exception\FeatureExistsException;
|
||||
use Xentral\Widgets\DataTable\Exception\FeatureNotFoundException;
|
||||
use Xentral\Widgets\DataTable\Exception\InvalidArgumentException;
|
||||
|
||||
class FeatureCollection implements IteratorAggregate
|
||||
{
|
||||
/** @var array $features */
|
||||
protected $features = [];
|
||||
|
||||
/**
|
||||
* @param array|DataTableFeatureInterface[] $features
|
||||
*/
|
||||
public function __construct(array $features = [])
|
||||
{
|
||||
foreach ($features as $feature) {
|
||||
$this->add($feature);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className Full-qualified class name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has($className)
|
||||
{
|
||||
$this->ensureClassNameParameter($className, __METHOD__);
|
||||
|
||||
foreach ($this->features as $feature) {
|
||||
if (get_class($feature) === $className) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className Full-qualified class name
|
||||
*
|
||||
* @throws FeatureNotFoundException
|
||||
*
|
||||
* @return DataTableFeatureInterface
|
||||
*/
|
||||
public function get($className)
|
||||
{
|
||||
$this->ensureClassNameParameter($className, __METHOD__);
|
||||
|
||||
foreach ($this->features as $feature) {
|
||||
if (get_class($feature) === $className) {
|
||||
return $feature;
|
||||
}
|
||||
}
|
||||
|
||||
throw new FeatureNotFoundException(sprintf('Feature class "%s" not found.', $className));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|DataTableFeatureInterface[]
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
return $this->features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new feature
|
||||
*
|
||||
* @param DataTableFeatureInterface $feature
|
||||
*
|
||||
* @throws FeatureExistsException If feature with same type already exists
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add(DataTableFeatureInterface $feature)
|
||||
{
|
||||
if ($this->has(get_class($feature))) {
|
||||
throw new FeatureExistsException(sprintf('Feature class "%s" already exists', get_class($feature)));
|
||||
}
|
||||
|
||||
$this->features[] = $feature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a feature; If feature with same type exists, it will be overwritten.
|
||||
*
|
||||
* @param DataTableFeatureInterface $feature
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set(DataTableFeatureInterface $feature)
|
||||
{
|
||||
$this->remove(get_class($feature));
|
||||
$this->features[] = $feature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|object $className Full-qualified class name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function remove($className)
|
||||
{
|
||||
$this->ensureClassNameParameter($className, __METHOD__);
|
||||
|
||||
foreach ($this->features as $index => $feature) {
|
||||
if (get_class($feature) === $className) {
|
||||
unset($this->features[$index]);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function removeAll()
|
||||
{
|
||||
$this->features = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayIterator|Traversable
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new ArrayIterator($this->features);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep copy object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach ($this->features as $index => $column) {
|
||||
$this->features[$index] = clone $column;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $className
|
||||
* @param string $callerName
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function ensureClassNameParameter($className, $callerName)
|
||||
{
|
||||
if (!is_string($className)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Parameter "className" in method "%s" has to be a class name.', $callerName
|
||||
));
|
||||
}
|
||||
|
||||
if (!class_exists($className, true)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'"%s" is not a valid class.', $className
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Feature;
|
||||
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
use Xentral\Widgets\DataTable\Options\DataTableOptions;
|
||||
use Xentral\Widgets\DataTable\Exception\FeatureIncompatibleException;
|
||||
|
||||
/**
|
||||
* @see https://datatables.net/extensions/fixedheader/
|
||||
*/
|
||||
final class FixedHeaderFeature implements DataTableFeatureInterface
|
||||
{
|
||||
/**
|
||||
* @throws FeatureIncompatibleException
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
throw new FeatureIncompatibleException('Feature "FixedHeaderFeature" does not work currently.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function modifyTable(DataTableInterface $table)
|
||||
{
|
||||
$this->modifyOptions($table->getOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableOptions $options
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function modifyOptions(DataTableOptions $options)
|
||||
{
|
||||
/** @see https://datatables.net/reference/option/fixedHeader */
|
||||
$options->setOption('fixedHeader', true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Feature;
|
||||
|
||||
use Xentral\Widgets\DataTable\Column\Column;
|
||||
use Xentral\Widgets\DataTable\Column\ColumnCollection;
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
use Xentral\Widgets\DataTable\Options\DataTableOptions;
|
||||
|
||||
/**
|
||||
* @see https://datatables.net/extensions/responsive/
|
||||
*/
|
||||
final class ResponsiveFeature implements DataTableFeatureInterface
|
||||
{
|
||||
/** @var int PRIO_HIGHEST */
|
||||
const PRIO_HIGHEST = 1;
|
||||
|
||||
/** @var int PRIO_HIGHER */
|
||||
const PRIO_HIGHER = 10;
|
||||
|
||||
/** @var int PRIO_NORMAL */
|
||||
const PRIO_NORMAL = 100;
|
||||
|
||||
/** @var int PRIO_LOWER */
|
||||
const PRIO_LOWER = 1000;
|
||||
|
||||
/** @var int PRIO_LOWEST */
|
||||
const PRIO_LOWEST = 10000;
|
||||
|
||||
/** @var array $responsiveProperty */
|
||||
private $responsiveProperty = [
|
||||
'details' => false,
|
||||
];
|
||||
|
||||
/** @var array $columnPriorities */
|
||||
private $columnPriorities = [];
|
||||
|
||||
/** @var int $defaultPriority */
|
||||
private $defaultPriority = self::PRIO_NORMAL;
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function modifyTable(DataTableInterface $table)
|
||||
{
|
||||
$this->modifyOptions($table->getOptions());
|
||||
$this->modifyColumns($table->getColumns());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $columnName
|
||||
* @param int $priority
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPriority($columnName, $priority)
|
||||
{
|
||||
$this->columnPriorities[$columnName] = (int)$priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $priority
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setDefaultPriority($priority)
|
||||
{
|
||||
$this->defaultPriority = (int)$priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableOptions $options
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function modifyOptions(DataTableOptions $options)
|
||||
{
|
||||
$options->setOption('responsive', $this->responsiveProperty);
|
||||
$options->removeOption('scrollX');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ColumnCollection $columns
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function modifyColumns(ColumnCollection $columns)
|
||||
{
|
||||
/** @var Column $column */
|
||||
foreach ($columns as $column) {
|
||||
$name = $column->getName();
|
||||
if (isset($this->columnPriorities[$name])) {
|
||||
$column->set('responsivePriority', $this->columnPriorities[$name]);
|
||||
} else {
|
||||
if (!$column->has('responsivePriority')) {
|
||||
$column->set('responsivePriority', $this->defaultPriority);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Feature;
|
||||
|
||||
use Closure;
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
|
||||
final class RowClassesFeature implements DataTableFeatureInterface
|
||||
{
|
||||
/** @var array $colors */
|
||||
private static $availableColors = [
|
||||
'lightgray',
|
||||
'lightgreen',
|
||||
'lightteal',
|
||||
'lightcyan',
|
||||
'lightblue',
|
||||
'lightindigo',
|
||||
'lightviolet',
|
||||
'lightfuchsia',
|
||||
'lightpink',
|
||||
'lightred',
|
||||
'lightorange',
|
||||
'lightyellow',
|
||||
'lightlime',
|
||||
];
|
||||
|
||||
/** @var array|string[] $classes */
|
||||
private $classes;
|
||||
|
||||
/** @var array|Closure[] $customFormatter */
|
||||
private $customFormatter = [];
|
||||
|
||||
/**
|
||||
* @param array|string[] $classes
|
||||
* @param array|Closure $customFormatter
|
||||
*/
|
||||
public function __construct(array $classes = [], array $customFormatter = [])
|
||||
{
|
||||
foreach ($classes as $class) {
|
||||
$this->addClass($class);
|
||||
}
|
||||
foreach ($customFormatter as $formatter) {
|
||||
$this->addCustomFormatter($formatter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addClass($className)
|
||||
{
|
||||
$this->classes[] = trim($className);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|string[]
|
||||
*/
|
||||
public function getClasses()
|
||||
{
|
||||
return $this->classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getClassesString()
|
||||
{
|
||||
return implode(' ', $this->classes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasCustomFormatter()
|
||||
{
|
||||
return !empty($this->customFormatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|Closure[]
|
||||
*/
|
||||
public function getCustomFormatter()
|
||||
{
|
||||
return $this->customFormatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Closure $closure
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addCustomFormatter(Closure $closure)
|
||||
{
|
||||
$this->customFormatter[] = $closure;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function modifyTable(DataTableInterface $table)
|
||||
{
|
||||
// @todo Logik steckt momentan in DataTableRenderer; muss aber hier rein
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getRandomColor()
|
||||
{
|
||||
$count = count(self::$availableColors);
|
||||
$index = mt_rand() % $count;
|
||||
|
||||
return self::$availableColors[$index];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Feature;
|
||||
|
||||
use Xentral\Widgets\DataTable\Column\Column;
|
||||
use Xentral\Widgets\DataTable\Column\ColumnFormatter;
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
use Xentral\Widgets\DataTable\Exception\DataTableExceptionInterface;
|
||||
use Xentral\Widgets\DataTable\Exception\InvalidArgumentException;
|
||||
|
||||
final class RowDetailsFeature implements DataTableFeatureInterface
|
||||
{
|
||||
/** @var string $ajaxUrl */
|
||||
private $ajaxUrl;
|
||||
|
||||
/** @var string $ajaxMethod */
|
||||
private $ajaxMethod;
|
||||
|
||||
/** @var array $ajaxParams @todo Additional AJAX parameter */
|
||||
private $ajaxParams = [];
|
||||
|
||||
/**
|
||||
* Der Wert aus der id-Spalte wird als POST-Parameter `id` übergeben
|
||||
*
|
||||
* @param string $ajaxUrl `./index.php?module=foo&action=bar`
|
||||
* @param string $ajaxMethod [GET|POST]
|
||||
* @param callable|null $customFormatter @todo
|
||||
*/
|
||||
public function __construct($ajaxUrl, $ajaxMethod = 'POST', $customFormatter = null)
|
||||
{
|
||||
$ajaxMethod = strtoupper($ajaxMethod);
|
||||
if (!in_array($ajaxMethod, ['GET', 'POST'])) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid method "%s".', $ajaxMethod));
|
||||
}
|
||||
|
||||
$this->ajaxUrl = $ajaxUrl;
|
||||
$this->ajaxMethod = $ajaxMethod;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @throws DataTableExceptionInterface
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function modifyTable(DataTableInterface $table)
|
||||
{
|
||||
$table->getOptions()->setOption('rowDetails', [
|
||||
'ajax' => [
|
||||
'url' => $this->ajaxUrl,
|
||||
'method' => $this->ajaxMethod,
|
||||
'data' => $this->ajaxParams,
|
||||
],
|
||||
]);
|
||||
|
||||
// Detail-Spalte erzeugen
|
||||
$newCol = Column::fixed('details', '', 'center', '20px');
|
||||
$newCol->setFormatter(ColumnFormatter::template('<span class="details" data-id="{ID}"></span>'));
|
||||
$newCol->addCssClass('dt-details');
|
||||
|
||||
// Detail-Spalte vor erste Spalte einfügen
|
||||
/** @var Column $firstCol */
|
||||
$firstCol = $table->getColumns()->getByIndex(0);
|
||||
$table->getColumns()->addBefore($newCol, $firstCol->getName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Feature;
|
||||
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
use Xentral\Widgets\DataTable\Exception\ColumnNotFoundException;
|
||||
use Xentral\Widgets\DataTable\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @see https://datatables.net/extensions/rowgroup/
|
||||
*/
|
||||
final class RowGroupFeature implements DataTableFeatureInterface
|
||||
{
|
||||
/** @var array $groupColumns */
|
||||
private $groupColumns;
|
||||
|
||||
/** @var bool $enabled */
|
||||
private $enabled;
|
||||
|
||||
/**
|
||||
* @param array $columnNames
|
||||
*/
|
||||
public function __construct(array $columnNames)
|
||||
{
|
||||
if (count($columnNames) === 0) {
|
||||
throw new InvalidArgumentException('Parameter "columnNames" is can not be empty.');
|
||||
}
|
||||
$this->groupColumns = $columnNames;
|
||||
$this->enabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @throws ColumnNotFoundException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function modifyTable(DataTableInterface $table)
|
||||
{
|
||||
foreach ($this->groupColumns as $columnName) {
|
||||
if (!$table->getColumns()->has($columnName)) {
|
||||
throw new ColumnNotFoundException(sprintf(
|
||||
'RowGroupFeature failed. Column "%s" not found.',
|
||||
$columnName
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($this->enabled === true) {
|
||||
$table->getOptions()->setOption('rowGroup', ['dataSrc' => $this->groupColumns]);
|
||||
}
|
||||
if ($this->enabled === false) {
|
||||
$table->getOptions()->setOption('rowGroup', false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $columnNames
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function groupBy(array $columnNames)
|
||||
{
|
||||
if (count($columnNames) === 0) {
|
||||
throw new InvalidArgumentException('Parameter "columnNames" is can not be empty.');
|
||||
}
|
||||
$this->groupColumns = $columnNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function enable()
|
||||
{
|
||||
$this->enabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function disable()
|
||||
{
|
||||
$this->enabled = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Feature;
|
||||
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
|
||||
final class StateSaveFeature implements DataTableFeatureInterface
|
||||
{
|
||||
/** @var bool $enabled */
|
||||
private $enabled;
|
||||
|
||||
/** @var int $duration In seconds (0 = Forever) */
|
||||
private $duration;
|
||||
|
||||
/**
|
||||
* @param bool $enabled
|
||||
* @param int $duration
|
||||
*/
|
||||
public function __construct($enabled = true, $duration = 0)
|
||||
{
|
||||
$this->enabled = (bool)$enabled;
|
||||
$this->duration = (int)$duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function modifyTable(DataTableInterface $table)
|
||||
{
|
||||
// $options = $table->getOptions()->toArray();
|
||||
// $options['columns'] = $table->getColumns()->toArray();
|
||||
// $table->getOptions()->setOption('revision', md5(json_encode($options)));
|
||||
|
||||
$table->getOptions()->setOption('stateSave', $this->enabled);
|
||||
$table->getOptions()->setOption('stateDuration', $this->duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function enable()
|
||||
{
|
||||
$this->enabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function disable()
|
||||
{
|
||||
$this->enabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getDuration()
|
||||
{
|
||||
return $this->duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $duration
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setDuration($duration)
|
||||
{
|
||||
$this->duration = (int)$duration;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Feature;
|
||||
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
|
||||
final class TableControlFeature implements DataTableFeatureInterface
|
||||
{
|
||||
/** @var array $buttonConfigCopy */
|
||||
private static $buttonConfigCopy = [
|
||||
'extend' => 'copy',
|
||||
'text' => 'Zwischenablage',
|
||||
];
|
||||
|
||||
/** @var array $buttonConfigCsv */
|
||||
private static $buttonConfigCsv = [
|
||||
'extend' => 'collection',
|
||||
'text' => 'CSV',
|
||||
'collectionTitle' => 'CSV-Export',
|
||||
'autoClose' => true,
|
||||
'buttons' => [
|
||||
[
|
||||
'text' => 'Alle Seiten',
|
||||
'action' => 'export-csv-all',
|
||||
],
|
||||
[
|
||||
'text' => 'Aktuelle Seite',
|
||||
'action' => 'export-csv-page',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/** @var array $buttonConfigExcel */
|
||||
private static $buttonConfigExcel = [
|
||||
'extend' => 'excel',
|
||||
'text' => 'Excel',
|
||||
];
|
||||
|
||||
/** @var array $buttonConfigPdf */
|
||||
private static $buttonConfigPdf = [
|
||||
'extend' => 'pdf',
|
||||
'text' => 'PDF',
|
||||
'orientation' => 'landscape',
|
||||
'pageSize' => 'A4',
|
||||
];
|
||||
|
||||
/** @var array $buttonConfigPrint */
|
||||
private static $buttonConfigPrint = [
|
||||
'extend' => 'print',
|
||||
'text' => 'Drucken',
|
||||
];
|
||||
|
||||
/** @var bool $info */
|
||||
private $info = true;
|
||||
|
||||
/** @var bool $paging */
|
||||
private $paging = true;
|
||||
|
||||
/** @var bool $searching */
|
||||
private $searching = true;
|
||||
|
||||
/** @var bool $lengthChange */
|
||||
private $lengthChange = true;
|
||||
|
||||
/** @var int|null $pageLength */
|
||||
private $pageLength;
|
||||
|
||||
/** @var bool $processing */
|
||||
private $processing = true;
|
||||
|
||||
/** @var bool $sorting */
|
||||
private $sorting = true;
|
||||
|
||||
/** @var array $buttons */
|
||||
private $buttons = [];
|
||||
|
||||
/**
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->setFullMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function modifyTable(DataTableInterface $table)
|
||||
{
|
||||
$table->getOptions()->setOption('info', $this->info);
|
||||
$table->getOptions()->setOption('paging', $this->paging);
|
||||
$table->getOptions()->setOption('buttons', $this->buttons);
|
||||
$table->getOptions()->setOption('searching', $this->searching);
|
||||
$table->getOptions()->setOption('lengthChange', $this->lengthChange);
|
||||
$table->getOptions()->setOption('processing', $this->processing);
|
||||
$table->getOptions()->setOption('ordering', $this->sorting);
|
||||
if ($this->pageLength !== null && $this->pageLength > 0) {
|
||||
$table->getOptions()->setOption('pageLength', $this->pageLength);
|
||||
$table->getOptions()->setOption('lengthChange', false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setFullMode()
|
||||
{
|
||||
$this->showInfo();
|
||||
$this->showButtons();
|
||||
$this->showLengthChange();
|
||||
$this->enableSearching();
|
||||
$this->enableSorting();
|
||||
$this->enablePaging();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setMinimalMode()
|
||||
{
|
||||
$this->showInfo();
|
||||
$this->enablePaging();
|
||||
$this->enableSorting();
|
||||
|
||||
$this->hideButtons();
|
||||
$this->hideLengthChange();
|
||||
$this->disableSearching();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function showInfo()
|
||||
{
|
||||
$this->info = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function hideInfo()
|
||||
{
|
||||
$this->info = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function showButtons()
|
||||
{
|
||||
$this->buttons = [
|
||||
'buttons' => [
|
||||
self::$buttonConfigCopy,
|
||||
self::$buttonConfigCsv,
|
||||
self::$buttonConfigExcel,
|
||||
self::$buttonConfigPdf,
|
||||
self::$buttonConfigPrint,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function hideButtons()
|
||||
{
|
||||
$this->buttons = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function showLengthChange()
|
||||
{
|
||||
$this->lengthChange = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function hideLengthChange()
|
||||
{
|
||||
$this->lengthChange = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $rowsPerPage
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPageLength($rowsPerPage)
|
||||
{
|
||||
$this->pageLength = (int)$rowsPerPage;
|
||||
$this->hideLengthChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function showProcessingIndicator()
|
||||
{
|
||||
$this->processing = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function hideProcessingIndicator()
|
||||
{
|
||||
$this->processing = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function enableSearching()
|
||||
{
|
||||
// @todo ColumnFilter aktivieren
|
||||
$this->searching = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function disableSearching()
|
||||
{
|
||||
// @todo ColumnFilter deaktivieren
|
||||
$this->searching = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function enableSorting()
|
||||
{
|
||||
$this->sorting = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function disableSorting()
|
||||
{
|
||||
$this->sorting = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function enablePaging()
|
||||
{
|
||||
$this->paging = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function disablePaging()
|
||||
{
|
||||
$this->paging = false;
|
||||
$this->lengthChange = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Feature;
|
||||
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
|
||||
final class TableStylingFeature implements DataTableFeatureInterface
|
||||
{
|
||||
/** @var array $cssClasses */
|
||||
private $cssClasses = [];
|
||||
|
||||
/**
|
||||
* @param bool $compact
|
||||
* @param bool $disableLineWrapping
|
||||
*/
|
||||
public function __construct($compact = false, $disableLineWrapping = false)
|
||||
{
|
||||
$this->setDefaultStyle();
|
||||
if ($compact === true) {
|
||||
$this->setCompactStyle();
|
||||
}
|
||||
if ($disableLineWrapping === true) {
|
||||
$this->disableLineWrapping();
|
||||
} else {
|
||||
$this->enableLineWrapping();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function modifyTable(DataTableInterface $table)
|
||||
{
|
||||
foreach ($this->cssClasses as $cssClass) {
|
||||
$table->getConfig()->addCssClass($cssClass);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* display: Short-hand for stripe, hover, row-border and order-column.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setDefaultStyle()
|
||||
{
|
||||
$this->removeCssClass('display');
|
||||
$this->removeCssClass('compact');
|
||||
$this->removeCssClass('order-column');
|
||||
|
||||
$this->enableHover();
|
||||
$this->disableRowBorder();
|
||||
$this->disableStripes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function setCompactStyle()
|
||||
{
|
||||
$this->addCssClass('compact');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function enableLineWrapping()
|
||||
{
|
||||
$this->removeCssClass('nowrap');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function disableLineWrapping()
|
||||
{
|
||||
$this->addCssClass('nowrap');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function enableHover()
|
||||
{
|
||||
$this->addCssClass('hover');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function disableHover()
|
||||
{
|
||||
$this->removeCssClass('hover');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function enableStripes()
|
||||
{
|
||||
$this->addCssClass('stripe');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function disableStripes()
|
||||
{
|
||||
$this->removeCssClass('stripe');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function enableRowBorder()
|
||||
{
|
||||
$this->addCssClass('row-border');
|
||||
$this->removeCssClass('cell-border');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function disableRowBorder()
|
||||
{
|
||||
$this->removeCssClass('row-border');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function hasCssClass($className)
|
||||
{
|
||||
return in_array($className, $this->cssClasses, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function addCssClass($className)
|
||||
{
|
||||
$this->cssClasses[] = trim($className);
|
||||
$this->cssClasses = array_unique($this->cssClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $className
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function removeCssClass($className)
|
||||
{
|
||||
$classKey = array_search($className, $this->cssClasses, true);
|
||||
if ($classKey !== false) {
|
||||
unset($this->cssClasses[$classKey]);
|
||||
$this->cssClasses = array_values($this->cssClasses);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Filter;
|
||||
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
use Xentral\Widgets\DataTable\Request\DataTableRequest;
|
||||
|
||||
abstract class AbstractFilter implements FilterInterface
|
||||
{
|
||||
/** @var string $type */
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getType();
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
* @param DataTableRequest $request
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function applyFilter(DataTableInterface $table, DataTableRequest $request);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Filter;
|
||||
|
||||
use Closure;
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
use Xentral\Widgets\DataTable\Request\DataTableRequest;
|
||||
|
||||
final class CustomFilter implements FilterInterface
|
||||
{
|
||||
/** @var Closure $closure */
|
||||
private $closure;
|
||||
|
||||
/**
|
||||
* @param Closure $closure
|
||||
*/
|
||||
public function __construct(Closure $closure)
|
||||
{
|
||||
$this->closure = $closure;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return FilterInterface::TYPE_CUSTOM;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
* @param DataTableRequest $request
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function applyFilter(DataTableInterface $table, DataTableRequest $request)
|
||||
{
|
||||
$closure = $this->closure;
|
||||
$closure($table->getBaseQuery(), $request);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Filter;
|
||||
|
||||
use ArrayIterator;
|
||||
use IteratorAggregate;
|
||||
use JsonSerializable;
|
||||
use Traversable;
|
||||
|
||||
class FilterCollection implements JsonSerializable, IteratorAggregate
|
||||
{
|
||||
/** @var array|FilterInterface[] $filters */
|
||||
protected $filters = [];
|
||||
|
||||
/**
|
||||
* @param array|FilterInterface[] $filters
|
||||
*/
|
||||
public function __construct(array $filters = [])
|
||||
{
|
||||
foreach ($filters as $filter) {
|
||||
$this->add($filter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FilterInterface $filter
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add(FilterInterface $filter)
|
||||
{
|
||||
$this->filters[] = $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|FilterInterface[]
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
return $this->filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this->filters as $filter) {
|
||||
$result[] = $filter->toArray();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayIterator|Traversable
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new ArrayIterator($this->filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep copy object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach ($this->filters as $index => $filter) {
|
||||
$this->filters[$index] = clone $filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Filter;
|
||||
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
use Xentral\Widgets\DataTable\Request\DataTableRequest;
|
||||
|
||||
interface FilterInterface
|
||||
{
|
||||
/** @var string TYPE_TEXT */
|
||||
const TYPE_TEXT = 'text';
|
||||
|
||||
/** @var string TYPE_NUMBER */
|
||||
const TYPE_NUMBER = 'number';
|
||||
|
||||
/** @var string TYPE_NUMBER_RANGE */
|
||||
const TYPE_NUMBER_RANGE = 'number_range';
|
||||
|
||||
/** @var string TYPE_CUSTOM */
|
||||
const TYPE_CUSTOM = 'custom';
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType();
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
* @param DataTableRequest $request
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function applyFilter(DataTableInterface $table, DataTableRequest $request);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Filter;
|
||||
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
use Xentral\Widgets\DataTable\Exception\FeatureNotImplementedException;
|
||||
use Xentral\Widgets\DataTable\Request\DataTableRequest;
|
||||
|
||||
/**
|
||||
* @deprecated Filter ist nocht nicht fertig
|
||||
*/
|
||||
final class NumberRangeFilter extends AbstractFilter
|
||||
{
|
||||
/**
|
||||
* @throws FeatureNotImplementedException
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
throw new FeatureNotImplementedException('Filter type not implemented yet.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return FilterInterface::TYPE_NUMBER_RANGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
* @param DataTableRequest $request
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function applyFilter(DataTableInterface $table, DataTableRequest $request)
|
||||
{
|
||||
// TODO: Implement applyFilter() method.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Filter;
|
||||
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
use Xentral\Widgets\DataTable\Exception\ColumnNotFoundException;
|
||||
use Xentral\Widgets\DataTable\Exception\InvalidArgumentException;
|
||||
use Xentral\Widgets\DataTable\Request\DataTableRequest;
|
||||
|
||||
class SingleWordTextFilter implements FilterInterface
|
||||
{
|
||||
/** @var string LIKE_EQUALS */
|
||||
const LIKE_EQUALS = 'equals';
|
||||
|
||||
/** @var string LIKE_STARTS_WITH */
|
||||
const LIKE_STARTS_WITH = 'startswith';
|
||||
|
||||
/** @var string LIKE_ENDS_WITH */
|
||||
const LIKE_ENDS_WITH = 'endswith';
|
||||
|
||||
/** @var string LIKE_ANY */
|
||||
const LIKE_ANY = 'any';
|
||||
|
||||
/** @var string $columnName*/
|
||||
private $columnName;
|
||||
|
||||
/** @var string $filterName */
|
||||
private $filterName;
|
||||
|
||||
/** @var string $likePattern */
|
||||
private $likePattern;
|
||||
|
||||
/**
|
||||
* @param string $columnName
|
||||
* @param string $filterName
|
||||
* @param string|null $likePattern
|
||||
*/
|
||||
public function __construct($columnName, $filterName, $likePattern = self::LIKE_ANY)
|
||||
{
|
||||
if ($likePattern !== null) {
|
||||
$validLikePatterns = [self::LIKE_EQUALS, self::LIKE_STARTS_WITH, self::LIKE_ENDS_WITH, self::LIKE_ANY];
|
||||
if (!in_array($likePattern, $validLikePatterns, true)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Like pattern "%s" is invalid. Valid patterns are: %s', $likePattern,
|
||||
implode(', ', $validLikePatterns)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
$this->columnName = $columnName;
|
||||
$this->filterName = $filterName;
|
||||
$this->likePattern = $likePattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFilterName()
|
||||
{
|
||||
return $this->filterName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getLikePattern()
|
||||
{
|
||||
return $this->likePattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return FilterInterface::TYPE_TEXT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
* @param DataTableRequest $request
|
||||
*
|
||||
* @throws ColumnNotFoundException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function applyFilter(DataTableInterface $table, DataTableRequest $request)
|
||||
{
|
||||
$column = $table->getColumns()->getByName($this->columnName);
|
||||
if ($column === null || $column->getDbColumn() === null) {
|
||||
throw new ColumnNotFoundException(sprintf(
|
||||
'Can not apply text filter. Column "%s" is missing.',
|
||||
$this->columnName
|
||||
));
|
||||
}
|
||||
|
||||
$filterValues = $request->getParams()->getFilterValues();
|
||||
if (!array_key_exists($this->filterName, $filterValues)) {
|
||||
return; // Filter param is not set
|
||||
}
|
||||
|
||||
$filterValue = (string)$filterValues[$this->filterName];
|
||||
if ($filterValue === '') {
|
||||
return; // Filter value is empty
|
||||
}
|
||||
|
||||
switch ($this->likePattern) {
|
||||
case self::LIKE_EQUALS:
|
||||
$filterCondition = $filterValue;
|
||||
break;
|
||||
case self::LIKE_STARTS_WITH:
|
||||
$filterCondition = $filterValue . '%';
|
||||
break;
|
||||
case self::LIKE_ENDS_WITH:
|
||||
$filterCondition = '%' . $filterValue;
|
||||
break;
|
||||
case self::LIKE_ANY:
|
||||
default:
|
||||
$filterCondition = '%' . $filterValue . '%';
|
||||
break;
|
||||
}
|
||||
|
||||
$table->getBaseQuery()->where($column->getDbColumn() . ' LIKE ?', $filterCondition);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Options;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
final class DataTableOptions implements JsonSerializable
|
||||
{
|
||||
/** @var array $options Datatable initialisation options */
|
||||
private $options;
|
||||
|
||||
/** @var array $defaultSorting */
|
||||
private $defaultSorting = [];
|
||||
|
||||
/** @var array $postSorting */
|
||||
private $postSorting = [];
|
||||
|
||||
/** @var array $preSorting */
|
||||
private $preSorting = [];
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->options = $this->getDefaults();
|
||||
foreach ($options as $property => $value) {
|
||||
$this->setOption($property, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasOption($property)
|
||||
{
|
||||
return isset($this->options[(string)$property]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $property
|
||||
* @param mixed|null $fallbackValue
|
||||
*
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function getOption($property, $fallbackValue = null)
|
||||
{
|
||||
if ($this->hasOption($property)) {
|
||||
return $this->options[(string)$property];
|
||||
}
|
||||
|
||||
return $fallbackValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets/overwrites a property
|
||||
*
|
||||
* @param string $property
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setOption($property, $value)
|
||||
{
|
||||
$this->options[(string)$property] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets a property
|
||||
*
|
||||
* @param $property
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function removeOption($property)
|
||||
{
|
||||
if ($this->hasOption($property)) {
|
||||
unset($this->options[(string)$property]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getDefaultSorting()
|
||||
{
|
||||
return $this->defaultSorting;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getPreSorting()
|
||||
{
|
||||
return $this->preSorting;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getPostSorting()
|
||||
{
|
||||
return $this->postSorting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default-Sortierung; Benutzer-Sortierung überschreibt Default-Sortierung
|
||||
*
|
||||
* @example ['lagerbestand' => 'DESC', 'bezeichnung' => 'ASC']
|
||||
*
|
||||
* @param array $sorting
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setDefaultSorting(array $sorting = [])
|
||||
{
|
||||
$this->defaultSorting = $sorting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Feste Vor-Sortierung; kann vom Benutzer nicht geändert werden
|
||||
*
|
||||
* @example ['lagerbestand' => 'DESC', 'bezeichnung' => 'ASC']
|
||||
*
|
||||
* @param array $sorting
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPreSorting(array $sorting = [])
|
||||
{
|
||||
$this->preSorting = $sorting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Feste Nach-Sortierung; kann vom Benutzer nicht geändert werden
|
||||
*
|
||||
* @example ['lagerbestand' => 'DESC', 'bezeichnung' => 'ASC']
|
||||
*
|
||||
* @param array $sorting
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setPostSorting(array $sorting = [])
|
||||
{
|
||||
$this->postSorting = $sorting;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getDefaults()
|
||||
{
|
||||
return [
|
||||
'processing' => true,
|
||||
'serverSide' => true,
|
||||
'ajax' => [
|
||||
'url' => null,
|
||||
'type' => 'GET',
|
||||
'data' => [],
|
||||
],
|
||||
'scrollX' => true,
|
||||
'orderCellsTop' => true, // Handle sorting events only on first header row
|
||||
'orderMulti' => true, // Multiple column ordering ability control
|
||||
'dom' => $this->getDefaultDomTemplate(),
|
||||
'language' => [
|
||||
'emptyTable' => 'Keine Einträge gefunden',
|
||||
'info' => 'Zeige _START_ bis _END_ von _TOTAL_ Einträgen',
|
||||
'infoEmpty' => 'Zeile 0 bis 0 von 0 Einträgen',
|
||||
'infoFiltered' => '(gefiltert aus insgesamt _MAX_ Einträgen)',
|
||||
'infoPostFix' => '',
|
||||
'decimal' => ',',
|
||||
'thousands' => '.',
|
||||
'lengthMenu' => '_MENU_ Einträge pro Seite',
|
||||
'loadingRecords' => 'Lade...',
|
||||
'processing' => 'Verarbeite...',
|
||||
'search' => 'Suche:',
|
||||
'zeroRecords' => 'Keine passenden Einträge gefunden',
|
||||
'paginate' => [
|
||||
'first' => '⇤',
|
||||
'last' => '⇥',
|
||||
'next' => '»',
|
||||
'previous' => '«',
|
||||
],
|
||||
'aria' => [
|
||||
'sortAscending' => ': Anklicken für aufsteigende Sortierung',
|
||||
'sortDescending' => ': Anklicken für absteigende Sortierung',
|
||||
],
|
||||
],
|
||||
|
||||
// Plugins
|
||||
'responsive' => false,
|
||||
|
||||
// Own config options
|
||||
'autoinit' => true,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* - l = Length changing input control ("Einträge pro Seite")
|
||||
* - f = Filtering input (Search)
|
||||
* - t = Table
|
||||
* - i = Information summary ("Zeige 1 bis 10 von 14 Einträgen")
|
||||
* - p = Pagination
|
||||
* - r = Processing display element (Loading overlay)
|
||||
* - B = Buttons
|
||||
* - R = ColReorder (Column visibility)
|
||||
*
|
||||
* @see https://datatables.net/reference/option/dom
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getDefaultDomTemplate()
|
||||
{
|
||||
return
|
||||
"<'datatable-top'<'datatable-length'l><'datatable-search'f>" .
|
||||
'r>t' .
|
||||
"<'datatable-bottom'<'datatable-info'i><'datatable-buttons'B><'datatable-paginate'p>>";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable;
|
||||
|
||||
use Closure;
|
||||
use Xentral\Components\Database\SqlQuery\SelectQuery;
|
||||
use Xentral\Widgets\DataTable\Column\ColumnCollection;
|
||||
use Xentral\Widgets\DataTable\Options\DataTableOptions;
|
||||
use Xentral\Widgets\DataTable\Feature\FeatureCollection;
|
||||
use Xentral\Widgets\DataTable\Filter\FilterCollection;
|
||||
|
||||
final class PreparedDataTable implements DataTableInterface
|
||||
{
|
||||
/** @var DataTableBuildConfig $config */
|
||||
private $config;
|
||||
|
||||
/** @var SelectQuery $query */
|
||||
private $query;
|
||||
|
||||
/** @var DataTableOptions $options */
|
||||
private $options;
|
||||
|
||||
/** @var ColumnCollection $columns */
|
||||
private $columns;
|
||||
|
||||
/** @var FeatureCollection $features */
|
||||
private $features;
|
||||
|
||||
/** @var FilterCollection $filters */
|
||||
private $filters;
|
||||
|
||||
/** @var Closure|null $customSearch @todo */
|
||||
private $customSearch;
|
||||
|
||||
/**
|
||||
* @param DataTableBuildConfig $config
|
||||
* @param DataTableOptions $options
|
||||
* @param SelectQuery $selectQuery
|
||||
* @param ColumnCollection $columns
|
||||
* @param FeatureCollection $features
|
||||
* @param FilterCollection $filters
|
||||
*/
|
||||
public function __construct(
|
||||
DataTableBuildConfig $config,
|
||||
DataTableOptions $options,
|
||||
SelectQuery $selectQuery,
|
||||
ColumnCollection $columns,
|
||||
FeatureCollection $features,
|
||||
FilterCollection $filters
|
||||
) {
|
||||
$this->config = $config;
|
||||
$this->options = $options;
|
||||
$this->query = $selectQuery;
|
||||
$this->columns = $columns;
|
||||
$this->features = $features;
|
||||
$this->filters = $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DataTableBuildConfig
|
||||
*/
|
||||
public function getConfig()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DataTableOptions
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SelectQuery
|
||||
*/
|
||||
public function getBaseQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ColumnCollection
|
||||
*/
|
||||
public function getColumns()
|
||||
{
|
||||
return $this->columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FeatureCollection
|
||||
*/
|
||||
public function getFeatures()
|
||||
{
|
||||
return $this->features;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FilterCollection
|
||||
*/
|
||||
public function getFilters()
|
||||
{
|
||||
return $this->filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Closure|null @todo
|
||||
*/
|
||||
public function getCustomSearch()
|
||||
{
|
||||
return $this->customSearch;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Request;
|
||||
|
||||
use Xentral\Components\Http\Request;
|
||||
use Xentral\Widgets\DataTable\Exception\InvalidArgumentException;
|
||||
|
||||
final class DataTableRequest
|
||||
{
|
||||
/** @var Request $request */
|
||||
private $request;
|
||||
|
||||
/** @var DataTableRequestParameter $params */
|
||||
private $params;
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param DataTableRequestParameter $parameter
|
||||
*/
|
||||
public function __construct(Request $request, DataTableRequestParameter $parameter)
|
||||
{
|
||||
$method = $request->getMethod();
|
||||
if (!in_array($method, ['GET', 'POST'], true)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Can not create DataTableRequest instance. HTTP method "%s" is invalid.', $method
|
||||
));
|
||||
}
|
||||
|
||||
$this->request = $request;
|
||||
$this->params = $parameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function fromRequest(Request $request)
|
||||
{
|
||||
$parameters = DataTableRequestParameter::fromRequest($request);
|
||||
|
||||
return new self($request, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDataRequest()
|
||||
{
|
||||
if (!$this->isValidDataTableRequest()) {
|
||||
return false;
|
||||
}
|
||||
if (!$this->isAjax()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function isValidDataTableRequest()
|
||||
{
|
||||
if (empty($this->getParams()->getTableName())) {
|
||||
return false;
|
||||
}
|
||||
if ($this->params->getDraw() < 1) {
|
||||
return false;
|
||||
}
|
||||
if (empty($this->params->getColumnsValues()) ||
|
||||
empty($this->params->getOrderValues()) ||
|
||||
empty($this->params->getSearchValues())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isAjax()
|
||||
{
|
||||
return $this->request->isAjax();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getMethod()
|
||||
{
|
||||
return $this->request->getMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DataTableRequestParameter
|
||||
*/
|
||||
public function getParams()
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Request
|
||||
*/
|
||||
public function getOriginalRequest()
|
||||
{
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isExportRequest()
|
||||
{
|
||||
if (!$this->isValidDataTableRequest()) {
|
||||
return false;
|
||||
}
|
||||
if ($this->isAjax()) {
|
||||
return false;
|
||||
}
|
||||
if (empty($this->params->getExportValues())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Request;
|
||||
|
||||
use Xentral\Components\Http\Request;
|
||||
|
||||
/**
|
||||
* @see https://datatables.net/manual/server-side
|
||||
*/
|
||||
final class DataTableRequestParameter
|
||||
{
|
||||
/** @var string|null $tableName */
|
||||
private $tableName;
|
||||
|
||||
/** @var int $draw Draw counter */
|
||||
private $draw;
|
||||
|
||||
/** @var int $start Paging first record offset */
|
||||
private $start;
|
||||
|
||||
/** @var int $length Number of records returned */
|
||||
private $length;
|
||||
|
||||
/** @var array $columns Column settings and search queries */
|
||||
private $columns;
|
||||
|
||||
/** @var array $search Global search query */
|
||||
private $search;
|
||||
|
||||
/** @var array $order Ordering settings */
|
||||
private $order;
|
||||
|
||||
/** @var array $filter Custom parameter for filter feature */
|
||||
private $filter;
|
||||
|
||||
/** @var array $export Custom parameter for export feature */
|
||||
private $export;
|
||||
|
||||
/**
|
||||
* @param string $tableName
|
||||
* @param int $draw
|
||||
* @param int $start
|
||||
* @param int $length
|
||||
* @param array $columns
|
||||
* @param array $search
|
||||
* @param array $order
|
||||
* @param array $filter
|
||||
* @param array $export
|
||||
*/
|
||||
public function __construct(
|
||||
$tableName = null,
|
||||
$draw = 1,
|
||||
$start = 0,
|
||||
$length = 10,
|
||||
$columns = [],
|
||||
$search = [],
|
||||
$order = [],
|
||||
$filter = [],
|
||||
$export = []
|
||||
) {
|
||||
$this->tableName = $tableName;
|
||||
$this->draw = (int)$draw;
|
||||
$this->start = (int)$start;
|
||||
$this->length = (int)$length;
|
||||
$this->columns = (array)$columns;
|
||||
$this->search = (array)$search;
|
||||
$this->order = (array)$order;
|
||||
$this->filter = (array)$filter;
|
||||
$this->export = (array)$export;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function fromRequest(Request $request)
|
||||
{
|
||||
$params = $request->getMethod() === 'GET' ? $request->get : $request->post;
|
||||
|
||||
$tableName = $params->getAlphaNumWithDashes('tablename', null);
|
||||
$draw = $params->getInt('draw', 1);
|
||||
$start = $params->getInt('start', 0);
|
||||
$length = $params->getInt('length', 10);
|
||||
$columns = (array)$params->get('columns', []);
|
||||
$search = (array)$params->get('search', []);
|
||||
$order = (array)$params->get('order', []);
|
||||
$filter = (array)$params->get('filter', []);
|
||||
$export = (array)$params->get('export', []);
|
||||
|
||||
return new self($tableName, $draw, $start, $length, $columns, $search, $order, $filter, $export);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getTableName()
|
||||
{
|
||||
return $this->tableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getDraw()
|
||||
{
|
||||
return $this->draw;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getStart()
|
||||
{
|
||||
return $this->start;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getLength()
|
||||
{
|
||||
return $this->length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getSearchValues()
|
||||
{
|
||||
return $this->search;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getOrderValues()
|
||||
{
|
||||
return $this->order;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getColumnsValues()
|
||||
{
|
||||
return $this->columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getFilterValues()
|
||||
{
|
||||
return $this->filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getExportValues()
|
||||
{
|
||||
return $this->export;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Result;
|
||||
|
||||
use JsonSerializable;
|
||||
use Xentral\Widgets\DataTable\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @see https://datatables.net/manual/server-side
|
||||
*/
|
||||
final class DataTableDataResult implements JsonSerializable
|
||||
{
|
||||
/** @var int $drawCounter The draw counter */
|
||||
private $drawCounter;
|
||||
|
||||
/** @var int $recordsTotal Total number of records, before filtering */
|
||||
private $recordsTotal;
|
||||
|
||||
/** @var int $recordsFiltered Total number of records, after filtering */
|
||||
private $recordsFiltered;
|
||||
|
||||
/** @var array $data */
|
||||
private $data;
|
||||
|
||||
/** @var string|null $errorMessage */
|
||||
private $errorMessage;
|
||||
|
||||
/** @var array|null $debugInfo */
|
||||
private $debugInfo;
|
||||
|
||||
/**
|
||||
* @param int $drawCounter
|
||||
* @param int $recordsTotal
|
||||
* @param int $recordsFiltered
|
||||
* @param array $data
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(
|
||||
$drawCounter = 1,
|
||||
$recordsTotal = 0,
|
||||
$recordsFiltered = 0,
|
||||
$data = []
|
||||
) {
|
||||
if (!is_int($drawCounter)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Invalid argument. Draw counter must be an integer. Given type: %s',
|
||||
strtolower(gettype($drawCounter))
|
||||
));
|
||||
}
|
||||
if (!is_int($recordsTotal)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Invalid argument. Total records count must be an integer. Given type: %s',
|
||||
strtolower(gettype($recordsTotal))
|
||||
));
|
||||
}
|
||||
if (!is_int($recordsFiltered)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Invalid argument. Filtered records count be an integer. Given type: %s',
|
||||
strtolower(gettype($recordsFiltered))
|
||||
));
|
||||
}
|
||||
if (!is_array($data)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Invalid argument. Data parameter must be an array. Given type: %s',
|
||||
strtolower(gettype($data))
|
||||
));
|
||||
}
|
||||
|
||||
$this->drawCounter = $drawCounter;
|
||||
$this->recordsTotal = $recordsTotal;
|
||||
$this->recordsFiltered = $recordsFiltered;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getResult()
|
||||
{
|
||||
$result = [
|
||||
'draw' => $this->drawCounter,
|
||||
'recordsTotal' => $this->recordsTotal,
|
||||
'recordsFiltered' => $this->recordsFiltered,
|
||||
'data' => $this->data,
|
||||
];
|
||||
|
||||
if ($this->debugInfo !== null) {
|
||||
$result['debug'] = $this->debugInfo;
|
||||
}
|
||||
if ($this->errorMessage !== null) {
|
||||
$result['error'] = $this->errorMessage;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getDrawCounter()
|
||||
{
|
||||
return $this->drawCounter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getRecordsTotal()
|
||||
{
|
||||
return $this->recordsTotal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getRecordsFiltered()
|
||||
{
|
||||
return $this->recordsFiltered;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|null
|
||||
*/
|
||||
public function getDebugInfo()
|
||||
{
|
||||
return $this->debugInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $debugInfo
|
||||
*/
|
||||
public function setDebugInfo($debugInfo)
|
||||
{
|
||||
if (!is_array($debugInfo)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Invalid argument. Debug information must be an array. Given type: %s',
|
||||
strtolower(gettype($debugInfo))
|
||||
));
|
||||
}
|
||||
|
||||
$this->debugInfo = $debugInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function hasError()
|
||||
{
|
||||
return $this->errorMessage !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getErrorMessage()
|
||||
{
|
||||
return $this->errorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $errorMessage
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function setErrorMessage($errorMessage)
|
||||
{
|
||||
if (!is_string($errorMessage)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Invalid argument. Error message must be a string. Given type: %s',
|
||||
strtolower(gettype($errorMessage))
|
||||
));
|
||||
}
|
||||
|
||||
$this->errorMessage = $errorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
return $this->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Result;
|
||||
|
||||
use Xentral\Widgets\DataTable\Exception\InvalidArgumentException;
|
||||
|
||||
final class DataTableHtmlResult
|
||||
{
|
||||
/** @var string $tableHtml */
|
||||
private $tableHtml;
|
||||
|
||||
/** @var array $scriptOptions Initialization options for DataTable */
|
||||
private $scriptOptions = [];
|
||||
|
||||
/**
|
||||
* @param string $tableHtml
|
||||
* @param array $scriptOptions
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct($tableHtml, array $scriptOptions)
|
||||
{
|
||||
if (empty($tableHtml)) {
|
||||
throw new InvalidArgumentException('Required parameter "tableHtml" is empty.');
|
||||
}
|
||||
if (empty($scriptOptions)) {
|
||||
throw new InvalidArgumentException('Required parameter "scriptOptions" is empty.');
|
||||
}
|
||||
|
||||
$this->tableHtml = $tableHtml;
|
||||
$this->scriptOptions = $scriptOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getResult()
|
||||
{
|
||||
return $this->getHtml();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getHtml()
|
||||
{
|
||||
$html = '<div class="datatable-container">';
|
||||
$html .= $this->getTableHtml();
|
||||
$html .= $this->getScriptHtml();
|
||||
$html .= '</div>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getTableHtml()
|
||||
{
|
||||
return $this->tableHtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getScriptHtml()
|
||||
{
|
||||
$optionsJsonString = json_encode(
|
||||
$this->getScriptOptions(),
|
||||
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT
|
||||
);
|
||||
|
||||
return sprintf('<script type="application/json">%s</script>', $optionsJsonString);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getScriptOptions()
|
||||
{
|
||||
return $this->scriptOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->getHtml();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Service;
|
||||
|
||||
use Xentral\Components\Database\Database;
|
||||
use Xentral\Widgets\DataTable\Column\ColumnCollection;
|
||||
use Xentral\Widgets\DataTable\DataTableBuildConfig;
|
||||
use Xentral\Widgets\DataTable\Exception\BuildFailedException;
|
||||
use Xentral\Widgets\DataTable\Options\DataTableOptions;
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
use Xentral\Widgets\DataTable\Feature\DataTableFeatureInterface;
|
||||
use Xentral\Widgets\DataTable\Feature\FeatureCollection;
|
||||
use Xentral\Widgets\DataTable\Filter\FilterCollection;
|
||||
use Xentral\Widgets\DataTable\PreparedDataTable;
|
||||
use Xentral\Widgets\DataTable\Type\DataTableTypeInterface;
|
||||
|
||||
final class DataTableBuilder
|
||||
{
|
||||
/** @var Database $database */
|
||||
private $database;
|
||||
|
||||
/**
|
||||
* @param Database $database
|
||||
*/
|
||||
public function __construct(Database $database)
|
||||
{
|
||||
$this->database = $database;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableBuildConfig $config
|
||||
*
|
||||
* @throws BuildFailedException
|
||||
*
|
||||
* @return DataTableInterface
|
||||
*/
|
||||
public function buildTable(DataTableBuildConfig $config)
|
||||
{
|
||||
if (empty(trim($config->getTableName()))) {
|
||||
throw new BuildFailedException('Build config is incomplete. Table name is empty.');
|
||||
}
|
||||
if (empty(trim($config->getAjaxUrl()))) {
|
||||
throw new BuildFailedException('Build config is incomplete. Property "ajaxUrl" is missing.');
|
||||
}
|
||||
if (!class_exists($config->getTableClass(), true)) {
|
||||
throw new BuildFailedException(sprintf('DataTable class "%s" not found', $config->getTableClass()));
|
||||
}
|
||||
$interfaces = class_implements($config->getTableClass(), true);
|
||||
if (!in_array(DataTableTypeInterface::class, $interfaces, true)) {
|
||||
throw new BuildFailedException(
|
||||
'Can not build data table. Class does not implement ' . DataTableTypeInterface::class
|
||||
);
|
||||
}
|
||||
|
||||
/** @var DataTableTypeInterface $table */
|
||||
$className = $config->getTableClass();
|
||||
$table = new $className();
|
||||
|
||||
// @todo getParent() verarbeiten
|
||||
|
||||
$options = new DataTableOptions();
|
||||
$table->configureOptions($options);
|
||||
|
||||
$columns = new ColumnCollection();
|
||||
$table->configureColumns($columns);
|
||||
|
||||
$query = $this->database->select();
|
||||
$table->configureQuery($query);
|
||||
|
||||
if ($query->hasOrderBy()) {
|
||||
throw new BuildFailedException(
|
||||
'Sorting in "configureQuery" will be overwritten. ' .
|
||||
'Use "setDefaultSorting" in "configureOptions" instead.'
|
||||
);
|
||||
}
|
||||
|
||||
$features = new FeatureCollection();
|
||||
$table->configureFeatures($features);
|
||||
|
||||
$filters = new FilterCollection();
|
||||
$table->configureFilters($filters);
|
||||
|
||||
$preparedTable = new PreparedDataTable($config, $options, $query, $columns, $features, $filters);
|
||||
$this->prepareTable($preparedTable);
|
||||
|
||||
return $preparedTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function prepareTable(DataTableInterface $table)
|
||||
{
|
||||
$this->prepareColumns($table);
|
||||
$this->applyFeatures($table);
|
||||
$this->prepareSorting($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function applyFeatures(DataTableInterface $table)
|
||||
{
|
||||
/** @var DataTableFeatureInterface $feature */
|
||||
foreach ($table->getFeatures() as $feature) {
|
||||
$feature->modifyTable($table);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function prepareColumns(DataTableInterface $table)
|
||||
{
|
||||
// Spalten aus dem SQL-Query holen
|
||||
$query = $table->getBaseQuery();
|
||||
$columnNames = $query->getCols();
|
||||
|
||||
foreach ($columnNames as $alias => $fullColumnName) {
|
||||
// Spalten mit Spaltenaliasen zuerst behandeln (easy)
|
||||
$column = $table->getColumns()->getByName($alias);
|
||||
if ($column !== null) {
|
||||
$column->setDbColumn($fullColumnName);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Tabellenalias aus Spaltenname entfernen
|
||||
$shortColumnName = $this->extractNameFromColumn($fullColumnName);
|
||||
$column = $table->getColumns()->getByName($shortColumnName);
|
||||
if ($column !== null) {
|
||||
$column->setDbColumn($fullColumnName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function prepareSorting(DataTableInterface $table)
|
||||
{
|
||||
$columnNames = array_column($table->getColumns()->toArray(), 'data');
|
||||
$defaultSorting = $table->getOptions()->getDefaultSorting();
|
||||
$postSorting = $table->getOptions()->getPostSorting();
|
||||
$preSorting = $table->getOptions()->getPreSorting();
|
||||
|
||||
$defaultSorting = $this->translateSortingValues($columnNames, $defaultSorting);
|
||||
$postSorting = $this->translateSortingValues($columnNames, $postSorting);
|
||||
$preSorting = $this->translateSortingValues($columnNames, $preSorting);
|
||||
|
||||
/**
|
||||
* Sortierung, wenn nichts gesetzt ist; Benutzer-Sortierung überschreibt diesen Wert
|
||||
*
|
||||
* @see https://datatables.net/reference/option/order
|
||||
*/
|
||||
if (empty($defaultSorting)) {
|
||||
$defaultSorting = [[0, 'asc']];
|
||||
}
|
||||
$table->getOptions()->setOption('order', $defaultSorting);
|
||||
|
||||
/**
|
||||
* Vor- und Nach-Sortierung; Kann vom Benutzer nicht geändert werden
|
||||
*
|
||||
* @see https://datatables.net/reference/option/orderFixed
|
||||
*/
|
||||
if (!empty($preSorting)) {
|
||||
$orderFixed['pre'] = $preSorting;
|
||||
}
|
||||
if (!empty($postSorting)) {
|
||||
$orderFixed['post'] = $postSorting;
|
||||
}
|
||||
if (!empty($orderFixed)) {
|
||||
$table->getOptions()->setOption('orderFixed', $orderFixed);
|
||||
} else {
|
||||
$table->getOptions()->removeOption('orderFixed');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @example ['lagerbestand' => 'DESC', 'bezeichnung' => 'ASC'] wird zu [[3, 'desc'], [1, 'asc']]
|
||||
*
|
||||
* @param array $columnNames
|
||||
* @param array $sortingValues
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function translateSortingValues($columnNames, $sortingValues)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($sortingValues as $columnName => $sortOrder) {
|
||||
$columnIndex = array_search($columnName, $columnNames, true);
|
||||
if ($columnIndex !== false) {
|
||||
$result[] = [$columnIndex, strtolower($sortOrder)];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $column
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function extractNameFromColumn($column)
|
||||
{
|
||||
if ($pos = strrpos($column, '.')) {
|
||||
return substr($column, $pos + 1);
|
||||
}
|
||||
|
||||
return $column;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,575 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Service;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Xentral\Components\Database\Database;
|
||||
use Xentral\Components\Database\SqlQuery\SelectQuery;
|
||||
use Xentral\Components\Exporter\Csv\CsvConfig;
|
||||
use Xentral\Components\Exporter\Csv\CsvWriter;
|
||||
use Xentral\Components\Exporter\Exception\InvalidResourceException;
|
||||
use Xentral\Widgets\DataTable\Column\Column;
|
||||
use Xentral\Widgets\DataTable\DataTableBuildConfig;
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
use Xentral\Widgets\DataTable\Exception\InvalidArgumentException;
|
||||
use Xentral\Widgets\DataTable\Feature\DebugFeature;
|
||||
use Xentral\Widgets\DataTable\Feature\RowClassesFeature;
|
||||
use Xentral\Widgets\DataTable\Filter\FilterInterface;
|
||||
use Xentral\Widgets\DataTable\Request\DataTableRequest;
|
||||
use Xentral\Widgets\DataTable\Result\DataTableDataResult;
|
||||
|
||||
final class DataTableFetcher
|
||||
{
|
||||
/** @var Database $db */
|
||||
private $db;
|
||||
|
||||
/** @var DataTableRequest $request */
|
||||
private $request;
|
||||
|
||||
/**
|
||||
* @param Database $db
|
||||
* @param DataTableRequest $request
|
||||
*/
|
||||
public function __construct(Database $db, DataTableRequest $request)
|
||||
{
|
||||
$this->db = $db;
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableBuildConfig $buildConfig
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canFetchData(DataTableBuildConfig $buildConfig)
|
||||
{
|
||||
if (!$this->request->isAjax()) {
|
||||
return false;
|
||||
}
|
||||
if ($this->request->getMethod() !== $buildConfig->getAjaxMethod()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tableNameDefined = $buildConfig->getTableName();
|
||||
$tableNameRequested = $this->request->getParams()->getTableName();
|
||||
|
||||
return $tableNameDefined === $tableNameRequested;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableBuildConfig $buildConfig
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canExportData(DataTableBuildConfig $buildConfig)
|
||||
{
|
||||
$exportParams = (array)$this->request->getParams()->getExportValues();
|
||||
if (empty($exportParams['format']) || empty($exportParams['result'])) {
|
||||
return false;
|
||||
}
|
||||
if ($this->request->isAjax()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tableNameDefined = $buildConfig->getTableName();
|
||||
$tableNameRequested = $this->request->getParams()->getTableName();
|
||||
|
||||
return $tableNameDefined === $tableNameRequested;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return DataTableDataResult
|
||||
*/
|
||||
public function fetchData(DataTableInterface $table)
|
||||
{
|
||||
$startParam = $this->request->getParams()->getStart();
|
||||
$lengthParam = $this->request->getParams()->getLength();
|
||||
|
||||
try {
|
||||
|
||||
$debugging = false;
|
||||
if ($table->getFeatures()->has(DebugFeature::class)) {
|
||||
/** @var DebugFeature $debugFeature */
|
||||
$debugFeature = $table->getFeatures()->get(DebugFeature::class);
|
||||
$debugging = $debugFeature->isEnabled();
|
||||
}
|
||||
|
||||
if ($debugging === true) {
|
||||
$debugData = ['profiler' => ['start' => microtime(true)]];
|
||||
}
|
||||
|
||||
$baseQuery = $table->getBaseQuery();
|
||||
$cols = $baseQuery->getCols();
|
||||
|
||||
// Set up query for total record count
|
||||
$recordsTotalQuery = clone $baseQuery;
|
||||
$recordsTotalQuery
|
||||
->resetCols()
|
||||
->cols([sprintf('COUNT(%s) AS num', $cols[0])]);
|
||||
|
||||
// Apply filters and searches
|
||||
$this->applyFilters($table);
|
||||
$this->applyColumnSearch($table, $baseQuery);
|
||||
$this->applyGlobalSearch($table, $baseQuery);
|
||||
|
||||
// Set up query for data + limit result set
|
||||
$dataQuery = clone $baseQuery;
|
||||
$dataQuery->offset($startParam);
|
||||
$dataQuery->limit($lengthParam);
|
||||
if ($startParam === -1 || $lengthParam === -1) {
|
||||
$dataQuery->offset(0);
|
||||
$dataQuery->limit(0);
|
||||
}
|
||||
|
||||
// Apply ORDER BY + LIMIT
|
||||
$sortingValues = $this->prepareSortingValue($table);
|
||||
$this->applySorting($dataQuery, $sortingValues);
|
||||
$this->applyPaging($dataQuery, $startParam, $lengthParam);
|
||||
|
||||
// Set up query for filtered record count
|
||||
// (= Record count with applied filters and searches)
|
||||
$recordsFilteredQuery = clone $baseQuery;
|
||||
$recordsFilteredQuery->resetCols()->cols([sprintf('COUNT(%s) AS num', $cols[0])]);
|
||||
|
||||
// Ergebnisanzahl; mit Filter
|
||||
$recordsFiltered = $this->db->fetchValue(
|
||||
$recordsFilteredQuery->getStatement(),
|
||||
$recordsFilteredQuery->getBindValues()
|
||||
);
|
||||
|
||||
// Ergebnisanzahl; ohne Filter
|
||||
$recordsTotal = $this->db->fetchValue(
|
||||
$recordsTotalQuery->getStatement(),
|
||||
$recordsTotalQuery->getBindValues()
|
||||
);
|
||||
|
||||
// Fetch data; displayed result
|
||||
$data = $this->db->fetchAll(
|
||||
$dataQuery->getStatement(),
|
||||
$dataQuery->getBindValues()
|
||||
);
|
||||
|
||||
// Column-Formatter anwenden
|
||||
$columnFormatters = $table->getColumns()->getFormatters();
|
||||
$this->applyColumnFormatters($data, $columnFormatters);
|
||||
|
||||
// Row-Formatter anwenden
|
||||
// @todo In RowClassesFeature auslagern
|
||||
if ($table->getFeatures()->has(RowClassesFeature::class)) {
|
||||
/** @var RowClassesFeature $rowStyling */
|
||||
$rowStyling = $table->getFeatures()->get(RowClassesFeature::class);
|
||||
if ($rowStyling->hasCustomFormatter()) {
|
||||
$rowFormatter = $rowStyling->getCustomFormatter();
|
||||
foreach ($data as &$rowValues) {
|
||||
$rowClasses = $rowStyling->getClassesString();
|
||||
foreach ($rowFormatter as $closure) {
|
||||
$rowClasses .= $closure($rowValues);
|
||||
}
|
||||
$rowValues['DT_RowClass'] = $rowClasses;
|
||||
}
|
||||
unset($rowValues);
|
||||
}
|
||||
}
|
||||
|
||||
// ID-Attribut für jede Zeile setzen
|
||||
$tableName = $table->getConfig()->getTableName();
|
||||
$this->appendRowIdAttribute($data, $tableName);
|
||||
|
||||
// Result-Objekt bauen
|
||||
$result = new DataTableDataResult(
|
||||
(int)$this->request->getParams()->getDraw(),
|
||||
(int)$recordsTotal,
|
||||
(int)$recordsFiltered,
|
||||
(array)$data
|
||||
);
|
||||
|
||||
} catch (Exception $exception) {
|
||||
$result = new DataTableDataResult();
|
||||
$result->setErrorMessage(sprintf(
|
||||
'Unhandled exception: (%s) %s',
|
||||
get_class($exception),
|
||||
$exception->getMessage()
|
||||
));
|
||||
}
|
||||
|
||||
if ($debugging === true) {
|
||||
$debugData['profiler']['finish'] = microtime(true);
|
||||
$debugData['profiler']['duration_real'] = $debugData['profiler']['finish'] - $debugData['profiler']['start'];
|
||||
$debugData['profiler']['duration'] = sprintf('%.6f', $debugData['profiler']['duration_real']) . ' seconds';
|
||||
|
||||
if (isset($dataQuery)) {
|
||||
$debugData['query']['statement'] = $dataQuery->getStatement();
|
||||
$debugData['query']['bindings'] = var_export($dataQuery->getBindValues(), true);
|
||||
}
|
||||
$result->setDebugInfo($debugData);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return string Path to export file
|
||||
*/
|
||||
public function exportData(DataTableInterface $table)
|
||||
{
|
||||
$startParam = (int)$this->request->getParams()->getStart();
|
||||
$lengthParam = (int)$this->request->getParams()->getLength();
|
||||
$exportParams = (array)$this->request->getParams()->getExportValues();
|
||||
|
||||
$exportFormat = !empty($exportParams['format']) ? $exportParams['format'] : 'csv';
|
||||
if ($exportFormat !== 'csv') {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Invalid export format "%s". Only "csv" is valid.', $exportFormat
|
||||
));
|
||||
}
|
||||
|
||||
$exportResult = !empty($exportParams['result']) ? $exportParams['result'] : 'page';
|
||||
if (!in_array($exportResult, ['all', 'page'], true)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Invalid export result parameter value "%s". Valid values: %s',
|
||||
$exportResult,
|
||||
implode(', ', ['all', 'page'])
|
||||
));
|
||||
}
|
||||
|
||||
// Alle Ergebnisse exportieren
|
||||
if ($exportResult === 'all') {
|
||||
$startParam = -1;
|
||||
$lengthParam = -1;
|
||||
}
|
||||
|
||||
$fileName = uniqid('export-' . $table->getConfig()->getTableName(), false) . '.csv';
|
||||
$filePath = sys_get_temp_dir() . '/' . $fileName;
|
||||
|
||||
$csv = @fopen($filePath, 'x+b');
|
||||
if ($csv === false) {
|
||||
throw new InvalidResourceException(sprintf('Failed to open resource for file path "%s".', $filePath));
|
||||
}
|
||||
|
||||
$writer = new CsvWriter($csv, new CsvConfig());
|
||||
|
||||
$titles = [];
|
||||
$dbCols = [];
|
||||
foreach ($table->getColumns() as $column) {
|
||||
/** @var Column $column */
|
||||
if ($column->isExportable() && !empty($column->getDbColumn())) {
|
||||
$titles[] = $column->getTitle();
|
||||
$dbCols[] = $column->getDbColumn();
|
||||
}
|
||||
}
|
||||
$writer->writeLine($titles);
|
||||
|
||||
$dataQuery = clone $table->getBaseQuery();
|
||||
$dataQuery->resetCols()->cols($dbCols);
|
||||
|
||||
// Apply filters and searches
|
||||
$this->applyFilters($table);
|
||||
$this->applyColumnSearch($table, $dataQuery);
|
||||
$this->applyGlobalSearch($table, $dataQuery);
|
||||
|
||||
// Apply ORDER BY
|
||||
$sortingValues = $this->prepareSortingValue($table);
|
||||
$this->applySorting($dataQuery, $sortingValues);
|
||||
|
||||
$itemsPerStep = 2500;
|
||||
$currentOffset = 0;
|
||||
$hasResults = true;
|
||||
|
||||
if ($exportResult === 'page') {
|
||||
$itemsPerStep = $lengthParam;
|
||||
$currentOffset = $startParam;
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
$dataQuery->offset($currentOffset);
|
||||
$dataQuery->limit($itemsPerStep);
|
||||
|
||||
$data = $this->db->yieldAll(
|
||||
$dataQuery->getStatement(),
|
||||
$dataQuery->getBindValues()
|
||||
);
|
||||
|
||||
if (!$data->valid()) {
|
||||
$hasResults = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
$writer->writeLines($data);
|
||||
|
||||
$currentOffset += $itemsPerStep;
|
||||
|
||||
// Nach einer Iteration aufhören, wenn nur eine Seite exportiert werden soll
|
||||
if ($exportResult === 'page') {
|
||||
$hasResults = false;
|
||||
}
|
||||
|
||||
} while ($hasResults);
|
||||
|
||||
fclose($csv);
|
||||
|
||||
return $filePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function applyFilters(DataTableInterface $table)
|
||||
{
|
||||
/** @var FilterInterface $filter */
|
||||
foreach ($table->getFilters() as $filter) {
|
||||
$filter->applyFilter($table, $this->request);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Suche über das allgemeine Suchfeld verarbeiten (oben rechts)
|
||||
*
|
||||
* @param DataTableInterface $table
|
||||
* @param SelectQuery $query
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function applyGlobalSearch(DataTableInterface $table, SelectQuery $query)
|
||||
{
|
||||
$searchValue = $this->getSearchParam();
|
||||
if (empty($searchValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$searchParts = explode(' ', $searchValue);
|
||||
$searchParts = array_filter($searchParts, 'trim');
|
||||
|
||||
// Custom Search @todo Momentan ohne Funktion; Es gibt keine Möglichkeit zum Setzen der Einstellung
|
||||
// Beispiel-Setter:
|
||||
//$this->setCustomSearch(function (SelectQuery $query) {
|
||||
// return $query
|
||||
// ->cols(['artikel.id'])
|
||||
// ->from('artikel')
|
||||
// ->where('artikel.name_de LIKE :query')
|
||||
// ->orWhere('artikel.name_en LIKE :query');
|
||||
//});
|
||||
//$customSearchClosure = $table->getCustomSearch();
|
||||
//if ($customSearchClosure !== null) {
|
||||
// $matchColumn = $query->getCols()[0];
|
||||
// $customSearchQuery = $customSearchClosure($this->db->select());
|
||||
//
|
||||
// $query->joinSubSelect('inner', $customSearchQuery, 'matches', 'matches.id = ' . $matchColumn);
|
||||
// $query->bindValue('query', '%' . $searchValue . '%');
|
||||
//
|
||||
// return;
|
||||
//}
|
||||
|
||||
// Normale Suche
|
||||
$searchableDbColumns = $table->getColumns()->getSearchableDbColumns();
|
||||
foreach ($searchParts as $searchWord) {
|
||||
$query->where(static function (SelectQuery $select) use ($searchableDbColumns, $searchWord) {
|
||||
foreach ($searchableDbColumns as $searchDbColumn) {
|
||||
$select->orWhere(sprintf('%s LIKE ?', $searchDbColumn), '%' . $searchWord . '%');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo In ColumnFilterFeature auslagern
|
||||
*
|
||||
* @param DataTableInterface $table
|
||||
* @param SelectQuery $query
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function applyColumnSearch(DataTableInterface $table, SelectQuery $query)
|
||||
{
|
||||
//$columnFilter = $table->getFeatures()->get(ColumnFilterFeature::class);
|
||||
|
||||
$params = (array)$this->request->getParams()->getColumnsValues();
|
||||
foreach ($params as $index => $param) {
|
||||
$searchValue = $param['search']['value'];
|
||||
if (empty($searchValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Spaltensuche wurde ausgefüllt
|
||||
$column = $table->getColumns()->getByName($param['name']);
|
||||
if ($column === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Zahlenbereich-Suche
|
||||
if (strpos($searchValue, 'number_range:') === 0) {
|
||||
$searchPattern = str_replace([':null|', '|null'], '|', $searchValue);
|
||||
if ($searchPattern === 'number_range:|') {
|
||||
continue; // Leere Suche
|
||||
}
|
||||
|
||||
$searchPattern = str_replace('number_range:', '', $searchPattern);
|
||||
$searchParts = explode('|', $searchPattern);
|
||||
if (count($searchParts) !== 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$valueFrom = str_replace(',', '.', $searchParts[0]);
|
||||
$valueTo = str_replace(',', '.', $searchParts[1]);
|
||||
if (is_numeric($valueFrom)) {
|
||||
$query->where($column->getDbColumn() . ' >= ?', (float)$valueFrom);
|
||||
}
|
||||
if (is_numeric($valueTo)) {
|
||||
$query->where($column->getDbColumn() . ' <= ?', (float)$valueTo);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Normale Textsuche
|
||||
$query->where($column->getDbColumn() . ' LIKE ?', '%' . $searchValue . '%');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://datatables.net/manual/server-side#Sent-parameters Parameters 'start' and 'length'
|
||||
*
|
||||
* @param SelectQuery $dataQuery
|
||||
* @param int $startValue
|
||||
* @param int $lengthValue
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function applyPaging(SelectQuery $dataQuery, $startValue, $lengthValue)
|
||||
{
|
||||
$dataQuery->offset($startValue);
|
||||
$dataQuery->limit($lengthValue);
|
||||
if ($startValue === -1 || $lengthValue === -1) {
|
||||
$dataQuery->offset(0);
|
||||
$dataQuery->limit(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SelectQuery $query
|
||||
* @param array $sortingValues
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function applySorting(SelectQuery $query, array $sortingValues)
|
||||
{
|
||||
if (!empty($sortingValues)) {
|
||||
$query->resetOrderBy();
|
||||
foreach ($sortingValues as $sortColumn => $sortDirection) {
|
||||
$query->orderBy([sprintf('%s %s', $sortColumn, strtoupper($sortDirection))]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Column-Formatter anwenden
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $formatters
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function applyColumnFormatters(array &$data, array $formatters = [])
|
||||
{
|
||||
if (empty($formatters)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($formatters as $colName => $formatter) {
|
||||
if (!is_callable($formatter)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($data as &$rowData) {
|
||||
$cellData = $rowData[$colName];
|
||||
$newValue = $this->callColumnFormatter($formatter, $cellData, $rowData);
|
||||
$rowData[$colName] = $newValue;
|
||||
}
|
||||
unset($rowData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Closure $callback
|
||||
* @param string $value
|
||||
* @param array $rowValues
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function callColumnFormatter(Closure $callback, $value, $rowValues)
|
||||
{
|
||||
return $callback($value, $rowValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function prepareSortingValue(DataTableInterface $table)
|
||||
{
|
||||
$orderValue = $this->getOrderParam();
|
||||
|
||||
$sorting = [];
|
||||
foreach ($orderValue as $orderItem) {
|
||||
$columnIndex = (int)$orderItem['column'];
|
||||
$column = $table->getColumns()->getByIndex($columnIndex);
|
||||
if ($column === null) {
|
||||
break;
|
||||
}
|
||||
$columnName = $column->getDbColumn();
|
||||
$sortDirection = in_array(strtolower($orderItem['dir']), ['asc', 'desc'], true)
|
||||
? strtolower($orderItem['dir'])
|
||||
: null;
|
||||
|
||||
if ($columnName !== null && $sortDirection !== null) {
|
||||
$sorting[$columnName] = strtoupper($sortDirection);
|
||||
}
|
||||
}
|
||||
|
||||
return $sorting;
|
||||
}
|
||||
|
||||
/**
|
||||
* ID-Attribut für jede Zeile setzen
|
||||
*
|
||||
* @param array $data
|
||||
* @param string $tableName
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function appendRowIdAttribute(&$data, $tableName)
|
||||
{
|
||||
// Row-ID hinzufügen
|
||||
foreach ($data as &$rowValues) {
|
||||
foreach ($rowValues as $key => &$value) {
|
||||
if ($key === 'id') {
|
||||
$rowValues['DT_RowId'] = sprintf('%s_row_%s', $tableName, $value);
|
||||
}
|
||||
}
|
||||
unset($value);
|
||||
}
|
||||
unset($rowValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function getSearchParam()
|
||||
{
|
||||
return $this->request->getParams()->getSearchValues()['value'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function getOrderParam()
|
||||
{
|
||||
return (array)$this->request->getParams()->getOrderValues();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Service;
|
||||
|
||||
use Xentral\Widgets\DataTable\Column\Column;
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
use Xentral\Widgets\DataTable\Result\DataTableHtmlResult;
|
||||
|
||||
final class DataTableRenderer
|
||||
{
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return DataTableHtmlResult
|
||||
*/
|
||||
public function createHtmlResult(DataTableInterface $table)
|
||||
{
|
||||
return new DataTableHtmlResult($this->getHtmlTable($table), $this->getDataTableOptions($table));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getDataTableOptions(DataTableInterface $table)
|
||||
{
|
||||
$options = $table->getOptions()->toArray();
|
||||
|
||||
$options['ajax'] = [
|
||||
'url' => $table->getConfig()->getAjaxUrl(),
|
||||
'type' => $table->getConfig()->getAjaxMethod(),
|
||||
'data' => $table->getConfig()->getAjaxParams(),
|
||||
];
|
||||
$options['columns'] = $table->getColumns()->toArray();
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableInterface $table
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getHtmlTable(DataTableInterface $table)
|
||||
{
|
||||
$columns = $table->getColumns();
|
||||
$headerHtml1 = '';
|
||||
$footerHtml = '';
|
||||
|
||||
/** @var Column $column */
|
||||
foreach ($columns as $column) {
|
||||
$headerHtml1 .= sprintf('<th data-name="%s">%s</th>', $column->getName(), $column->getTitle());
|
||||
if ($column->has('footerHtml')) {
|
||||
$footerHtml .= sprintf('<th data-name="%s">%s</th>', $column->getName(), $column->get('footerHtml'));
|
||||
} else {
|
||||
$footerHtml .= sprintf('<th data-name="%s">%s</th>', $column->getName(), $column->getTitle());
|
||||
}
|
||||
}
|
||||
|
||||
$html = "\n";
|
||||
$html .= sprintf(
|
||||
'<table id="%s" class="%s" width="100%%" data-autoinit="%s">',
|
||||
$table->getConfig()->getTableName(),
|
||||
$table->getConfig()->getCssClassesString(),
|
||||
$table->getConfig()->isAutoInit() ? 'true' : 'false'
|
||||
) . "\n";
|
||||
$html .= '<thead>';
|
||||
$html .= '<tr>' . $headerHtml1 . '</tr>';
|
||||
$html .= '</thead>' . "\n";
|
||||
$html .= '<tfoot><tr>' . $footerHtml . '</tr></tfoot>' . "\n";
|
||||
$html .= '</table>' . "\n";
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Service;
|
||||
|
||||
use Xentral\Components\Http\FileResponse;
|
||||
use Xentral\Components\Http\JsonResponse;
|
||||
use Xentral\Components\Http\Response;
|
||||
use Xentral\Widgets\DataTable\DataTableBuildConfig;
|
||||
use Xentral\Widgets\DataTable\Request\DataTableRequest;
|
||||
|
||||
final class DataTableRequestHandler
|
||||
{
|
||||
/** @var DataTableService $service */
|
||||
private $service;
|
||||
|
||||
/** @var DataTableRequest $request */
|
||||
private $request;
|
||||
|
||||
/**
|
||||
* @param DataTableService $service
|
||||
* @param DataTableRequest $request
|
||||
*/
|
||||
public function __construct(DataTableService $service, DataTableRequest $request)
|
||||
{
|
||||
$this->service = $service;
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableBuildConfig $config
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateHtml(DataTableBuildConfig $config)
|
||||
{
|
||||
return $this->service->renderHtml($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableBuildConfig $config
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canHandleRequest(DataTableBuildConfig $config)
|
||||
{
|
||||
if ($this->request->getMethod() !== $config->getAjaxMethod()) {
|
||||
return false;
|
||||
}
|
||||
if ($this->request->isDataRequest()) {
|
||||
return true;
|
||||
}
|
||||
if ($this->request->isExportRequest()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableBuildConfig $config
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function handleRequest(DataTableBuildConfig $config)
|
||||
{
|
||||
if ($this->request->isDataRequest()) {
|
||||
return $this->handleDataRequest($config);
|
||||
}
|
||||
if ($this->request->isExportRequest()) {
|
||||
return $this->handleExportRequest($config);
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'success' => false,
|
||||
'error' => 'Can not fetch data from datatable. This is not a valid request.',
|
||||
], Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableBuildConfig $config
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
private function handleExportRequest(DataTableBuildConfig $config)
|
||||
{
|
||||
$filePath = $this->service->exportData($config);
|
||||
|
||||
return FileResponse::createFromFile($filePath, 'export.csv', 'text/csv', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableBuildConfig $config
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
private function handleDataRequest(DataTableBuildConfig $config)
|
||||
{
|
||||
if (!$this->service->canFetchData($config)) {
|
||||
return new JsonResponse([
|
||||
'success' => false,
|
||||
'error' => 'Can not fetch data from datatable. Build config does not match with request parameters.',
|
||||
], Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$result = $this->service->fetchData($config);
|
||||
$status = $result->hasError() ? Response::HTTP_INTERNAL_SERVER_ERROR : Response::HTTP_OK;
|
||||
|
||||
return new JsonResponse($result, $status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Service;
|
||||
|
||||
use Xentral\Widgets\DataTable\DataTableBuildConfig;
|
||||
use Xentral\Widgets\DataTable\DataTableInterface;
|
||||
use Xentral\Widgets\DataTable\Result\DataTableDataResult;
|
||||
use Xentral\Widgets\DataTable\Result\DataTableHtmlResult;
|
||||
|
||||
final class DataTableService
|
||||
{
|
||||
/** @var DataTableBuilder $builder */
|
||||
private $builder;
|
||||
|
||||
/** @var DataTableRenderer $renderer */
|
||||
private $renderer;
|
||||
|
||||
/** @var DataTableFetcher $fetcher */
|
||||
private $fetcher;
|
||||
|
||||
/**
|
||||
* @param DataTableBuilder $builder
|
||||
* @param DataTableRenderer $renderer
|
||||
* @param DataTableFetcher $fetcher
|
||||
*/
|
||||
public function __construct(DataTableBuilder $builder, DataTableRenderer $renderer, DataTableFetcher $fetcher)
|
||||
{
|
||||
$this->builder = $builder;
|
||||
$this->renderer = $renderer;
|
||||
$this->fetcher = $fetcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableBuildConfig $buildConfig
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canFetchData(DataTableBuildConfig $buildConfig)
|
||||
{
|
||||
return $this->fetcher->canFetchData($buildConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableBuildConfig $buildConfig
|
||||
*
|
||||
* @return DataTableDataResult
|
||||
*/
|
||||
public function fetchData(DataTableBuildConfig $buildConfig)
|
||||
{
|
||||
$dataTable = $this->buildTable($buildConfig);
|
||||
|
||||
return $this->fetcher->fetchData($dataTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableBuildConfig $buildConfig
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canExportData(DataTableBuildConfig $buildConfig)
|
||||
{
|
||||
return $this->fetcher->canExportData($buildConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableBuildConfig $buildConfig
|
||||
*
|
||||
* @return string Path to temporary file
|
||||
*/
|
||||
public function exportData(DataTableBuildConfig $buildConfig)
|
||||
{
|
||||
$dataTable = $this->buildTable($buildConfig);
|
||||
|
||||
return $this->fetcher->exportData($dataTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableBuildConfig $buildConfig
|
||||
*
|
||||
* @return DataTableHtmlResult
|
||||
*/
|
||||
public function renderHtml(DataTableBuildConfig $buildConfig)
|
||||
{
|
||||
$dataTable = $this->buildTable($buildConfig);
|
||||
|
||||
return $this->renderer->createHtmlResult($dataTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DataTableBuildConfig $buildConfig
|
||||
*
|
||||
* @return DataTableInterface
|
||||
*/
|
||||
private function buildTable(DataTableBuildConfig $buildConfig)
|
||||
{
|
||||
return $this->builder->buildTable($buildConfig);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Type;
|
||||
|
||||
use Xentral\Components\Database\SqlQuery\SelectQuery;
|
||||
use Xentral\Widgets\DataTable\Column\ColumnCollection;
|
||||
use Xentral\Widgets\DataTable\Feature\DataTableFeatureInterface;
|
||||
use Xentral\Widgets\DataTable\Feature\FeatureCollection;
|
||||
use Xentral\Widgets\DataTable\Feature\ResponsiveFeature;
|
||||
use Xentral\Widgets\DataTable\Feature\StateSaveFeature;
|
||||
use Xentral\Widgets\DataTable\Feature\TableControlFeature;
|
||||
use Xentral\Widgets\DataTable\Feature\TableStylingFeature;
|
||||
use Xentral\Widgets\DataTable\Filter\FilterCollection;
|
||||
use Xentral\Widgets\DataTable\Options\DataTableOptions;
|
||||
|
||||
abstract class AbstractDataTableType implements DataTableTypeInterface
|
||||
{
|
||||
/**
|
||||
* @param DataTableOptions $options
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(DataTableOptions $options)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SelectQuery $query
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureQuery(SelectQuery $query)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ColumnCollection $columns
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureColumns(ColumnCollection $columns)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FeatureCollection $features
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureFeatures(FeatureCollection $features)
|
||||
{
|
||||
$this->addDefaultFeatures($features);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FilterCollection $filters
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureFilters(FilterCollection $filters)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param FeatureCollection $featureCollection
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function addDefaultFeatures(FeatureCollection $featureCollection)
|
||||
{
|
||||
foreach ($this->getDefaultFeatures() as $defaultFeature) {
|
||||
$defaultFeatureClassName = get_class($defaultFeature);
|
||||
if (!$featureCollection->has($defaultFeatureClassName)) {
|
||||
$featureCollection->add($defaultFeature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DataTableFeatureInterface[]|array
|
||||
*/
|
||||
private function getDefaultFeatures()
|
||||
{
|
||||
return [
|
||||
new StateSaveFeature($enabled = true, $duration = 0),
|
||||
new TableStylingFeature($compact = false, $noWrap = false),
|
||||
new TableControlFeature(),
|
||||
new ResponsiveFeature(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\DataTable\Type;
|
||||
|
||||
use Xentral\Components\Database\SqlQuery\SelectQuery;
|
||||
use Xentral\Widgets\DataTable\Column\ColumnCollection;
|
||||
use Xentral\Widgets\DataTable\Options\DataTableOptions;
|
||||
use Xentral\Widgets\DataTable\Feature\FeatureCollection;
|
||||
use Xentral\Widgets\DataTable\Filter\FilterCollection;
|
||||
|
||||
interface DataTableTypeInterface
|
||||
{
|
||||
/** @var string|null PARENT_TABLE */
|
||||
const PARENT_TABLE = null;
|
||||
|
||||
/**
|
||||
* @param DataTableOptions $options
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureOptions(DataTableOptions $options);
|
||||
|
||||
/**
|
||||
* @param SelectQuery $query
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureQuery(SelectQuery $query);
|
||||
|
||||
/**
|
||||
* @param ColumnCollection $columns
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureColumns(ColumnCollection $columns);
|
||||
|
||||
/**
|
||||
* @param FeatureCollection $features
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureFeatures(FeatureCollection $features);
|
||||
|
||||
/**
|
||||
* @param FilterCollection $filters
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function configureFilters(FilterCollection $filters);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
# DataTables
|
||||
|
||||
## Annahmen
|
||||
|
||||
* Nur falls Zeilen selektiert werden sollen:
|
||||
* In jeder SQL-Abfrage muss die erste Spalte eine eindeutige ID zurückliefern.
|
||||
* Diese Spalte muss den Namen `id` bekommen.
|
||||
* Diese Spalte muss nicht als `Column` definiert werden.
|
||||
|
||||
* Jede Tabellenspalte benötigt einen eindeutigen Namen; für die Zuordnung von Filtern.
|
||||
* Dieser Name korrespondiert mit einem SQL-Spaltennamen bzw. dem Alias. Beispiel:
|
||||
`SELECT projekt.abkuerzung AS projekt_name ... ` dann muss der Spaltenname `projekt_name` und nicht `abkuerzung` heißen.
|
||||
|
||||
* Jede DataTable benötigt einen eindeutigen Namen.
|
||||
* Für die Zuordnung von Filtern.
|
||||
* Eindeutiger Name wird aus Klassenname generiert, wenn kein Name in der BuildConfig angegeben wird.
|
||||
|
||||
## Spaltenarten
|
||||
|
||||
Siehe `\Xentral\Widgets\DataTable\Column\Column` Klasse.
|
||||
|
||||
##### `Column::visible($name, $title, $align = 'left', $width = null)`
|
||||
* Sichtbar
|
||||
* Nicht sortierbar
|
||||
* Nicht durchsuchbar
|
||||
|
||||
##### `Column::sortable($name, $title, $align = 'left', $width = null)`
|
||||
* Sichtbar
|
||||
* Sortierbar
|
||||
* Nicht durchsuchbar
|
||||
|
||||
##### `Column::searchable($name, $title, $align = 'left', $width = null)`
|
||||
* Sichtbar
|
||||
* Sortierbar
|
||||
* Durchsuchbar
|
||||
|
||||
##### `Column::fixed($name, $title, $align = 'left', $width = null)`
|
||||
* Sichtbar
|
||||
* Nicht sortierbar
|
||||
* Nicht durchsuchbar
|
||||
* Für Menü-Spalten und Zeilen-Selektion
|
||||
|
||||
##### `Column::hidden($name, $title, $align = 'left', $width = null)`
|
||||
* Initial ausgeblendet; kann eingeblendet werden (Feature noch nicht implementiert)
|
||||
* Nicht sortierbar
|
||||
* Nicht durchsuchbar
|
||||
|
||||
|
||||
## Aufbau
|
||||
|
||||
```
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Vorlage
|
||||
|
||||
```php
|
||||
|
||||
```
|
||||
Reference in New Issue
Block a user