diff --git a/src/TwiGrid/Components/Action.php b/src/TwiGrid/Components/Action.php index 96b827c..5bc2c25 100644 --- a/src/TwiGrid/Components/Action.php +++ b/src/TwiGrid/Components/Action.php @@ -51,7 +51,7 @@ public function getCallback(): callable public function setConfirmation(string $confirmation = null): self { - $this->confirmation = strlen($confirmation) ? (string) $confirmation : null; + $this->confirmation = $confirmation === '' ? null : $confirmation; return $this; } diff --git a/src/TwiGrid/DataGrid.php b/src/TwiGrid/DataGrid.php index 3cf1223..b84409a 100644 --- a/src/TwiGrid/DataGrid.php +++ b/src/TwiGrid/DataGrid.php @@ -15,6 +15,7 @@ use TwiGrid\Components\Action; use TwiGrid\Components\Column; use TwiGrid\Components\RowAction; +use Nette\ComponentModel\IContainer; use Nette\Utils\Callback as NCallback; use Nette\Bridges\ApplicationLatte\Template; use Nette\Application\UI\Control as NControl; @@ -143,7 +144,7 @@ class DataGrid extends NControl // === AJAX PAYLOAD =========== - /** @var \stdClass|null */ + /** @var \stdClass */ private $payload; @@ -318,9 +319,15 @@ public function getColumns(): ?\ArrayIterator public function hasManySortableColumns(): bool { + $columns = $this->getColumns(); + + if (!$columns) { + return false; + } + $hasMany = false; - foreach ($this->getColumns() as $column) { + foreach ($columns as $column) { if ($column->isSortable()) { if ($hasMany) { // 2nd sortable -> has many return true; @@ -361,7 +368,7 @@ public function handleRowAction(string $name, string $primary, string $token = n { $action = $this['rowActions']->getComponent($name); - if (!$action->isProtected() || Helpers::checkCsrfToken($this->session, $token)) { + if (!$action->isProtected() || ($token && Helpers::checkCsrfToken($this->session, $token))) { $action->invoke($this->getRecordHandler()->findIn($primary, $this->getData())); $this->refreshState(); $this->redraw(true, true, ['body', 'footer']); @@ -518,13 +525,20 @@ public function setDataLoader(callable $loader): self /** @return array|\Traversable */ public function getData() { + if (!$this->dataLoader) { + throw new \LogicException('Data loader not set.'); + } + if ($this->data === null) { $order = $this->orderBy; $primaryDir = count($order) ? end($order) : Components\Column::ASC; + $primaryKey = $this->getRecordHandler()->getPrimaryKey(); - foreach ($this->getRecordHandler()->getPrimaryKey() as $column) { - if (!isset($order[$column])) { - $order[$column] = $primaryDir; + if ($primaryKey) { + foreach ($primaryKey as $column) { + if (!isset($order[$column])) { + $order[$column] = $primaryDir; + } } } @@ -548,7 +562,8 @@ public function getData() public function hasData(): bool { - return (bool) count($this->getData()); + $data = $this->getData(); + return count(is_array($data) ? $data : iterator_to_array($data)) > 0; } @@ -659,6 +674,10 @@ public function getItemCount(): ?int { if ($this->itemCount === null) { if ($this->itemCounter === null) { // fallback - fetch data with empty filters + if (!$this->dataLoader) { + throw new \LogicException('Data loader not set.'); + } + $data = NCallback::invoke($this->dataLoader, $this->filters, [], null, 0); if ($data instanceof NSelection) { @@ -692,8 +711,14 @@ protected function createComponentForm(): Form $form = new Form($this->getRecordHandler()); $form->addProtection(); $form->setTranslator($this->getTranslator()); - $form->onSuccess[] = [$this, 'processForm']; - $form->onSubmit[] = [$this, 'formSubmitted']; + + $form->onSuccess[] = function (\Nette\Forms\Form $form): void { + $this->processForm($form); + }; + + $form->onSubmit[] = function (\Nette\Forms\Form $form): void { + $this->formSubmitted($form); + }; return $form; } @@ -759,21 +784,25 @@ public function addPaginationControls(): self { if ($this->itemsPerPage !== null) { $this->initPagination(); - $this['form']->addPaginationControls($this->page, $this->pageCount); + $this['form']->addPaginationControls($this->page, (int) $this->pageCount); } return $this; } - public function formSubmitted(Form $form): void + public function formSubmitted(\Nette\Forms\Form $form): void { $this->redraw(false, false, ['form-errors']); } - public function processForm(Form $form): void + public function processForm(\Nette\Forms\Form $form): void { + if (!$form instanceof Form) { + throw new \LogicException('Invalid form instance.'); + } + // detect submit button by lazy buttons appending (beginning with the most lazy ones) $this->addFilterButtons(); @@ -792,7 +821,11 @@ public function processForm(Form $form): void if ($button instanceof NSubmitButton) { $name = $button->getName(); - $path = $button->getParent()->lookupPath(Form::class); + + /** @var IContainer $parent */ + $parent = $button->getParent(); + + $path = $parent->lookupPath(Form::class); if ("$path-$name" === 'filters-buttons-filter') { $this->addFilterCriteria(); @@ -837,7 +870,14 @@ public function processForm(Form $form): void $values = $form->getInlineValues(); if ($values !== null) { - NCallback::invoke($this->ieProcessCallback, $this->getRecordHandler()->findIn($this->iePrimary, $this->getData()), $values); + if ($this->iePrimary !== null) { + if (!$this->ieProcessCallback) { + throw new \LogicException('Inline edit callback not set.'); + } + + NCallback::invoke($this->ieProcessCallback, $this->getRecordHandler()->findIn($this->iePrimary, $this->getData()), $values); + } + $this->deactivateInlineEditing(); } @@ -845,7 +885,11 @@ public function processForm(Form $form): void $this->deactivateInlineEditing(false); } else { - $this->activateInlineEditing($button->getName()); + $primary = $button->getName(); + + if ($primary) { + $this->activateInlineEditing($primary); + } } } } @@ -902,7 +946,7 @@ public function render(): void $latte->addProvider('formsStack', [$form]); } - $template->columns = $this->getColumns(); + $template->columns = $columns = $this->getColumns(); $template->dataLoader = [$this, 'getData']; $template->recordVariable = $this->recordVariable; @@ -919,7 +963,7 @@ public function render(): void $template->isPaginated = $this->itemsPerPage !== null; - $template->columnCount = count($template->columns) + $template->columnCount = ($columns ? count($columns) : 0) + ($template->hasGroupActions ? 1 : 0) + ($template->hasFilters || $template->hasRowActions || $template->hasInlineEdit ? 1 : 0); diff --git a/src/TwiGrid/Form.php b/src/TwiGrid/Form.php index 33f39fd..b8a5809 100644 --- a/src/TwiGrid/Form.php +++ b/src/TwiGrid/Form.php @@ -75,7 +75,11 @@ public function addGroupActionCheckboxes(): self { if ($this->lazyCreateContainer('actions', 'records', $records)) { $first = true; - foreach ($this->getParent()->getData() as $record) { + + /** @var DataGrid $grid */ + $grid = $this->getParent(); + + foreach ($grid->getData() as $record) { $hash = $this->recordHandler->getPrimaryHash($record); $records[$hash] = $checkbox = new Checkbox; @@ -206,11 +210,20 @@ protected function lazyCreateContainer(string $parent, string $name, NContainer public static function validateCheckedCount(Checkbox $checkbox): bool { + /** @var Form $form */ + $form = $checkbox->getForm(); + /** @var Button $button */ - $button = $checkbox->getForm()->isSubmitted(); + $button = $form->isSubmitted(); + + /** @var NContainer $buttonParent */ + $buttonParent = $button->getParent(); + + /** @var NContainer $checkboxParent */ + $checkboxParent = $checkbox->getParent(); - return $button->getParent()->lookupPath(NForm::class) !== 'actions-buttons' - || in_array(true, $checkbox->getParent()->getValues(true), true); + return $buttonParent->lookupPath(NForm::class) !== 'actions-buttons' + || in_array(true, (array) $checkboxParent->getValues('array'), true); } } diff --git a/src/TwiGrid/RecordHandler.php b/src/TwiGrid/RecordHandler.php index c50a4c1..88cb8b5 100644 --- a/src/TwiGrid/RecordHandler.php +++ b/src/TwiGrid/RecordHandler.php @@ -40,7 +40,7 @@ public function getPrimaryKey(): ?array } - public function setValueGetter(callable $callback): self + public function setValueGetter(?callable $callback): self { $this->valueGetter = $callback; return $this; @@ -87,6 +87,10 @@ public function getValue($record, string $column, bool $need = true) */ public function getPrimary($record): array { + if (!$this->primaryKey) { + throw new \LogicException('Primary key not set.'); + } + $primaries = []; foreach ($this->primaryKey as $column) { $primaries[$column] = (string) $this->getValue($record, $column);