diff --git a/src/Controls/PasswordRevealInput.php b/src/Controls/PasswordRevealInput.php new file mode 100644 index 0000000..3714ff8 --- /dev/null +++ b/src/Controls/PasswordRevealInput.php @@ -0,0 +1,113 @@ +setHtmlType('password'); + $this->getControlPrototype()->appendAttribute('class', self::INPUT_CLASS); + } + + public function getControl(): Html + { + $input = parent::getControl(); + $input->value = $this->getRenderedValue(); + + $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) + ->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); + } +} diff --git a/src/DynamicContainer.php b/src/DynamicContainer.php index 91b03e7..df88c32 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,29 @@ 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 { + if (str_contains($name, static::NEW_PREFIX)) { + $this->newCount++; + } 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 +100,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 +136,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 +145,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), 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; + } } 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 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));