diff --git a/src/Column.php b/src/Column.php index 845f2b6..096b39a 100644 --- a/src/Column.php +++ b/src/Column.php @@ -28,6 +28,9 @@ class Column /** @var Datagrid */ protected $grid; + /** @var array */ + protected $attributes = []; + public function __construct($name, $label, Datagrid $grid) { @@ -37,6 +40,39 @@ public function __construct($name, $label, Datagrid $grid) } + /** + * @param string $name + * @param mixed $value + * @return self + */ + public function setAttribute($name, $value = true) + { + $this->attributes[$name] = $value; + return $this; + } + + + /** + * @param string $name + * @return bool + */ + public function hasAttribute($name) + { + return isset($this->attributes[$name]); + } + + + /** + * @param string $name + * @param mixed $default + * @return self + */ + public function getAttribute($name, $default = null) + { + return $this->attributes[$name] ?? $default; + } + + public function enableSort($default = NULL) { $this->sort = TRUE; diff --git a/src/Datagrid.blocks.latte b/src/Datagrid.blocks.latte index c740e3b..6081467 100644 --- a/src/Datagrid.blocks.latte +++ b/src/Datagrid.blocks.latte @@ -20,6 +20,10 @@ {/if} {/define} +{define row-head-cell} + {$column->label} +{/define} + {define row-head-columns} {ifset $form[actions]} @@ -28,7 +32,7 @@ {foreach $columns as $column} {if $column->canSort()} - {$column->label} + {include #row-head-cell column => $column} {if $column->isAsc()} {elseif $column->isDesc()} @@ -37,7 +41,7 @@ {/if} {else} - {$column->label} + {include #row-head-cell column => $column} {/if} {/foreach} @@ -91,6 +95,40 @@ {$control->translate(Edit)} {/define} +{define row-edit-control} + {input $column->name} +

{$error}

+{/define} + +{define row-insert-control} + {input $formContainer[$column->name]} +{/define} + +{define row-insert-empty} +{/define} + +{define row-insert-button} + {input button} +{/define} + +{define row-actions-delete-link} + {$control->translate(Delete)} +{/define} + +{define row-insert} + {formContainer insert-data} + {foreach $columns as $column} + {ifset $formContainer[$column->name]} + {include #row-insert-control form => $form, formContainer => $formContainer, column => $column} + {else} + {include #row-insert-empty form => $form, formContainer => $formContainer, column => $column} + {/} + {/foreach} + {/formContainer} + {include #row-insert-button form => $form} +{/define} + + {define row} {include #row-inner row => $row} @@ -117,10 +155,7 @@ {include #"cell-edit-{$column->name}" form => $form, column => $column, row => $row} {else} {formContainer edit} - {input $column->name} - {if $form[edit][$column->name]->hasErrors()} -

{$error}

- {/if} + {include #row-edit-control form => $form, formContainer => $formContainer, column => $column, row => $row} {/formContainer} {/ifset} @@ -148,8 +183,13 @@ {else} {ifset #row-actions} {include #row-actions row => $row, primary => $primary} - {elseif $control->getEditFormFactory()} - {include #row-actions-edit-link row => $row, primary => $primary} + {else} + {if $control->getEditFormFactory()} + {include #row-actions-edit-link row => $row, primary => $primary} + {/if} + {if $control->getDeleteCallback()} + {include #row-actions-delete-link row => $row, primary => $primary} + {/if} {/ifset} {/if} diff --git a/src/Datagrid.latte b/src/Datagrid.latte index 5dd6b3d..04dd2de 100644 --- a/src/Datagrid.latte +++ b/src/Datagrid.latte @@ -4,7 +4,7 @@ * @license MIT * @link https://github.com/nextras *} -
+
{snippet rows} {var $_templates = []} @@ -21,7 +21,10 @@ {php $hasActionsColumn = (bool) $control->getEditFormFactory() /* we may render only one row so the form[filter] may not be created */ - || isset($this->blockQueue["row-actions"]); + || isset($this->blockQueue["row-actions"]) + || isset($form["insert"]) + || isset($form["filter"]) + || $control->getDeleteCallback(); $hasGlobalActionsColumn = isset($form['actions']); foreach ($_templates as $_template): @@ -50,6 +53,11 @@ {ifset #empty-result}{include #empty-result}{/ifset} {/if} + + + {include #row-insert} + + diff --git a/src/Datagrid.php b/src/Datagrid.php index 01a315a..a45dde5 100644 --- a/src/Datagrid.php +++ b/src/Datagrid.php @@ -13,7 +13,6 @@ use Nette\Bridges\ApplicationLatte\Template; use Nette\Forms\Container; use Nette\Forms\Controls\Button; -use Nette\Forms\Controls\Checkbox; use Nette\Utils\Html; use Nette\Utils\Paginator; use Nette\Localization\ITranslator; @@ -42,6 +41,9 @@ class Datagrid extends UI\Control /** @persistent */ public $page = 1; + /** @var string|null */ + protected $anchor; + /** @var array */ protected $filterDataSource = []; @@ -54,12 +56,24 @@ class Datagrid extends UI\Control /** @var callable */ protected $dataSourceCallback; + /** @var callable|null */ + protected $formFactory; + + /** @var callable|null */ + protected $insertFormFactory; + + /** @var callable|null */ + protected $insertFormCallback; + /** @var callable|null */ protected $editFormFactory; /** @var callable|null */ protected $editFormCallback; + /** @var callable|null */ + protected $deleteCallback; + /** @var callable|null */ protected $filterFormFactory; @@ -72,7 +86,7 @@ class Datagrid extends UI\Control /** @var Paginator */ protected $paginator; - /** @var ITranslator */ + /** @var ITranslator|null */ protected $translator; /** @var callable|null */ @@ -136,6 +150,20 @@ public function getRowPrimaryKey() } + public function setAnchor($anchor = '') + { + $this->anchor = $anchor === '' + ? $this->getUniqueId() + : $anchor; + } + + + public function getAnchor() + { + return $this->anchor; + } + + public function setColumnGetterCallback(callable $getterCallback = null) { $this->columnGetterCallback = $getterCallback; @@ -160,6 +188,18 @@ public function getDataSourceCallback() } + public function setFormFactory(callable $formFactory) + { + $this->formFactory = $formFactory; + } + + + public function getFormFactory() + { + return $this->formFactory; + } + + public function setEditFormFactory(callable $editFormFactory = null) { $this->editFormFactory = $editFormFactory; @@ -184,6 +224,30 @@ public function getEditFormCallback() } + public function setInsertFormFactory(callable $insertFormFactory = null) + { + $this->insertFormFactory = $insertFormFactory; + } + + + public function getInsertFormFactory() + { + return $this->insertFormFactory; + } + + + public function setInsertFormCallback(callable $insertFormCallback = null) + { + $this->insertFormCallback = $insertFormCallback; + } + + + public function getInsertFormCallback() + { + return $this->insertFormCallback; + } + + public function setFilterFormFactory(callable $filterFormFactory = null) { $this->filterFormFactory = $filterFormFactory; @@ -196,6 +260,18 @@ public function getFilterFormFactory() } + public function setDeleteCallback(callable $callback = null) + { + $this->deleteCallback = $callback; + } + + + public function getDeleteCallback() + { + return $this->deleteCallback; + } + + public function addGlobalAction($name, $label, callable $action) { $this->globalActions[$name] = [$label, $action]; @@ -221,8 +297,9 @@ public function setPagination($itemsPerPage, callable $itemsCountCallback = null /** * @param string|Template $path + * @param bool $append */ - public function addCellsTemplate($path) + public function addCellsTemplate($path, $append = true) { if ($path instanceof Template) { $path = $path->getFile(); @@ -230,7 +307,12 @@ public function addCellsTemplate($path) if (!file_exists($path)) { throw new \InvalidArgumentException("Template '{$path}' does not exist."); } - $this->cellsTemplates[] = $path; + + if ($append) { + $this->cellsTemplates[] = $path; + } else { + array_unshift($this->cellsTemplates, $path); + } } @@ -242,7 +324,7 @@ public function getCellsTemplates() } - public function setTranslator(ITranslator $translator) + public function setTranslator(ITranslator $translator = null) { $this->translator = $translator; } @@ -337,16 +419,31 @@ protected function attached($presenter) protected function getData($key = null) { if (!$this->data) { + if ($this->dataSourceCallback === null) { + throw new \Exception('Data source callback is not set. Set it by ' . __CLASS__ . '::setDataSourceCallback().'); + } + $onlyRow = $key !== null && $this->presenter->isAjax(); if ($this->orderColumn !== NULL && !isset($this->columns[$this->orderColumn])) { $this->orderColumn = NULL; } + $validFilterData = []; + if ($this->filterFormFactory) { + $this['form']->isValid(); // triggers validation + foreach ($this['form']['filters']->getControls() as $name => $control) { + if ($control->getErrors() === []) { + $validFilterData[$name] = $control->getValue(); + } + } + $validFilterData = $this->filterFormFilter($validFilterData); + } + if (!$onlyRow && $this->paginator) { $itemsCount = call_user_func( $this->paginatorItemsCountCallback, - $this->filterDataSource, + $validFilterData, $this->orderColumn ? [$this->orderColumn, strtoupper($this->orderType)] : null ); @@ -358,7 +455,7 @@ protected function getData($key = null) $this->data = call_user_func( $this->dataSourceCallback, - $this->filterDataSource, + $validFilterData, $this->orderColumn ? [$this->orderColumn, strtoupper($this->orderType)] : null, $onlyRow ? null : $this->paginator ); @@ -418,6 +515,15 @@ public function handleEdit($primaryValue, $cancelEditPrimaryValue = null) } + public function handleDelete($primaryValue) + { + call_user_func($this->deleteCallback, $primaryValue); + if ($this->presenter->isAjax()) { + $this->redrawControl('rows'); + } + } + + public function handleSort() { if ($this->presenter->isAjax()) { @@ -428,15 +534,28 @@ public function handleSort() public function createComponentForm() { - $form = new UI\Form; + $form = $this->formFactory === null + ? new UI\Form + : call_user_func($this->formFactory, $this); + + if (!$form instanceof UI\Form) { + $type = is_object($form) ? get_class($form) : gettype($form); + throw new \Exception('Form factory callback has to return ' . UI\Form::class . ", but $type returned."); + } + + if ($this->anchor !== null) { + $form->onAnchor[] = function (UI\Form $form) { + $form->setAction($form->getAction() . '#' . $this->getAnchor()); + }; + } if ($this->filterFormFactory) { $form['filter'] = call_user_func($this->filterFormFactory); if (!isset($form['filter']['filter'])) { - $form['filter']->addSubmit('filter', $this->translate('Filter')); + $form['filter']->addSubmit('filter', 'Filter')->setValidationScope($form['filter']->getControls()); } if (!isset($form['filter']['cancel'])) { - $form['filter']->addSubmit('cancel', $this->translate('Cancel')); + $form['filter']->addSubmit('cancel', 'Cancel')->setValidationScope(false); } $this->prepareFilterDefaults($form['filter']); @@ -445,16 +564,25 @@ public function createComponentForm() } } + if ($this->insertFormFactory) { + $form['insert'] = new Container; + $form['insert']['data'] = call_user_func($this->insertFormFactory); + $form['insert']->addSubmit('button', 'Insert')->setValidationScope($form['insert']['data']->getControls()); + } + if ($this->editFormFactory && ($this->editRowKey !== null || !empty($_POST['edit']))) { $data = $this->editRowKey !== null && empty($_POST) ? $this->getData($this->editRowKey) : null; $form['edit'] = call_user_func($this->editFormFactory, $data); - if (!isset($form['edit']['save'])) - $form['edit']->addSubmit('save', 'Save'); - if (!isset($form['edit']['cancel'])) - $form['edit']->addSubmit('cancel', 'Cancel'); - if (!isset($form['edit'][$this->rowPrimaryKey])) + if (!isset($form['edit']['save'])) { + $form['edit']->addSubmit('save', 'Save')->setValidationScope($form['edit']->getControls()); + } + if (!isset($form['edit']['cancel'])) { + $form['edit']->addSubmit('cancel', 'Cancel')->setValidationScope(false); + } + if (!isset($form['edit'][$this->rowPrimaryKey])) { $form['edit']->addHidden($this->rowPrimaryKey); + } $form['edit'][$this->rowPrimaryKey] ->setDefaultValue($this->editRowKey) @@ -463,11 +591,18 @@ public function createComponentForm() if ($this->globalActions) { $actions = array_map(function($row) { return $row[0]; }, $this->globalActions); - $form['actions'] = new Container(); - $form['actions']->addSelect('action', 'Action', $actions) - ->setPrompt('- select action -'); + $form['actions'] = new Container; $form['actions']->addCheckboxList('items', '', []); - $form['actions']->addSubmit('process', 'Do'); + + if (count($actions) === 1) { + $form['actions']->addHidden('action', key($actions)); + $form['actions']->addSubmit('process', current($actions))->setValidationScope(false); + + } else { + $form['actions']->addSelect('action', 'Action', $actions) + ->setPrompt('- select action -'); + $form['actions']->addSubmit('process', 'Do')->setValidationScope(false); + } } if ($this->translator) { @@ -483,6 +618,15 @@ public function createComponentForm() public function processForm(UI\Form $form) { $allowRedirect = true; + if (isset($form['insert']) && $form['insert']['button']->isSubmittedBy()) { + if ($form['insert']['data']->isValid()) { + call_user_func($this->insertFormCallback, $form['insert']['data']); + $this->redrawControl('rows'); + } else { + $allowRedirect = false; + } + } + if (isset($form['edit'])) { if ($form['edit']['save']->isSubmittedBy()) { if ($form['edit']->isValid()) {