Skip to content

Commit 6d29b93

Browse files
committed
support for true optional controls via setRequired(FALSE) (BC break)
1 parent d96bab4 commit 6d29b93

File tree

9 files changed

+98
-18
lines changed

9 files changed

+98
-18
lines changed

src/Forms/Controls/CsrfProtection.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ class CsrfProtection extends HiddenField
2828
public function __construct($message)
2929
{
3030
parent::__construct();
31-
$this->setOmitted()->addRule(self::PROTECTION, $message);
31+
$this->setOmitted()
32+
->setRequired()
33+
->addRule(self::PROTECTION, $message);
3234
$this->monitor(Nette\Application\UI\Presenter::class);
3335
}
3436

src/Forms/Controls/SelectBox.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ public function __construct($label = NULL, array $items = NULL)
3232
{
3333
parent::__construct($label, $items);
3434
$this->setOption('type', 'select');
35-
$this->addRule([$this, 'isOk'], Nette\Forms\Validator::$messages[self::VALID]);
35+
$this->addCondition(Nette\Forms\Form::BLANK)
36+
->addRule([$this, 'isOk'], Nette\Forms\Validator::$messages[self::VALID]);
3637
}
3738

3839

src/Forms/Helpers.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ public static function exportRules(Rules $rules)
132132

133133
$payload[] = $item;
134134
}
135+
if ($payload && $rules->isOptional()) {
136+
array_unshift($payload, ['op' => 'optional']);
137+
}
135138
return $payload;
136139
}
137140

src/Forms/Rules.php

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class Rules implements \IteratorAggregate
2020
/** @deprecated */
2121
public static $defaultMessages;
2222

23-
/** @var Rule */
23+
/** @var Rule|FALSE|NULL */
2424
private $required;
2525

2626
/** @var Rule[] */
@@ -52,7 +52,7 @@ public function setRequired($value = TRUE)
5252
if ($value) {
5353
$this->addRule(Form::REQUIRED, $value === TRUE ? NULL : $value);
5454
} else {
55-
$this->required = NULL;
55+
$this->required = FALSE;
5656
}
5757
return $this;
5858
}
@@ -68,6 +68,15 @@ public function isRequired()
6868
}
6969

7070

