diff --git a/app/Components/TeamForm.php b/app/Components/TeamForm.php index f084f47..fb73924 100644 --- a/app/Components/TeamForm.php +++ b/app/Components/TeamForm.php @@ -9,13 +9,12 @@ use App\Model\Configuration\Fields; use App\Model\Configuration\Fields\Field; use App\Model\InputModifier; +use Contributte\FormMultiplier\Multiplier; use Contributte\Translation\Wrappers\Message; use Contributte\Translation\Wrappers\NotTranslate; -use Kdyby\Replicator\Container as ReplicatorContainer; use Nette\Application\UI; use Nette\Forms\Container; use Nette\Forms\Controls; -use Nette\Forms\Controls\SubmitButton; use Nette\Localization\Translator; use Nette\Utils\Json; use Nextras\FormComponents\Controls\DateControl; @@ -46,7 +45,8 @@ public function __construct( $group->setOption('container', 'div aria-hidden="true" class="visually-hidden"'); // Browsers consider the first submit button a default submit button for use when submitting the form using Enter key. // Let’s add the save button to the top, to prevent the remove button of the first container from being picked. - $this->addSubmit('save_default_submit', 'messages.team.action.register')->getControlPrototype()->setHtmlAttribute('aria-hidden', 'true')->setHtmlAttribute('tabindex', '-1'); + $defaultSaveButton = $this->addSubmit('save_default_submit', 'messages.team.action.register'); + $defaultSaveButton->getControlPrototype()->setHtmlAttribute('aria-hidden', 'true')->setHtmlAttribute('tabindex', '-1'); $this->addProtection(); $this->addGroup('messages.team.info.label'); @@ -60,20 +60,20 @@ public function __construct( $rule->addRule(function(CategoryEntry $entry) use ($defaultMaxMembers): bool { $category = $entry->getValue(); $maxMembers = $this->entries->categories->allCategories[$category]->maxMembers ?? $defaultMaxMembers; - /** @var ReplicatorContainer */ - $replicator = $entry->form['persons']; + /** @var Multiplier */ // For PHPStan. + $multiplier = $entry->form['persons']; - return iterator_count($replicator->getContainers()) <= $maxMembers; + return $multiplier->getCopyNumber() <= $maxMembers; }, 'messages.team.error.too_many_members_simple'); // TODO: add params like in add/remove buttons $rule = $category->addCondition(true); // not to block the export of rules to JS $rule->addRule(function(CategoryEntry $entry) use ($defaultMinMembers): bool { $category = $entry->getValue(); $minMembers = $this->entries->categories->allCategories[$category]->minMembers ?? $defaultMinMembers; - /** @var ReplicatorContainer */ - $replicator = $entry->form['persons']; + /** @var Multiplier */ // For PHPStan. + $multiplier = $entry->form['persons']; - return iterator_count($replicator->getContainers()) >= $minMembers; + return $multiplier->getCopyNumber() >= $minMembers; }, 'messages.team.error.too_few_members_simple'); $fields = $this->entries->teamFields; @@ -82,23 +82,12 @@ public function __construct( $this->addTextArea('message', 'messages.team.message.label'); $this->setCurrentGroup(); - $this->addSubmit('save', $isEditing ? 'messages.team.action.edit' : 'messages.team.action.register'); - $this->addSubmit('add', 'messages.team.action.add')->setValidationScope([])->onClick[] = function(SubmitButton $button) use ($defaultMaxMembers): void { - $category = $button->form->getUnsafeValues(null)['category']; - $maxMembers = $this->entries->categories->allCategories[$category]->maxMembers ?? $defaultMaxMembers; - /** @var ReplicatorContainer */ - $replicator = $button->form['persons']; - if (iterator_count($replicator->getContainers()) < $maxMembers) { - $replicator->createOne(); - } else { - $button->form->addError($this->translator->translate('messages.team.error.too_many_members', $maxMembers, ['category' => $category]), false); - } - }; + $saveButton = $this->addSubmit('save', $isEditing ? 'messages.team.action.edit' : 'messages.team.action.register'); $fields = $this->entries->personFields; $i = 0; - $persons = new ReplicatorContainer( - factory: function(Container $container) use (&$i, $fields, $defaultMinMembers): void { + $persons = new Multiplier( + factory: function(Container $container, self $form) use (&$i, $fields): void { ++$i; $group = $this->addGroup(); $group->setOption('label', new Message('messages.team.person.label', $i)); @@ -121,48 +110,48 @@ public function __construct( $email->setRequired(); $group->setOption('description', 'messages.team.person.isContact'); } - - $container->addSubmit('remove', 'messages.team.action.remove')->setValidationScope([])->onClick[] = function(SubmitButton $button) use ($container, $defaultMinMembers): void { - $category = $button->form->getUnsafeValues(null)['category']; - $minMembers = $this->entries->categories->allCategories[$category]->minMembers ?? $defaultMinMembers; - /** @var ReplicatorContainer */ // For PHPStan. - $replicator = $button->form['persons']; - if (iterator_count($replicator->getContainers()) > $minMembers) { - $replicator->remove($container, true); - } else { - $button->form->addError($this->translator->translate('messages.team.error.too_few_members', $minMembers, ['category' => $category]), false); - } - }; }, - createDefault: $initialMembers, - forceDefault: true, + copyNumber: $initialMembers, + maxCopies: $defaultMaxMembers, ); $this['persons'] = $persons; + $persons->onCreateComponents[] = function(Multiplier $multiplier) use ($defaultMaxMembers): void { + if (!$this->isSubmitted()) { + return; + } + + $category = $this->getUnsafeValues(null)['category']; + $count = iterator_count($multiplier->getContainers()); + + $multiplierReflection = new \ReflectionClass(Multiplier::class); + $httpData = $multiplierReflection->getProperty('httpData'); + $httpData->setAccessible(true); + $values = $multiplierReflection->getProperty('values'); + $values->setAccessible(true); + $resolver = new \Contributte\FormMultiplier\ComponentResolver($httpData->getValue($multiplier), $values->getValue($multiplier), $multiplier->getMaxCopies(), $multiplier->getMinCopies()); + + $maxMembers = $this->entries->categories->allCategories[$category]->maxMembers ?? $defaultMaxMembers; + if ($resolver->isCreateAction() && $count >= $maxMembers) { + $this->addError($this->translator->translate('messages.team.error.too_many_members', $maxMembers, ['category' => $category]), false); + } + }; + $persons->setMinCopies($defaultMinMembers); + $persons->addCreateButton('messages.team.action.add')->setNoValidate(); + $persons->addRemoveButton('messages.team.action.remove'); - $this->onRender[] = $this->updatePersonButtonsState(...); $this->onValidate[] = $this->checkCategoryConstraints(...); - } - private function updatePersonButtonsState(): void { - /** @var \Kdyby\Replicator\Container */ - $persons = $this['persons']; - $count = iterator_count($persons->getContainers()); - $minMembers = $this->entries->minMembers; - $maxMembers = $this->entries->maxMembers; - - if ($count >= $maxMembers) { - /** @var Controls\SubmitButton */ - $add = $this['add']; - $add->setDisabled(); - } + $minMembersCheck = function(Controls\SubmitButton $button) use ($persons, $defaultMinMembers): void { + $category = $this->getUnsafeValues(null)['category']; + $count = iterator_count($persons->getContainers()); - if ($count <= $minMembers) { - foreach ($persons->getContainers() as $person) { - /** @var Controls\SubmitButton */ - $remove = $person['remove']; - $remove->setDisabled(); + $minMembers = $this->entries->categories->allCategories[$category]->minMembers ?? $defaultMinMembers; + if ($count < $minMembers) { + $this->addError($this->translator->translate('messages.team.error.too_few_members', $minMembers, ['category' => $category]), false); } - } + }; + $defaultSaveButton->onClick[] = $minMembersCheck; + $saveButton->onClick[] = $minMembersCheck; } private function addCustomFields(array $fields, Container $container): void { diff --git a/app/Presenters/TeamPresenter.php b/app/Presenters/TeamPresenter.php index 03d9f9f..ac0be84 100644 --- a/app/Presenters/TeamPresenter.php +++ b/app/Presenters/TeamPresenter.php @@ -14,9 +14,9 @@ use App\Model\Orm\Invoice\Invoice; use App\Model\Orm\ItemReservation\ItemReservation; use App\Model\Orm\Team\Team; +use Contributte\FormMultiplier\Multiplier; use DateTimeImmutable; use Exception; -use Kdyby\Replicator\Container as ReplicatorContainer; use Latte; use Nette; use Nette\Application\ForbiddenRequestException; @@ -123,10 +123,10 @@ public function renderRegister(): void { if ($form->isSubmitted() === false) { // Create sufficient number of person subforms for the most common team size (when it is greater than minimum team size). $remainingMembers = $this->entries->initialMembers - $this->entries->minMembers; - /** @var ReplicatorContainer */ - $replicator = $form['persons']; + /** @var Multiplier */ // For PHPStan. + $multiplier = $form['persons']; for ($i = $remainingMembers; $i > 0; --$i) { - $replicator->createOne(); + $multiplier->addCopy(); } } } @@ -156,50 +156,48 @@ public function renderEdit(int $id = null): void { } $form = $this->getComponent('teamForm'); - if ($form->isSubmitted() === false) { - $default = []; - $default['name'] = $team->name; - $default['category'] = $team->category; - $default['message'] = $team->message; - $default['persons'] = []; - - $fields = $this->entries->teamFields; + $default = []; + $default['name'] = $team->name; + $default['category'] = $team->category; + $default['message'] = $team->message; + $default['persons'] = []; + + $fields = $this->entries->teamFields; + foreach ($fields as $field) { + $name = $field->name; + if (isset($team->getJsonData()->$name)) { + $default[$name] = $team->getJsonData()->$name; + } elseif ($field instanceof Fields\SportidentField) { + $default[$name] = [ + SportidentControl::NAME_NEEDED => true, + ]; + } + } + + $fields = $this->entries->personFields; + foreach ($team->persons as $person) { + $personDefault = [ + 'firstname' => $person->firstname, + 'lastname' => $person->lastname, + 'gender' => $person->gender, + 'email' => $person->email, + 'birth' => $person->birth, + ]; + foreach ($fields as $field) { $name = $field->name; - if (isset($team->getJsonData()->$name)) { - $default[$name] = $team->getJsonData()->$name; + if (isset($person->getJsonData()->$name)) { + $personDefault[$name] = $person->getJsonData()->$name; } elseif ($field instanceof Fields\SportidentField) { - $default[$name] = [ + $personDefault[$name] = [ SportidentControl::NAME_NEEDED => true, ]; } } - $fields = $this->entries->personFields; - foreach ($team->persons as $person) { - $personDefault = [ - 'firstname' => $person->firstname, - 'lastname' => $person->lastname, - 'gender' => $person->gender, - 'email' => $person->email, - 'birth' => $person->birth, - ]; - - foreach ($fields as $field) { - $name = $field->name; - if (isset($person->getJsonData()->$name)) { - $personDefault[$name] = $person->getJsonData()->$name; - } elseif ($field instanceof Fields\SportidentField) { - $personDefault[$name] = [ - SportidentControl::NAME_NEEDED => true, - ]; - } - } - - $default['persons'][] = $personDefault; - } - $form->setValues($default); + $default['persons'][] = $personDefault; } + $form->setDefaults($default); } } @@ -489,9 +487,9 @@ private function processTeamForm(Nette\Forms\Controls\SubmitButton $button): voi /** @var ?string $firstMemberName */ $firstMemberName = null; - /** @var ReplicatorContainer */ - $replicator = $form['persons']; - $personContainers = iterator_to_array($replicator->getContainers()); + /** @var Multiplier */ // For PHPStan. + $multiplier = $form['persons']; + $personContainers = iterator_to_array($multiplier->getContainers()); foreach ($values['persons'] as $personKey => $member) { $personContainer = $personContainers[$personKey]; $firstname = $member['firstname']; diff --git a/composer.json b/composer.json index 546f2d1..d9aee5b 100644 --- a/composer.json +++ b/composer.json @@ -12,9 +12,9 @@ ], "require": { "php": ">= 8.1", + "contributte/forms-multiplier": "^3.3", "contributte/mail": "^0.6.0", "contributte/translation": "^2.0", - "kdyby/forms-replicator": "^2.0.0", "latte/latte": "~2.5", "moneyphp/money": "^4.0", "nette/application": "~3.0", diff --git a/composer.lock b/composer.lock index a6e2bf1..86fba97 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,74 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a73afe89d6375cd457980c51689578c2", + "content-hash": "827f792d6a2d4cde712b3c735f5b6241", "packages": [ + { + "name": "contributte/forms-multiplier", + "version": "v3.3.1", + "source": { + "type": "git", + "url": "https://github.com/contributte/forms-multiplier.git", + "reference": "80e15b6515a372c46e564decc7608f2f548d42d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/contributte/forms-multiplier/zipball/80e15b6515a372c46e564decc7608f2f548d42d6", + "reference": "80e15b6515a372c46e564decc7608f2f548d42d6", + "shasum": "" + }, + "require": { + "nette/forms": "^3.1.0", + "php": ">=7.2" + }, + "require-dev": { + "codeception/codeception": "^4.0.0", + "codeception/module-asserts": "^1.3", + "codeception/module-phpbrowser": "^1.0", + "latte/latte": "^3.0.0", + "nette/application": "^3.0.0", + "nette/di": "^3.0.0", + "ninjify/qa": "^0.12", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-nette": "^1.0", + "webchemistry/testing-helpers": "~2.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Contributte\\FormMultiplier\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "description": "Multiplier for nette forms", + "keywords": [ + "Forms", + "contributte", + "multiplier", + "nette" + ], + "support": { + "issues": "https://github.com/contributte/forms-multiplier/issues", + "source": "https://github.com/contributte/forms-multiplier/tree/v3.3.1" + }, + "funding": [ + { + "url": "https://contributte.org/partners.html", + "type": "custom" + }, + { + "url": "https://github.com/f3l1x", + "type": "github" + } + ], + "time": "2022-12-03T18:36:23+00:00" + }, { "name": "contributte/mail", "version": "v0.6.0", @@ -159,79 +225,6 @@ ], "time": "2022-11-25T10:43:13+00:00" }, - { - "name": "kdyby/forms-replicator", - "version": "v2.0.0", - "source": { - "type": "git", - "url": "https://github.com/Kdyby/FormsReplicator.git", - "reference": "cd6c9ccfaade43b85d181a8041ec5a7f842969b1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Kdyby/FormsReplicator/zipball/cd6c9ccfaade43b85d181a8041ec5a7f842969b1", - "reference": "cd6c9ccfaade43b85d181a8041ec5a7f842969b1", - "shasum": "" - }, - "require": { - "nette/forms": "^3.0", - "nette/utils": "^3.0", - "php": ">=7.1" - }, - "require-dev": { - "nette/application": "^3.0@rc", - "nette/bootstrap": "^3.0@rc", - "nette/di": "^3.0@rc", - "nette/tester": "^2.2", - "tracy/tracy": "^2.6" - }, - "suggest": { - "nette/di": "to use ReplicatorExtension[CompilerExtension]" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "psr-4": { - "Kdyby\\Replicator\\": "src/Replicator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause", - "GPL-2.0", - "GPL-3.0" - ], - "authors": [ - { - "name": "Filip Procházka", - "email": "filip@prochazka.su", - "homepage": "http://filip-prochazka.com" - }, - { - "name": "David Šolc", - "email": "solcik@gmail.com", - "homepage": "https://solc.dev" - } - ], - "description": "Nette forms container replicator aka addDynamic", - "homepage": "http://kdyby.org", - "keywords": [ - "Forms", - "addDynamic", - "kdyby", - "nette", - "replicator" - ], - "support": { - "issues": "https://github.com/Kdyby/FormsReplicator/issues", - "source": "https://github.com/Kdyby/FormsReplicator/tree/master" - }, - "time": "2019-03-18T16:16:28+00:00" - }, { "name": "latte/latte", "version": "v2.11.6",