From ac6894f4242331ac03a1dbfa60d94214c9323c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Klob=C3=A1s?= Date: Mon, 7 Oct 2019 17:28:29 +0200 Subject: [PATCH 1/4] =?UTF-8?q?peckadesign/pdforms#20=20Roz=C5=A1=C3=AD?= =?UTF-8?q?=C5=99en=C3=AD=20o=20mo=C5=BEnost=20pou=C5=BE=C3=ADvat=20dostup?= =?UTF-8?q?n=C3=A1=20Nette=20pravidla=20jako=20nepovinn=C3=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rules::NETTE_RULE_PROXY - RuleOptionsFactory::createNetteOptional() --- doc/index.md | 1 + doc/nette_optional.md | 39 +++++++++++++++++++++ src/RuleOptionsFactory.php | 23 ++++++++++++ src/Rules.php | 13 ++++++- tests/Unit/Forms/RuleOptionsFactoryTest.php | 6 ++++ tests/Unit/Forms/RuleOptionsTest.php | 11 ++++++ 6 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 doc/nette_optional.md diff --git a/doc/index.md b/doc/index.md index f2c3ba1..84b87c1 100644 --- a/doc/index.md +++ b/doc/index.md @@ -13,3 +13,4 @@ Knihovna poskytuje nástroje, pomocí kterých je možné zaregistrovat vlastní - [Vlastní pravidla, měkká validace a validační kontext](custom_rules.md) - [Je libo AJAX?](ajax.md) - [AJAX s JS callbackem, závilost na více formulářových polích](ajax_dependent_inputs.md) +- [Měkká validace pomocí dostupných Nette validátorů](nette_optional.md) diff --git a/doc/nette_optional.md b/doc/nette_optional.md new file mode 100644 index 0000000..c57a112 --- /dev/null +++ b/doc/nette_optional.md @@ -0,0 +1,39 @@ +## Měkká validace pomocí dostupných Nette validátorů + +Nette má velkou sadu validačních pravidel pokrývající obecné validace vstupů. V případě, že byste chtěli některý z nich použít, museli byste si ho volat na frontendu, ale backend o této validaci vůbec neví. Když k formuláři přijde backend programátor, tak bude koukat, kde se ta validace bere. Navíc byste si museli dopsat frontend obsluhu pro konkrétní prvek a volat si Nette validátor. Celé to jde hezky zobecnit a vzájemně propojit. `pd/forms` poskytuje jednoduchý mechanismus jak toho docílit - obecné pravidlo `\Pd\Forms\Rules::NETTE_RULE_PROXY`. + +Obecné pravidlo `\Pd\Forms\Rules::NETTE_RULE_PROXY` navážeme na požadovaný formulářový prvek - to zajistí zpracování (vlastně nezpracování na backendu) a předá potřebná nastavení pro frontendové zpracování. Pravidlo nakonfigurujeme pomocí `Pd\Form\RuleOptions`, které vytvoříme z dostupné továrny metodou `createNetteOptional()`. Ta má dva parametry - Nette pravidlo a argument(y) pro toto pravidlo. + +```php +ruleOptionsFactory = $ruleOptionsFactory; + } + + public function create(): \Nette\Forms\Form + { + $form = new \Nette\Forms\Form; + + $companyIdentifierOptions = $this->ruleOptionsFactory->createNetteOptional( + \Nette\Forms\Form::PATTERN, + '([0-9]\s*){0,8}' + ); + + $form->addText('companyIdentifier', 'IČO') + ->addCondition(\Nette\Forms\Form::FILLED) + ->addRule(\Pd\Forms\Rules::NETTE_RULE_PROXY, '_msg_ico_pattern', $companyIdentifierOptions) + ; + } +} +``` + +Na frontendu obsluha pd/forms zavolá validátor `pattern` z `netteForms.js` a provede validaci formulářového prvku a výsledek oznámí uživateli. Při odeslání formuláře nedochází k backendové validaci, protože o tu nemáme zájem. Chceme pouze uživatele informovat, že je IČO není validaní, ale necháme ho např. dokončit objednávku, abychom nepřišli o konverzi. diff --git a/src/RuleOptionsFactory.php b/src/RuleOptionsFactory.php index 14789e4..b56788b 100644 --- a/src/RuleOptionsFactory.php +++ b/src/RuleOptionsFactory.php @@ -4,6 +4,9 @@ final class RuleOptionsFactory { + private const NETTE_RULE = 'netteRule'; + private const NETTE_RULE_ARGS = 'netteRuleArgs'; + /** * @var \Nette\Localization\ITranslator */ @@ -27,4 +30,24 @@ public function createRequired(): \Pd\Forms\RuleOptions { return new \Pd\Forms\RuleOptions($this->translator, FALSE); } + + + public function createNetteOptional(string $netteRule, $ruleArguments = NULL): \Pd\Forms\RuleOptions + { + $options = new \Pd\Forms\RuleOptions($this->translator, TRUE); + + $options->addContext(self::NETTE_RULE, $netteRule); + + if ($ruleArguments !== NULL) { + if ( ! \is_scalar($ruleArguments) && ! \is_array($ruleArguments)) { + throw new \InvalidArgumentException( + \sprintf('Arguments for optional Nette rule must be scalar or array, %s given', \gettype($ruleArguments)) + ); + } + + $options->addContext(self::NETTE_RULE_ARGS, $ruleArguments); + } + + return $options; + } } diff --git a/src/Rules.php b/src/Rules.php index a2433ab..cd0fcbf 100644 --- a/src/Rules.php +++ b/src/Rules.php @@ -5,6 +5,8 @@ final class Rules { public const AJAX = self::class . '::ajax'; + public const NETTE_RULE_PROXY = self::class . '::netteRuleProxy'; + public const PHONE = self::class . '::phone'; public const CONTAINS_NUMBER = self::class . '::containsNumber'; public const NO_EXTERNAL_SOURCES = self::class . '::noExternalSources'; @@ -30,7 +32,7 @@ public static function ajax(\Nette\Forms\IControl $control, \Pd\Forms\RuleOption ->validateInput($control->getValue(), $options->getNormalizedDependentInputs()); if ($validationResult->getStatus() === \Pd\Forms\RuleOptions::STATUS_TIMEOUT) { - return TRUE; // externí služba není dostupná, formulář musí projít + return TRUE; } if ( ! $validationResult->isValid()) { @@ -48,6 +50,15 @@ public static function ajax(\Nette\Forms\IControl $control, \Pd\Forms\RuleOption } + /** + * Rule is never validated on backend, used for proxying nette rules as optional only + */ + public static function netteRuleProxy(\Nette\Forms\IControl $control, \Pd\Forms\RuleOptions $options): bool + { + return TRUE; + } + + public static function phone(\Nette\Forms\IControl $control, ?\Pd\Forms\RuleOptions $options): bool { if ($options !== NULL && $options->isOptional()) { diff --git a/tests/Unit/Forms/RuleOptionsFactoryTest.php b/tests/Unit/Forms/RuleOptionsFactoryTest.php index a606a19..c0830ec 100644 --- a/tests/Unit/Forms/RuleOptionsFactoryTest.php +++ b/tests/Unit/Forms/RuleOptionsFactoryTest.php @@ -24,6 +24,12 @@ public function testCreateOptions(): void $required = $this->ruleOptionsFactory->createRequired(); \Tester\Assert::false($required->isOptional()); + + $netteOptional = $this->ruleOptionsFactory->createNetteOptional('someNetteRule', 'argumentForNetteRule'); + + \Tester\Assert::true($netteOptional->isOptional()); + \Tester\Assert::same('someNetteRule', $netteOptional->getContext('netteRule')); + \Tester\Assert::same('argumentForNetteRule', $netteOptional->getContext('netteRuleArgs')); } diff --git a/tests/Unit/Forms/RuleOptionsTest.php b/tests/Unit/Forms/RuleOptionsTest.php index 1dd4c6d..d246494 100644 --- a/tests/Unit/Forms/RuleOptionsTest.php +++ b/tests/Unit/Forms/RuleOptionsTest.php @@ -25,6 +25,17 @@ public function testSerialization(): void \Tester\Assert::same(\Nette\Utils\Json::encode(['optional' => TRUE]), \Nette\Utils\Json::encode($optional)); + $netteOptional = $this->ruleOptionsFactory->createNetteOptional('someNetteRule', 'argumentForNetteRule'); + $netteOptionalExpected = [ + 'optional' => TRUE, + 'context' => [ + 'netteRule' => 'someNetteRule', + 'netteRuleArgs' => 'argumentForNetteRule', + ], + ]; + + \Tester\Assert::same(\Nette\Utils\Json::encode($netteOptionalExpected), \Nette\Utils\Json::encode($netteOptional)); + $required = $this->ruleOptionsFactory->createRequired() ->enableAjax('http://ajaxValidationTarget.pecka', $this->validationService) ->addValidationMessage('first', 'message') From bd2996a5e1ae08a78f89da15c3eae98f1f55a305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Klob=C3=A1s?= Date: Fri, 11 Oct 2019 10:03:45 +0200 Subject: [PATCH 2/4] Version matrix + License --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index f7f0c7b..fca6f4a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![Build Status](https://travis-ci.com/peckadesign/pdForms.svg?branch=master)](https://travis-ci.com/peckadesign/pdForms) +[![Licence](https://img.shields.io/packagist/l/pd/forms.svg?style=flat-square)](https://packagist.org/packages/pd/forms) # Pd\Form\Rules + pdForms.js Knihovna poskytuje nástroje, pomocí kterých je možné zaregistrovat vlastní validační pravidla do Nette\Forms a navíc poskytuje podporu pro live, měkkou a ajaxovou validaci, které lze zaregistrovat v PHP kódu. Řešení vychází z nativní podpory Nette pro custom validační pravidla https://pla.nette.org/cs/vlastni-validacni-pravidla, ale nespoléhá ani nekopíruje interní quirks Nette frameworku. @@ -7,3 +8,10 @@ Knihovna poskytuje nástroje, pomocí kterých je možné zaregistrovat vlastní - [Jak přidávat validace na formulářích](doc/index.md) - [Changelog](https://github.com/peckadesign/pdForms/releases) + +## Verze + +Version | Branch | Nette | PHP | +---------|----------|--------|---------| +`^3.0.0` | `master` | `2.4` | `>=7.1` | +`^2.0.0` | `v2.0` | `2.3` | `>=7.1` | From 0e38458ceea90bcfc53da612ecd7cfe62caf2817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20=C5=A0er=C3=BD?= Date: Fri, 11 Oct 2019 12:19:45 +0200 Subject: [PATCH 3/4] =?UTF-8?q?peckadesign/pdforms#20=20Roz=C5=A1=C3=AD?= =?UTF-8?q?=C5=99en=C3=AD=20o=20mo=C5=BEnost=20pou=C5=BE=C3=ADvat=20dostup?= =?UTF-8?q?n=C3=A1=20Nette=20pravidla=20jako=20nepovinn=C3=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - přidání JS pravidla `PdFormsRules_netteRuleProxy` volající pravidlo z `Nette.validators` --- package.json | 2 +- src/assets/pdForms.js | 4 ++-- .../pdForms.validator.netteRuleProxy.js | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 src/assets/validators/pdForms.validator.netteRuleProxy.js diff --git a/package.json b/package.json index 754af68..05c2b1e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "pd-forms", "title": "pdForms", "description": "Customization of netteForms for use in PeckaDesign.", - "version": "3.0.0", + "version": "3.0.1", "author": "PeckaDesign, s.r.o ", "contributors": [ "Radek Šerý ", diff --git a/src/assets/pdForms.js b/src/assets/pdForms.js index 56b08c3..88922e0 100644 --- a/src/assets/pdForms.js +++ b/src/assets/pdForms.js @@ -1,7 +1,7 @@ /** * @name pdForms * @author Radek Šerý - * @version 3.0.0 + * @version 3.0.1 * * Features: * - live validation @@ -45,7 +45,7 @@ var pdForms = window.pdForms || {}; - pdForms.version = '3.0.0'; + pdForms.version = '3.0.1'; /** diff --git a/src/assets/validators/pdForms.validator.netteRuleProxy.js b/src/assets/validators/pdForms.validator.netteRuleProxy.js new file mode 100644 index 0000000..c631f31 --- /dev/null +++ b/src/assets/validators/pdForms.validator.netteRuleProxy.js @@ -0,0 +1,15 @@ +/** + * @name pdForms.validator.netteRuleProxy + * @author Radek Šerý + * + * Implementation of proxy rule. This rule is used for optional validating using other Nette validators. + */ +(function() { + + Nette.validators.PdFormsRules_netteRuleProxy = function (elem, arg, val) { + var validator = pdForms.formatOperation(arg.context.netteRule); + + return validator in Nette.validators ? Nette.validators[validator](elem, arg.context.netteRuleArgs, val) : true; + }; + +})(); From feb8ddff499e6a2e85a44adbaf05540b1384cf07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radek=20=C5=A0er=C3=BD?= Date: Fri, 11 Oct 2019 12:20:03 +0200 Subject: [PATCH 4/4] =?UTF-8?q?peckadesign/pdforms#17=20Nezobrazen=C3=AD?= =?UTF-8?q?=20chybov=C3=A9=20zpr=C3=A1vy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - přesunutí volání mazání pravidel z rekurzivně volaného `pdForms.validateControl` do event handleru pro live validaci `pdForms.validateInput` - BC break: úprava volání normalizace pravidel; pokud při volání `Nette.validateControl` předáváme pravidla, očekává se, že již byla normalizována pomocí `pdForms.normalizeRules` --- src/assets/pdForms.js | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/assets/pdForms.js b/src/assets/pdForms.js index 88922e0..8b739c4 100644 --- a/src/assets/pdForms.js +++ b/src/assets/pdForms.js @@ -156,14 +156,20 @@ validate.forEach(function(elem) { if (elem.getAttribute('data-pdforms-ever-focused')) { - // validate control using nette-rules && pd-rules (which are inside nette-rules actually) - var ret = Nette.validateControl(elem); + // assumes the input is valid, therefore removing all messages except those associated with ajax rules; this + // prevents flashing of message, when ajax rule is evaluated - ajax rules removes their messages when the ajax + // rule is evaluated + pdForms.removeMessages(elem, false); + var rules = Nette.parseJSON(elem.getAttribute('data-nette-rules')); rules = pdForms.normalizeRules(rules); + + // validate control using nette-rules && pd-rules (which are inside nette-rules actually) + var valid = Nette.validateControl(elem, rules); var hasAjaxRule = pdForms.hasAjaxRule(rules); // has to be here and not inside validateControl as it should add ok class only if whole input is valid (not only parts of condional rule etc.) - if (ret && ! hasAjaxRule) { + if (valid && ! hasAjaxRule) { // add pdforms-valid class name if the input is valid pdForms.addMessage(elem, null, pdForms.constants.MESSAGE_VALID); } @@ -176,13 +182,6 @@ * Validates form element using optional nette-rules. */ pdForms.validateControl = function(elem, rules, onlyCheck, value, emptyOptional) { - // assumes the input is valid, therefore removing all messages except those associated with ajax rules; this - // prevents flashing of message, when ajax rule is evaluated - ajax rules removes their messages when the ajax - // rule is evaluated; when onlyCheck is true, we dont' want to modify DOM at all - if (! onlyCheck) { - pdForms.removeMessages(elem, false); - } - // validate rules one-by-one to know which passed for (var id = 0, len = rules.length; id < len; id++) { var rule = rules[id]; @@ -548,16 +547,19 @@ */ Nette.validateControl = function(elem, rules, onlyCheck, value, emptyOptional) { elem = elem.tagName ? elem : elem[0]; // RadioNodeList - rules = rules || Nette.parseJSON(elem.getAttribute('data-nette-rules')); + + if (! rules) { + rules = Nette.parseJSON(elem.getAttribute('data-nette-rules')); + + // convert arg property in rules into Nette format + rules = pdForms.normalizeRules(rules); + } // no rules -> skip element validation if (rules.length === 0) { return true; } - // convert arg property in rules into Nette format - rules = pdForms.normalizeRules(rules); - return pdForms.validateControl(elem, rules, onlyCheck, value, emptyOptional); };