71+
/**
72+
* @internal
73+
*/
74+
public function isOptional()
75+
{
76+
return $this->required === FALSE;
77+
}
78+
79+
7180
/**
7281
* Adds a validation rule for the current control.
7382
* @param mixed rule type
@@ -208,7 +217,7 @@ public function getToggleStates($toggles = [], $success = TRUE)
208217
$toggles[$id] = ($success xor !$hide) || !empty($toggles[$id]);
209218
}
210219

211-
foreach ($this as $rule) {
220+
foreach ($this->rules as $rule) {
212221
if ($rule->branch) {
213222
$toggles = $rule->branch->getToggleStates($toggles, $success && static::validateRule($rule));
214223
}
@@ -221,12 +230,16 @@ public function getToggleStates($toggles = [], $success = TRUE)
221230
* Validates against ruleset.
222231
* @return bool
223232
*/
224-
public function validate()
233+
public function validate($emptyOptional = FALSE)
225234
{
235+
$emptyOptional = $emptyOptional || $this->isOptional() && !$this->control->isFilled();
226236
foreach ($this as $rule) {
227-
$success = $this->validateRule($rule);
237+
if (!$rule->branch && $emptyOptional && $rule->validator !== Form::FILLED) {
238+
return TRUE;
239+
}
228240

229-
if ($success && $rule->branch && !$rule->branch->validate()) {
241+
$success = $this->validateRule($rule);
242+
if ($success && $rule->branch && !$rule->branch->validate($rule->validator === Form::BLANK ? FALSE : $emptyOptional)) {
230243
return FALSE;
231244

232245
} elseif (!$success && !$rule->branch) {

src/assets/netteForms.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ Nette.getEffectiveValue = function(elem) {
131131
/**
132132
* Validates form element against given rules.
133133
*/
134-
Nette.validateControl = function(elem, rules, onlyCheck, value) {
134+
Nette.validateControl = function(elem, rules, onlyCheck, value, emptyOptional) {
135135
elem = elem.tagName ? elem : elem[0]; // RadioNodeList
136136
rules = rules || Nette.parseJSON(elem.getAttribute('data-nette-rules'));
137137
value = value === undefined ? {value: Nette.getEffectiveValue(elem)} : value;
@@ -141,15 +141,20 @@ Nette.validateControl = function(elem, rules, onlyCheck, value) {
141141
op = rule.op.match(/(~)?([^?]+)/),
142142
curElem = rule.control ? elem.form.elements.namedItem(rule.control) : elem;
143143

144+
rule.neg = op[1];
145+
rule.op = op[2];
146+
rule.condition = !!rule.rules;
147+
144148
if (!curElem) {
145149
continue;
150+
} else if (rule.op === 'optional') {
151+
emptyOptional = !Nette.validateRule(elem, ':filled', null, value);
152+
continue;
153+
} else if (emptyOptional && !rule.condition && rule.op !== ':filled') {
154+
return true;
146155
}
147156

148-
rule.neg = op[1];
149-
rule.op = op[2];
150-
rule.condition = !!rule.rules;
151157
curElem = curElem.tagName ? curElem : curElem[0]; // RadioNodeList
152-
153158
var curValue = elem === curElem ? value : {value: Nette.getEffectiveValue(curElem)},
154159
success = Nette.validateRule(curElem, rule.op, rule.arg, curValue);
155160

@@ -160,7 +165,7 @@ Nette.validateControl = function(elem, rules, onlyCheck, value) {
160165
}
161166

162167
if (rule.condition && success) {
163-
if (!Nette.validateControl(elem, rule.rules, onlyCheck, value)) {
168+
if (!Nette.validateControl(elem, rule.rules, onlyCheck, value, rule.op === ':blank' ? false : emptyOptional)) {
164169
return false;
165170
}
166171
} else if (!rule.condition && !success) {

tests/Forms/Controls.BaseControl.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ require __DIR__ . '/../bootstrap.php';
1515
test(function () { // error handling
1616
$form = new Form;
1717
$input = $form->addText('text')
18-
->addRule($form::EMAIL, 'error');
18+
->setRequired('error');
1919

2020
Assert::same([], $input->getErrors());
2121
Assert::null($input->getError());

tests/Forms/Controls.CsrfProtection.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ $input = $form->addProtection('Security token did not match. Possible CSRF attac
2121

2222
$form->fireEvents();
2323

24-
Assert::same(['Security token did not match. Possible CSRF attack.'], $form->getErrors());
24+
Assert::same(['This field is required.'], $form->getErrors());
2525
Assert::null($input->getOption('rendered'));
2626
Assert::match('<input type="hidden" name="_token_" value="%S%">', (string) $input->getControl());
2727
Assert::true($input->getOption('rendered'));

tests/Forms/Helpers.exportRules.phpt

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,59 @@ test(function () {
2525
],
2626
], Helpers::exportRules($input->getRules()));
2727
});
28+
29+
30+
test(function () {
31+
$form = new Form;
32+
$input = $form->addText('text');
33+
$input->addRule(Form::EMAIL);
34+
Assert::same([
35+
['op' => ':email', 'msg' => 'Please enter a valid email address.']
36+
], Helpers::exportRules($input->getRules()));
37+
});
38+
39+
40+
test(function () {
41+
$form = new Form;
42+
$input = $form->addText('text');
43+
$input->setRequired(FALSE);
44+
$input->addRule(Form::EMAIL);
45+
Assert::same([
46+
['op' => 'optional'],
47+
['op' => ':email', 'msg' => 'Please enter a valid email address.'],
48+
], Helpers::exportRules($input->getRules()));
49+
});
50+
51+
52+
test(function () {
53+
$form = new Form;
54+
$input1 = $form->addText('text1');
55+
$input2 = $form->addText('text2');
56+
$input2->setRequired(FALSE);
57+
$input2->addConditionOn($input1, Form::EMAIL)
58+
->setRequired(TRUE)
59+
->addRule($form::EMAIL);
60+
$input2->addConditionOn($input1, Form::INTEGER)
61+
->setRequired(FALSE)
62+
->addRule($form::EMAIL);
63+
64+
Assert::same([
65+
['op' => 'optional'],
66+
[
67+
'op' => ':email',
68+
'rules' => [
69+
['op' => ':filled', 'msg' => 'This field is required.'],
70+
['op' => ':email', 'msg' => 'Please enter a valid email address.'],
71+
],
72+
'control' => 'text1',
73+
],
74+
[
75+
'op' => ':integer',
76+
'rules' => [
77+
['op' => 'optional'],
78+
['op' => ':email', 'msg' => 'Please enter a valid email address.'],
79+
],
80+
'control' => 'text1',
81+
],
82+
], Helpers::exportRules($input2->getRules()));
83+
});

tests/Forms/Rules.required.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ test(function () { // setRequired(FALSE)
7979
Assert::count(1, $items);
8080
Assert::same(Form::EMAIL, $items[0]->validator);
8181

82-
Assert::false($rules->validate());
83-
Assert::same(['Please enter a valid email address.'], $input->getErrors());
82+
Assert::true($rules->validate());
83+
Assert::same([], $input->getErrors());
8484
});
8585

8686

0 commit comments

Comments
 (0)