From 49c2cbaaa8fb866e67b337c72bf7a6be7a0e072c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Sat, 10 Jan 2026 14:08:59 +0100 Subject: [PATCH 1/8] Improves dynamic container functionality Enhances the dynamic container component by allowing for easier creation and management of static containers. This includes: - Using exceptions for clarity and error handling. - Improving default value setting by skipping when the form is not yet submitted. - Improving type safety. --- src/DynamicContainer.php | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/DynamicContainer.php b/src/DynamicContainer.php index 91b03e7..cadde7c 100644 --- a/src/DynamicContainer.php +++ b/src/DynamicContainer.php @@ -2,15 +2,15 @@ namespace ADT\Forms; +use Exception; use Nette; use Nette\Application\UI; use Nette\Application\UI\Presenter; -use Nette\Forms\Form; use Traversable; class DynamicContainer extends BaseContainer { - const NEW_PREFIX = '_new_'; + const string NEW_PREFIX = '_new_'; private StaticContainerFactory $staticContainerFactory; private bool $allowAdding = true; @@ -63,23 +63,26 @@ public function validate(?array $controls = NULL): void * @param string $name * @return Nette\ComponentModel\IComponent|null */ - protected function createComponent($name): ?Nette\ComponentModel\IComponent + protected function createComponent(string $name): ?Nette\ComponentModel\IComponent { return $this[$name] = $this->staticContainerFactory->create(); } - public function setStaticContainerFactory($staticContainerFactory) + public function setStaticContainerFactory($staticContainerFactory): static { $this->staticContainerFactory = $staticContainerFactory; return $this; } + /** + * @throws Exception + */ public function getTemplate(): StaticContainer { if (!$this->isAllowAdding()) { - throw new \Exception('Adding is not allowed.'); + throw new Exception('Adding is not allowed.'); } if (!$this->template) { @@ -94,12 +97,15 @@ public function createNew(): StaticContainer return $this[static::NEW_PREFIX . $this->newCount++]; } - /** - * Fill-in with values. - * @param array|object $data - * @return static - * @internal - */ + public function setDefaults(object|array $data, bool $erase = false): static + { + $form = $this->getForm(throw: false); + if (!$form || !$form->isAnchored() || !$form->isSubmitted()) { + return parent::setDefaults($data, $erase); + } + return $this; + } + public function setValues(object|array $values, bool $erase = false, bool $onlyDisabled = false): static { foreach ($values as $name => $value) { @@ -127,7 +133,7 @@ public function setAllowAdding(bool $allowAdding): self private function getHttpData(): ?array { - $path = explode(self::NAME_SEPARATOR, $this->lookupPath('Nette\Application\UI\Form')); + $path = explode(self::NameSeparator, $this->lookupPath('Nette\Application\UI\Form')); $allData = $this->getForm()->getHttpData(); return Nette\Utils\Arrays::get($allData, $path, NULL); } @@ -136,7 +142,7 @@ private function getHttpData(): ?array /** * @return StaticContainer[] */ - public function getContainers() + public function getContainers(): array { return array_filter( array_filter($this->getComponents(), fn($item) => $item instanceof StaticContainer), From f1f0a3d098259056772af1922b1849e32f6bfe43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Fri, 30 Jan 2026 14:00:48 +0100 Subject: [PATCH 2/8] Returns named components from section Ensures that components are returned as an associative array where keys are the component names and values are the components themselves. This provides more convenient access to the components. --- src/Section.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Section.php b/src/Section.php index 71f6a7a..9817b71 100644 --- a/src/Section.php +++ b/src/Section.php @@ -84,7 +84,11 @@ public function add(...$items): static public function getComponents(): array { - return $this->getControls(); + $components = []; + foreach ($this->getControls() as $control) { + $components[$control->getName()] = $control; + } + return $components; } public function getAncestorSections(): array From 9a751681140ecbc2063504aaa8b269d2b619818f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Sat, 31 Jan 2026 08:29:39 +0100 Subject: [PATCH 3/8] Adds form validation and data filtering Adds functionality to filter validated values from a form, excluding those with validation errors. Improves form data handling by providing a way to retrieve only trusted, validated data. The validation process checks for disabled controls, empty optional fields, and individual rule success, ensuring data integrity and reliability. --- src/Form.php | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/src/Form.php b/src/Form.php index dca5eb1..4026a92 100644 --- a/src/Form.php +++ b/src/Form.php @@ -5,6 +5,7 @@ use Nette; use Nette\Application\UI\Presenter; use Nette\Forms\Controls\BaseControl; +use stdClass; class Form extends Nette\Application\UI\Form { @@ -16,7 +17,7 @@ class Form extends Nette\Application\UI\Form const string GROUP_LEVEL_SEPARATOR = '-'; private ?BootstrapFormRenderer $renderer = null; - /** @var ControlGroup[] */ + /** @var Nette\Forms\ControlGroup[] */ public array $ancestorGroups = []; public function __construct(?Nette\ComponentModel\IContainer $parent = null, ?string $name = null) @@ -53,4 +54,92 @@ public function watchForSubmit(BaseControl $control): void { $control->setHtmlAttribute('data-adt-redraw-snippet', $this->addSubmit('submit')->getHtmlName()); } + + /** + * Returns only validated values from the form. + * Values from controls that have validation errors are excluded. + */ + public function getValidatedValues(string|object|bool|null $returnType = null): object|array + { + $allValues = $this->getUntrustedValues($returnType); + return $this->filterValidValues($this, $allValues); + } + + private function filterValidValues(Nette\Forms\Container $container, $values): array|stdClass + { + $result = is_array($values) ? [] : new stdClass; + $isArray = is_array($values); + + foreach ($container->getComponents() as $name => $component) { + $name = (string) $name; + + // Zkontroluj, jestli hodnota existuje + if ($isArray) { + if (!array_key_exists($name, $values)) { + continue; + } + $value = $values[$name]; + } else { + if (!property_exists($values, $name)) { + continue; + } + $value = $values->$name; + } + + if ($component instanceof Nette\Forms\Container) { + // Rekurzivně filtruj vnořený kontejner + $filtered = $this->filterValidValues($component, $value); + if (is_array($result)) { + $result[$name] = $filtered; + } else { + $result->$name = $filtered; + } + } elseif ($component instanceof BaseControl) { + // Zkontroluj, jestli je control validní + if ($this->isControlValid($component)) { + if (is_array($result)) { + $result[$name] = $value; + } else { + $result->$name = $value; + } + } + } + } + + return $result; + } + + private function isControlValid(BaseControl $control): bool + { + if ($control->isDisabled()) { + return false; + } + + $rules = $control->getRules(); + $emptyOptional = !$rules->isRequired() && !$control->isFilled(); + + return $this->validateBranch($rules, $emptyOptional); + } + + private function validateBranch(Nette\Forms\Rules $branch, bool $emptyOptional): bool + { + foreach ($branch as $rule) { + if (!$rule->branch && $emptyOptional && $rule->validator !== Nette\Forms\Form::Filled) { + continue; + } + + $success = $branch::validateRule($rule); + if (!$success && !$rule->branch) { + return false; + } + + if ($success && $rule->branch) { + if (!$this->validateBranch($rule->branch, $rule->validator === Nette\Forms\Form::Blank ? false : $emptyOptional)) { + return false; + } + } + } + + return true; + } } From e02508256caef4d92a2664338541ce6d083ce0bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Thu, 5 Feb 2026 07:49:55 +0100 Subject: [PATCH 4/8] Increments new component count if prefixed Increments the counter for dynamically created components only when the component's name includes the designated prefix. This ensures accurate tracking of newly instantiated components. --- src/DynamicContainer.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/DynamicContainer.php b/src/DynamicContainer.php index cadde7c..df88c32 100644 --- a/src/DynamicContainer.php +++ b/src/DynamicContainer.php @@ -65,6 +65,9 @@ public function validate(?array $controls = NULL): void */ protected function createComponent(string $name): ?Nette\ComponentModel\IComponent { + if (str_contains($name, static::NEW_PREFIX)) { + $this->newCount++; + } return $this[$name] = $this->staticContainerFactory->create(); } From 4a31ef2e889426508639da73e0213adddcba5aee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kud=C4=9Blka?= Date: Wed, 24 Jun 2026 17:20:12 +0200 Subject: [PATCH 5/8] Use getter for control name --- src/SectionTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SectionTrait.php b/src/SectionTrait.php index c1773fe..6cf2d8e 100644 --- a/src/SectionTrait.php +++ b/src/SectionTrait.php @@ -68,7 +68,7 @@ public function addSection(?callable $factory = null, ?string $name = null, ?Blo $redrawHandlers[] = $this->redrawHandlers[$_controlName]; } else { foreach ($section->getWatchForRedraw() as $_control) { - $_controlName = $_control->name; + $_controlName = $_control->getName(); if (!isset($this->redrawHandlers[$_controlName])) { $this->redrawHandlers[$_controlName] = $this->addSubmit('_redraw' . ucfirst($_controlName)); From bce30e9d044729bfa9d9b1156522ced0fa662d32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Konvi=C4=8Dka?= Date: Mon, 29 Jun 2026 10:48:32 +0200 Subject: [PATCH 6/8] =?UTF-8?q?feat:=20form=20prvek=20addPasswordReveal=20?= =?UTF-8?q?pro=20maskovan=C3=BD=20text=20s=20odkryt=C3=ADm?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Controls/PasswordRevealInput.php | 118 +++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 src/Controls/PasswordRevealInput.php diff --git a/src/Controls/PasswordRevealInput.php b/src/Controls/PasswordRevealInput.php new file mode 100644 index 0000000..9e79d36 --- /dev/null +++ b/src/Controls/PasswordRevealInput.php @@ -0,0 +1,118 @@ +setHtmlType('password'); + $this->getControlPrototype()->appendAttribute('class', self::INPUT_CLASS); + } + + public function getControl(): Html + { + $input = parent::getControl(); + + $button = Html::el('button') + ->setAttribute('type', 'button') + ->setAttribute('tabindex', '-1') + ->appendAttribute('class', 'btn btn-outline-secondary ' . self::TOGGLE_CLASS) + ->addHtml(Html::el('i')->setAttribute('class', 'fas fa-eye')); + + $group = Html::el('div') + ->setAttribute('class', 'input-group') + ->addHtml($input) + ->addHtml($button); + + if (!self::$scriptRendered) { + self::$scriptRendered = true; + $group->addHtml(self::getScript()); + } + + return $group; + } + + public static function addPasswordReveal(Container $container, string $name, $label = null): self + { + $component = new self($label); + $container->addComponent($component, $name); + return $component; + } + + public static function register(): void + { + Form::extensionMethod('addPasswordReveal', [self::class, 'addPasswordReveal']); + Container::extensionMethod('addPasswordReveal', [self::class, 'addPasswordReveal']); + } + + private static function getScript(): Html + { + $js = << self::INPUT_CLASS, + '{TOGGLE_CLASS}' => self::TOGGLE_CLASS, + ]); + + return Html::el('script')->setHtml($js); + } +} From a055e665614f10562c9b6a48764fe4c8ef502e05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Konvi=C4=8Dka?= Date: Mon, 29 Jun 2026 21:02:16 +0200 Subject: [PATCH 7/8] =?UTF-8?q?fix:=20vykresluj=20odkr=C3=BDvac=C3=AD=20sc?= =?UTF-8?q?ript=20u=20ka=C5=BEd=C3=A9ho=20controlu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Controls/PasswordRevealInput.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Controls/PasswordRevealInput.php b/src/Controls/PasswordRevealInput.php index 9e79d36..9d01666 100644 --- a/src/Controls/PasswordRevealInput.php +++ b/src/Controls/PasswordRevealInput.php @@ -14,8 +14,6 @@ class PasswordRevealInput extends TextInput public const string INPUT_CLASS = 'toggle-password-input'; public const string TOGGLE_CLASS = 'toggle-password-reveal'; - private static bool $scriptRendered = false; - public function __construct($label = null, ?int $maxLength = null) { parent::__construct($label, $maxLength); @@ -37,12 +35,8 @@ public function getControl(): Html $group = Html::el('div') ->setAttribute('class', 'input-group') ->addHtml($input) - ->addHtml($button); - - if (!self::$scriptRendered) { - self::$scriptRendered = true; - $group->addHtml(self::getScript()); - } + ->addHtml($button) + ->addHtml(self::getScript()); return $group; } From 9159cd4f2d87c1a38476ea88653437150f33e520 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Konvi=C4=8Dka?= Date: Mon, 29 Jun 2026 21:44:44 +0200 Subject: [PATCH 8/8] fix: vypisuj ulozenou hodnotu i pro type=password --- src/Controls/PasswordRevealInput.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Controls/PasswordRevealInput.php b/src/Controls/PasswordRevealInput.php index 9d01666..3714ff8 100644 --- a/src/Controls/PasswordRevealInput.php +++ b/src/Controls/PasswordRevealInput.php @@ -25,6 +25,7 @@ public function __construct($label = null, ?int $maxLength = null) public function getControl(): Html { $input = parent::getControl(); + $input->value = $this->getRenderedValue(); $button = Html::el('button') ->setAttribute('type', 'button')