diff --git a/composer.json b/composer.json
index 8f44086..9c80509 100644
--- a/composer.json
+++ b/composer.json
@@ -3,7 +3,7 @@
"type": "library",
"license": ["MIT"],
"require": {
- "php": ">=8.1",
+ "php": ">=8.3",
"nette/application": "^3.0",
"nette/forms": "^3.2.3",
"nette/utils": "^3.0 | ^4.0"
diff --git a/src/BaseContainer.php b/src/BaseContainer.php
index 3cbeb66..ff4caff 100644
--- a/src/BaseContainer.php
+++ b/src/BaseContainer.php
@@ -13,7 +13,7 @@ abstract class BaseContainer extends Container
// we have to create an IControl instance and call "addError" on it
// the control must not be an instance of "HiddenField"
// otherwise the error will be added to the form instead of the container
- const ERROR_CONTROL_NAME = '_containerError_';
+ const string ERROR_CONTROL_NAME = '_containerError_';
private array $options = [];
@@ -24,7 +24,7 @@ abstract class BaseContainer extends Container
* @param string|null $message
* @return static
*/
- public function setRequired(?string $message)
+ public function setRequired(?string $message): static
{
$this->requiredMessage = $message;
return $this;
@@ -43,7 +43,7 @@ protected function isRequired(): bool
}
- public function setOption($key, $value)
+ public function setOption($key, $value): static
{
if ($value === null) {
unset($this->options[$key]);
@@ -76,15 +76,21 @@ public function addError($message, bool $translate = true): void
public static function register(): void
{
Container::extensionMethod('addStaticContainer', function (Container $_this, string $name, Closure $factory, ?string $isFilledComponentName = null, ?string $isRequiredMessage = null) {
- return $_this[$name] = (new StaticContainerFactory($name, $factory, $isFilledComponentName))
+ $control = (new StaticContainerFactory($name, $factory, $isFilledComponentName))
->create()
->setRequired($isRequiredMessage);
+ $control->currentGroup = $_this->currentGroup;
+ $_this->currentGroup?->add($control);
+ return $_this[$name] = $control;
});
Container::extensionMethod('addDynamicContainer', function (Container $_this, string $name, Closure $factory, ?string $isFilledComponentName = null, ?string $isRequiredMessage = null) {
- return $_this[$name] = (new DynamicContainer)
+ $control = (new DynamicContainer)
->setStaticContainerFactory(new StaticContainerFactory($name, $factory, $isFilledComponentName))
->setRequired($isRequiredMessage);
+ $control->currentGroup = $_this->currentGroup;
+ $_this->currentGroup?->add($control);
+ return $_this[$name] = $control;
});
}
}
diff --git a/src/BaseForm.latte b/src/BaseForm.latte
new file mode 100644
index 0000000..484cef9
--- /dev/null
+++ b/src/BaseForm.latte
@@ -0,0 +1,94 @@
+{if $templateFile}
+ {import $templateFile}
+{/if}
+
+{varType ADT\Forms\Form $form}
+
+{define errors}
+ {$control['form']->getRenderer()->renderErrors()|noescape}
+{/define}
+
+{define renderEl}
+ {if $el instanceof ADT\Forms\Section}
+ {varType ADT\Forms\Section $el}
+
getName()}id="{$el->getHtmlId()}"{/if}>
+ {if $el->getOption('redrawHandlers')}
+ {if $control->isControlInvalid($el->getHtmlId())}
+
+ {include renderSection, section => $el}
+
+ {else}
+
+ {include renderSection, section => $el}
+
+ {/if}
+
+ {foreach $el->getOption('redrawHandlers') as $_el}
+ {include renderControl control => $_el}
+ {/foreach}
+ {else}
+ {include renderSection, section => $el}
+ {/if}
+
+ {else}
+ {varType Nette\ComponentModel\Component $el}
+ {var $blockName = 'component-' . $el->lookupPath(Nette\Forms\Form::class)}
+ {ifset #$blockName}
+ {include #$blockName, component => $el}
+ {else}
+ {if $el instanceof ADT\Forms\StaticContainer}
+ {varType ADT\Forms\StaticContainer $el}
+ {foreach $el->getElements() as $_el}
+ {include renderEl, el => $_el}
+ {/foreach}
+ {else}
+ {include renderControl, control => $el}
+ {/if}
+ {/ifset}
+ {/if}
+{/define}
+
+{define renderSection}
+ {varType ADT\Forms\Section $section}
+ {var $blockName = 'section-' . $section->getName()}
+ {ifset #$blockName}
+ {include #$blockName, section => $section}
+ {elseif $section->getOption('blockName')}
+ {var $blockName = $section->getOption('blockName')}
+ {include #$blockName, section => $section}
+ {else}
+ {foreach $section->getElements() as $_el}
+ {include renderEl, el => $_el}
+ {/foreach}
+ {/ifset}
+{/define}
+
+{define renderControl}
+ {if !$control->getOption('rendered')}
+ {if $control instanceof \Nette\Forms\Controls\SubmitButton && !$control->getCaption() || $control instanceof Nette\Forms\Controls\HiddenField}
+ {input $control hidden => true}
+ {else}
+ {formPair $control}
+ {/if}
+ {/if}
+{/define}
+
+{define renderForm}
+
+ {form form}
+ {include errors}
+
+ {foreach $form->getElements() as $_el}
+ {include renderEl el => $_el}
+ {/foreach}
+ {/form}
+
+{/define}
+
+{snippetArea formArea}
+ {ifset #form}
+ {include #form}
+ {else}
+ {include renderForm}
+ {/ifset}
+{/snippetArea}
diff --git a/src/BaseForm.php b/src/BaseForm.php
index e1d38a2..f46e82e 100644
--- a/src/BaseForm.php
+++ b/src/BaseForm.php
@@ -5,14 +5,16 @@
use Exception;
use Nette\Application\UI\Control;
use Nette\Application\UI\Presenter;
+use Nette\Forms\Controls\BaseControl;
+use Nette\Forms\SubmitterControl;
use Nette\Utils\ArrayHash;
use Nette\Utils\Callback;
-use Nette\Utils\Reflection;
use Nette\Utils\Type;
+use ReflectionClass;
use ReflectionException;
+use ReflectionParameter;
/**
- * @property-read Form $form
* @method onBeforeInitForm($form)
* @method onAfterInitForm($form)
* @method onBeforeValidateForm($form)
@@ -21,17 +23,9 @@
*/
abstract class BaseForm extends Control
{
- /** @var Form */
- protected $form;
-
- /** @var string|null */
- public ?string $templateFilename = null;
-
- /** @var bool */
- public bool $isAjax = true;
-
- /** @var bool */
- public bool $emptyHiddenToggleControls = false;
+ protected Form $form;
+ protected bool $isAjax = true;
+ protected bool $emptyHiddenToggleControls = true;
/** @var callable[] */
protected array $paramResolvers = [];
@@ -68,7 +62,7 @@ abstract class BaseForm extends Control
public function __construct()
{
- $this->paramResolvers[] = function(string $type, $values = null) {
+ $this->paramResolvers[] = function(string $type, object|array|null $values) {
if ($type === Form::class || is_subclass_of($type, Form::class)) {
return $this->form;
} elseif ($type === Presenter::class || is_subclass_of($type, Presenter::class)) {
@@ -77,7 +71,7 @@ public function __construct()
if ($type === ArrayHash::class) {
return $values;
} elseif ($type === 'array') {
- return (array) $values;
+ return $this->convertArrayHashToArray($values);
}
}
@@ -85,7 +79,7 @@ public function __construct()
};
$this->monitor(Presenter::class, function() {
- $form = $this->form = $this['form'] = $this->createComponentForm();
+ $form = $this->form = $this['form'];
/** @link BaseForm::validateFormCallback() */
$form->onValidate[] = [$this, 'validateFormCallback'];
@@ -114,7 +108,7 @@ public function __construct()
if ($form->isSubmitted()) {
if (is_bool($form->isSubmitted()) || $form->isSubmitted()->isDisabled()) {
- $form->setSubmittedBy(null);
+ $form->addError('The form must be submitted using the specific submit button.');
}
elseif ($form->isSubmitted()->getValidationScope() !== null) {
$form->onValidate = [];
@@ -128,10 +122,22 @@ public function __construct()
*/
final public function validateFormCallback(Form $form): void
{
+ // pridal jsem kvuli sobit pokladne, kde jsme meli ajax select a kde nam to na
+ // $validItems = $this->getAjaxEntity()->formatValues($this->getAjaxEntity()->hydrateValues($validValues, $this->getForm()->getValues('array')));
+ // hazelo
+ // Nette\Forms\Container::getValues() invoked but the form is not valid (form 'form')
+ // primarni problem je ten, ze $form->getUntrustedValues() vraci hodnoty jen z inputu
+ // ktere uz jsou vykreslene a tudiz tam jde treba jen pulka hodnot
+ // aby nemusely byt submit buttony definovane pred tim ajax selectem, tak jeste pridavame
+ // !$form->isSubmitted() instanceof SubmitterControl::class
+ if (!$form->isSubmitted() instanceof SubmitterControl || $form->isSubmitted()->getValidationScope() !== null) {
+ return;
+ }
+
$this->onBeforeValidateForm($form);
if ($form->isValid() && method_exists($this, 'validateForm')) {
- $this->invokeHandler([$this, 'validateForm'], $form->getUnsafeValues(null));
+ $this->invokeHandler([$this, 'validateForm'], $form->getUntrustedValues());
}
}
@@ -166,34 +172,20 @@ final public function processFormCallback(Form $form): void
*/
public function render(): void
{
- $this->template->setFile(__DIR__ . DIRECTORY_SEPARATOR . 'form.latte');
-
- $customTemplatePath = (
- (!empty($this->templateFilename))
- ? $this->templateFilename
- : str_replace('.php', '.latte', $this->getReflection()->getFileName())
- );
-
- if (file_exists($customTemplatePath)) {
- $this->template->customTemplatePath = $customTemplatePath;
- }
-
if ($this->isAjax) {
$this->form->getElementPrototype()->class[] = 'ajax';
}
- if ($this->presenter->isAjax()) {
- $this->redrawControl('formArea');
- }
-
if (method_exists($this, 'renderForm')) {
$this->invokeHandler([$this, 'renderForm']);
}
- $this->template->render();
+ $this->getTemplate()->templateFile = $this->getTemplateFile();
+ $this->getTemplate()->setFile(static::getDefaultTemplateFile());
+ $this->getTemplate()->render();
}
- protected function createComponentForm(): Form
+ protected function createComponentForm()
{
return new Form();
}
@@ -203,31 +195,31 @@ protected function _()
return call_user_func_array([$this->form->getTranslator(), 'translate'], func_get_args());
}
- public function setOnBeforeInitForm(callable $onBeforeInitForm)
+ public function setOnBeforeInitForm(callable $onBeforeInitForm): static
{
$this->onBeforeInitForm[] = $onBeforeInitForm;
return $this;
}
- public function setOnAfterInitForm(callable $onAfterInitForm)
+ public function setOnAfterInitForm(callable $onAfterInitForm): static
{
$this->onAfterInitForm[] = $onAfterInitForm;
return $this;
}
- public function setOnBeforeValidateForm(callable $onBeforeValidateForm)
+ public function setOnBeforeValidateForm(callable $onBeforeValidateForm): static
{
$this->onBeforeValidateForm[] = $onBeforeValidateForm;
return $this;
}
- public function setOnBeforeProcessForm(callable $onBeforeProcessForm)
+ public function setOnBeforeProcessForm(callable $onBeforeProcessForm): static
{
$this->onBeforeProcessForm[] = $onBeforeProcessForm;
return $this;
}
- public function setOnSuccess(callable $onSuccess)
+ public function setOnSuccess(callable $onSuccess): static
{
$this->onSuccess[] = $onSuccess;
return $this;
@@ -237,9 +229,9 @@ public function setOnSuccess(callable $onSuccess)
* @throws ReflectionException
* @throws Exception
*/
- private function invokeHandler($handler, $formValues = null)
+ protected function invokeHandler(callable $handler, object|array|null $formValues = null)
{
- $types = array_map(function(\ReflectionParameter $param) {
+ $types = array_map(function(ReflectionParameter $param) {
return Type::resolve($param->getType()->getName(), $param);
}, Callback::toReflection($handler)->getParameters());
@@ -262,36 +254,92 @@ private function invokeHandler($handler, $formValues = null)
}
}
- $handler(...$params);
+ return $handler(...$params);
}
/**
- * @deprecated Use $this->form instead
- * @return Form
+ * @throws Exception
*/
- public function getForm()
- {
- return $this['form'];
- }
-
- protected function processToggles(Form $form, bool $emptyValue)
+ protected function processToggles(Form $form, bool $emptyValue): void
{
if ($this->emptyHiddenToggleControls) {
$toggles = $form->getToggles();
- foreach ($form->getGroups() as $_group) {
- $toggleName = '';
- foreach (explode('_', (string)$_group->getOption('label')) as $_togglePart) {
- $toggleName = trim($toggleName . '_' . $_togglePart, '_');
- if (isset($toggles[$toggleName]) && $toggles[$toggleName] === false) {
- foreach ($_group->getControls() as $_control) {
- $_control->setOption('hidden', true);
- if ($emptyValue) {
- $_control->setValue(null);
+ foreach ($this->getSections() as $_section) {
+ if (isset($toggles[$_section->getHtmlId()]) && $toggles[$_section->getHtmlId()] === false) {
+ foreach ($_section->getControls() as $_control) {
+ if (!$_control instanceof BaseControl) {
+ continue;
+ }
+ $_control->setOption('hidden', true);
+ if ($emptyValue) {
+ if (method_exists($_control, 'setNullable')) {
+ $_control->setNullable();
}
+ $_control->setValue(null);
}
}
}
}
}
}
-}
+
+ /**
+ * @return Section[]
+ */
+ protected function getSections(): array
+ {
+ $sections = [];
+ foreach ($this->form->getSections() as $_section) {
+ $sections[] = $_section;
+ }
+ foreach ($this->form->getComponentTree() as $_component) {
+ if (!$_component instanceof StaticContainer) {
+ continue;
+ }
+ foreach ($_component->getSections() as $_section) {
+ $sections[] = $_section;
+ }
+
+ }
+ return $sections;
+ }
+
+ protected function getTemplateFile(): ?string
+ {
+ $reflectionClass = new ReflectionClass($this);
+ $templateName = $reflectionClass->getShortName() .'.latte';
+
+ $templateFile = dirname($reflectionClass->getFileName()) . '/' . $templateName;
+ if (file_exists($templateFile)) {
+ return $templateFile;
+ }
+
+ return null;
+ }
+
+ protected function convertArrayHashToArray($data)
+ {
+ if ($data instanceof ArrayHash) {
+ $data = (array) $data;
+ }
+
+ if (is_array($data)) {
+ foreach ($data as $key => $value) {
+ $data[$key] = $this->convertArrayHashToArray($value);
+ }
+ }
+
+ return $data;
+ }
+
+ public static function getDefaultTemplateFile(): string
+ {
+ return __DIR__ . '/BaseForm.latte';
+ }
+
+ public function redrawControl(?string $snippet = null, bool $redraw = true): void
+ {
+ parent::redrawControl('formArea');
+ parent::redrawControl($snippet, $redraw);
+ }
+}
\ No newline at end of file
diff --git a/src/BlockName.php b/src/BlockName.php
new file mode 100644
index 0000000..2c5cc80
--- /dev/null
+++ b/src/BlockName.php
@@ -0,0 +1,8 @@
+getOption('type');
if ($control instanceof Nette\Forms\Controls\Button) {
$control->renderAsButton();
-
+
if ($control->getValidationScope() !== null) {
$control->getControlPrototype()->addClass('btn btn-outline-secondary');
} else {
@@ -296,7 +296,16 @@ protected static function bootstrap4(Nette\Forms\Container $container): void
}
} elseif (in_array($type, ['checkbox', 'radio'], true)) {
- if ($control instanceof Nette\Forms\Controls\Checkbox) {
+
+ if ($control->getOption('switch') === true) {
+ $control->getContainerPrototype()
+ ->setName('div')
+ ->addClass('form-check form-switch');
+
+ $control->getControlPrototype()
+ ->addClass('form-check-input');
+ }
+ elseif ($control instanceof Nette\Forms\Controls\Checkbox) {
$control->getLabelPrototype()->addClass('form-check-label');
} else {
$control->getItemLabelPrototype()->addClass('form-check-label');
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/ElementsTrait.php b/src/ElementsTrait.php
new file mode 100644
index 0000000..121145c
--- /dev/null
+++ b/src/ElementsTrait.php
@@ -0,0 +1,55 @@
+elements === null) {
+ $this->elements = $this->buildElements();
+ }
+ return $this->elements;
+ }
+
+ private function buildElements(): array
+ {
+ $result = [];
+
+ foreach ($this->getComponents() as $component) {
+ if ($component->getOption('redrawHandler') === true) {
+ continue;
+ }
+
+ if ($component instanceof Container) {
+ $section = $component->getCurrentGroup();
+ } else {
+ $section = $component->getOption('section');
+ }
+
+ if ($section === null || $section === $this) {
+ $result[] = $component;
+ }
+ }
+
+ foreach ($this->sections as $_section) {
+ if (!$_section->getOption('insertAfter')) {
+ array_unshift($result, $_section);
+ } else {
+ foreach ($result as $key => $el) {
+ if ($el === $_section->getOption('insertAfter')) {
+ array_splice($result, $key + 1, 0, [$_section]);
+ break;
+ }
+ }
+ }
+ }
+
+ return $result;
+ }
+}
\ No newline at end of file
diff --git a/src/Form.php b/src/Form.php
index 49c31de..4026a92 100644
--- a/src/Form.php
+++ b/src/Form.php
@@ -4,19 +4,27 @@
use Nette;
use Nette\Application\UI\Presenter;
+use Nette\Forms\Controls\BaseControl;
+use stdClass;
-class Form extends \Nette\Application\UI\Form
+class Form extends Nette\Application\UI\Form
{
use AnnotationsTrait;
use GetComponentTrait;
+ use SectionTrait;
+ use ElementsTrait;
+
+ const string GROUP_LEVEL_SEPARATOR = '-';
private ?BootstrapFormRenderer $renderer = null;
+ /** @var Nette\Forms\ControlGroup[] */
+ public array $ancestorGroups = [];
public function __construct(?Nette\ComponentModel\IContainer $parent = null, ?string $name = null)
{
parent::__construct($parent, $name);
- $this->monitor(Presenter::class, function($presenter) {
+ $this->monitor(Presenter::class, function() {
// must be called here because onError and onRender callbacks are set in the constructor
$this->getRenderer();
});
@@ -41,4 +49,97 @@ public function addError($message, bool $translate = true): void
}
parent::addError($message, false);
}
+
+ 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
new file mode 100644
index 0000000..9817b71
--- /dev/null
+++ b/src/Section.php
@@ -0,0 +1,120 @@
+parent = $parent;
+ $this->ancestorSections = $ancestorSections;
+ $this->name = $name;
+ }
+
+ public function addSection(Container $parent, array $ancestorSections, ?string $name): Section
+ {
+ $this->sections[] = $section = new Section($parent, $ancestorSections, $name);
+ return $section;
+ }
+
+ public function getName(): ?string
+ {
+ return $this->name;
+ }
+
+ public function getParent(): Container
+ {
+ return $this->parent;
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function getHtmlId(): string
+ {
+ if ($this->name === null) {
+ throw new Exception('Section name is not set.');
+ }
+
+ if ($this->parent instanceof Form) {
+ return $this->name;
+ }
+
+ return $this->parent->lookupPath(Form::class) . Form::GROUP_LEVEL_SEPARATOR . $this->name;
+ }
+
+ public function add(...$items): static
+ {
+ foreach ($items as $item) {
+ if ($item instanceof Control) {
+ $item->setOption('section', $this);
+ $this->controls[$item] = null;
+
+ } elseif ($item instanceof Container) {
+ if ($item->getParent() instanceof DynamicContainer) {
+ continue;
+ }
+ $this->controls[$item] = null;
+
+ } else {
+ $type = get_debug_type($item);
+ throw new InvalidArgumentException("Control or Container items expected, $type given.");
+ }
+ }
+
+ return $this;
+ }
+
+ public function getComponents(): array
+ {
+ $components = [];
+ foreach ($this->getControls() as $control) {
+ $components[$control->getName()] = $control;
+ }
+ return $components;
+ }
+
+ public function getAncestorSections(): array
+ {
+ return $this->ancestorSections;
+ }
+
+ public function getWatchForRedraw(): array|SubmitterControl
+ {
+ return $this->watchForRedraw;
+ }
+
+ public function setWatchForRedraw(array|SubmitterControl $watchForRedraw): static
+ {
+ $this->watchForRedraw = $watchForRedraw;
+ return $this;
+ }
+
+ public function getValidationScope(): array
+ {
+ return $this->validationScope;
+ }
+
+ public function setValidationScope(array $validationScope): static
+ {
+ $this->validationScope = $validationScope;
+ return $this;
+ }
+}
\ No newline at end of file
diff --git a/src/SectionTrait.php b/src/SectionTrait.php
new file mode 100644
index 0000000..6cf2d8e
--- /dev/null
+++ b/src/SectionTrait.php
@@ -0,0 +1,106 @@
+getComponents()) as $_component) {
+ // redrawHandlery jsou umele vlozene, takze nechceme, aby ovlivnovali poradi
+ if ($_component->getOption('redrawHandler') === true) {
+ continue;
+ }
+
+ $lastComponent = $_component;
+ break;
+ }
+ $insertAfter = $this->lastSection?->getOption('insertAfter') !== $lastComponent && ($lastComponent instanceof Container ? $lastComponent->getCurrentGroup() : $lastComponent->getOption('section')) === $this->getCurrentGroup() ? $lastComponent : $this->lastSection;
+ if ($this->getCurrentGroup()) {
+ $section = $this->getCurrentGroup()->addSection($this, $this->getForm()->ancestorGroups, $name);
+ } else {
+ $section = new Section($this, $this->getForm()->ancestorGroups, $name);
+ $this->sections[] = $section;
+ }
+ if ($name) {
+ if (!preg_match(self::NameRegexp, $name)) {
+ throw new InvalidArgumentException("Component name must be non-empty alphanumeric string, '$name' given.");
+ }
+ if (isset($this->allSections[$name])) {
+ throw new Exception("Section $name already exists.");
+ }
+ $this->allSections[$name] = $section;
+ }
+ $this->setCurrentGroup($section);
+ $this->getForm()->ancestorGroups[] = $section;
+ $section->setOption('insertAfter', $insertAfter);
+ $section->setOption('blockName', $blockName?->getName());
+ $section->setWatchForRedraw($watchForRedraw);
+ $section->setValidationScope($validationScope);
+ $factory && $factory($section);
+ $this->lastSection = $section;
+ array_pop($this->getForm()->ancestorGroups);
+ $this->setCurrentGroup($this->getForm()->ancestorGroups ? end($this->getForm()->ancestorGroups) : null);
+
+ if ($section->getWatchForRedraw()) {
+ $redrawHandlers = [];
+
+ if ($section->getWatchForRedraw() instanceof SubmitterControl) {
+ $_controlName = $section->getWatchForRedraw()->getName();
+ $this->redrawHandlers[$_controlName] = $section->getWatchForRedraw();
+ $redrawHandlers[] = $this->redrawHandlers[$_controlName];
+ } else {
+ foreach ($section->getWatchForRedraw() as $_control) {
+ $_controlName = $_control->getName();
+
+ if (!isset($this->redrawHandlers[$_controlName])) {
+ $this->redrawHandlers[$_controlName] = $this->addSubmit('_redraw' . ucfirst($_controlName));
+ }
+ if ($this->redrawHandlers[$_controlName]->getValidationScope() !== null) {
+ $this->redrawHandlers[$_controlName]->setValidationScope(array_merge($this->redrawHandlers[$_controlName]->getValidationScope(), $section->getValidationScope()));
+ } else {
+ $this->redrawHandlers[$_controlName]->setValidationScope($section->getValidationScope());
+ }
+ $this->redrawHandlers[$_controlName]->setOption('redrawHandler', true);
+ $this->redrawHandlers[$_controlName]->onClick[] = function () use ($onRedraw, $section) {
+ $onRedraw && $onRedraw();
+ $this->getForm()->getParent()->redrawControl($section->getHtmlId());
+ };
+
+ $_control->setHtmlAttribute('data-adt-redraw-snippet', $this->redrawHandlers[$_controlName]->getHtmlName());
+
+ $redrawHandlers[] = $this->redrawHandlers[$_controlName];
+ }
+ }
+
+ $section->setOption('redrawHandlers', $redrawHandlers);
+ }
+
+ return $section;
+ }
+
+ /**
+ * @return Section[]
+ */
+ public function getSections(): array
+ {
+ return $this->allSections;
+ }
+}
\ No newline at end of file
diff --git a/src/StaticContainer.php b/src/StaticContainer.php
index 2203256..6c4ddcd 100644
--- a/src/StaticContainer.php
+++ b/src/StaticContainer.php
@@ -5,10 +5,13 @@
use Closure;
use Nette\Application\UI\Presenter;
use Nette\Forms\Controls\BaseControl;
+use Nette\Http\FileUpload;
class StaticContainer extends BaseContainer
{
use GetComponentTrait;
+ use SectionTrait;
+ use ElementsTrait;
private ?BaseControl $isFilledComponent = null;
private bool $isTemplate = false;
@@ -53,16 +56,16 @@ public function setIsFilledComponent(BaseControl $isFilledComponent): self
}
- public function isEmpty($excludeIsFilledComponent = false): bool
+ public function isEmpty(bool $excludeIsFilledComponent = false): bool
{
- // we don't want to validate the controls, just check if they are empty or not
+ // we don't want to validate the controls, just check if they are empty, or not
// getValues causes a loop
- $values = $this->getUnsafeValues('array');
+ $values = $this->getUntrustedValues('array');
if ($excludeIsFilledComponent) {
unset($values[$this->getIsFilledComponent()->getName()]);
}
foreach ($values as &$_value) {
- if ($_value instanceof Nette\Http\FileUpload && !$_value->isOk()) {
+ if ($_value instanceof FileUpload && !$_value->isOk()) {
$_value = null;
}
}
diff --git a/src/form.latte b/src/form.latte
deleted file mode 100644
index 597e2fd..0000000
--- a/src/form.latte
+++ /dev/null
@@ -1,72 +0,0 @@
-{define errors}
- {$control['form']->getRenderer()->renderErrors()|noescape}
-{/define}
-
-{define renderContainer}
- {if $container instanceof Nette\Forms\Container}
- {var $form = $container->getForm()}
- {/if}
- {if $container instanceof Nette\Forms\Form && $container->getGroups()}
- {foreach $container->getGroups() as $_group}
- {continueIf !$_group->getOption('visual')}
-
- {include renderGroup form => $container, group => $_group}
- {/foreach}
- {else}
- {foreach $container->getControls() as $c}
- {continueIf $c instanceof \Nette\Forms\Controls\HiddenField}
- {continueIf $c->getOption('rendered')}
-
- {if $c instanceof \Nette\Forms\Container}
- {include renderContainer, container => $c}
- {else}
- {if $c instanceof \Nette\Forms\Controls\SubmitButton && !$c->getCaption()}
- {input $c hidden => true}
- {else}
- {formPair $c}
- {/if}
- {/if}
- {/foreach}
- {/if}
-{/define}
-
-{define renderGroup}
- {if is_string($group)}
- {var $group = $form->getGroup($group)}
- {/if}
-
- {var $groupName = (string) $group->getOption('label')}
- {if str_starts_with((string) $group->getOption('label'), 'snippet-')}
- {var $snippetName = explode('-', $group->getOption('label'))}
- {var $snippetName = end($snippetName)}
-
- {include renderContainer container => $group}
-
- {else}
-
-
- {include renderContainer container => $group}
-
- {foreach $form->getGroups() as $_group}
- {var $_groupName = (string) $_group->getOption('label')}
- {if str_starts_with($_groupName, $groupName . '_')}
- {include renderGroup form => $container, group => $_group}
- {/if}
- {/foreach}
-
- {/if}
-
- {php $group->setOption('visual', false)}
-{/define}
-
-{snippetArea formArea}
- {ifset $customTemplatePath}
- {include $customTemplatePath with blocks}
- {else}
- {form form}
- {include errors}
-
- {include renderContainer container => $form}
- {/form}
- {/ifset}
-{/snippetArea}