Skip to content

Commit 6e34d91

Browse files
committed
FormNode: {form scope name} as alternative to {formContext name}
The scope keyword (placed before the form name) makes {form} skip the <form> tag emission while still pushing the form on the stack, so {input}/{label}/{inputError} resolve against it. Semantically identical to {formContext}, but stays with the canonical {form} tag.
1 parent 0f6b603 commit 6e34d91

5 files changed

Lines changed: 181 additions & 2 deletions

File tree

src/Bridges/FormsLatte/Nodes/FormNode.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,22 @@
1818

1919

2020
/**
21-
* {form name [, attributes]} ... {/form}
21+
* {form [scope] name [, attributes]} ... {/form}
2222
* {formContext name} ... {/formContext}
2323
* Renders form tags and initializes form context.
24+
* The `scope` keyword makes {form} skip the <form> tag emission while still pushing the
25+
* form on the stack — semantically identical to {formContext}, but with the canonical
26+
* {form} tag.
2427
*/
2528
class FormNode extends StatementNode
2629
{
30+
private const Modes = ['scope'];
31+
2732
public ExpressionNode $name;
2833
public ArrayNode $attributes;
2934
public AreaNode $content;
3035
public bool $print;
36+
public ?string $mode = null;
3137

3238

3339
/** @return \Generator<int, ?list<string>, array{AreaNode, ?Tag}, static> */
@@ -40,13 +46,16 @@ public static function create(Tag $tag): \Generator
4046
$tag->outputMode = $tag::OutputKeepIndentation;
4147
$tag->expectArguments();
4248
$node = $tag->node = new static;
49+
$node->mode = $tag->name === 'form' && in_array($tag->parser->stream->tryPeek()?->text, self::Modes, strict: true)
50+
? $tag->parser->stream->consume()->text
51+
: null;
4352
$node->name = $tag->parser->parseUnquotedStringOrExpression();
4453
if (!$tag->parser->stream->tryConsume(',') && !$tag->parser->isEnd()) {
4554
$position = $tag->parser->stream->peek()->position;
4655
trigger_error("Missing comma before arguments in {{$tag->name}} tag $position.", E_USER_DEPRECATED);
4756
}
4857
$node->attributes = $tag->parser->parseArguments();
49-
$node->print = $tag->name === 'form';
58+
$node->print = $tag->name === 'form' && $node->mode !== 'scope';
5059

5160
[$node->content, $endTag] = yield;
5261
if ($endTag && $node->name instanceof StringNode) {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
2+
<label for="frm-name">Name:</label>
3+
<input type="text" name="name" id="frm-name">
4+
<input type="text" name="email" id="frm-email">
5+
6+
7+
8+
9+
<label for="frm-name">Name:</label>
10+
<input type="text" name="name" id="frm-name">
11+
<input type="text" name="email" id="frm-email">
12+
13+
14+
15+
16+
<input type="text" name="name" id="frm-name">
17+
18+
19+
20+
<form action="" method="post" class="outer">
21+
<input type="text" name="name" id="frm-name">
22+
23+
24+
<input type="text" name="email" id="frm-email">
25+
26+
</form>
27+
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php declare(strict_types=1);
2+
3+
/** Generated by Latte 3.1.3 */
4+
5+
use Latte\Runtime as LR;
6+
7+
/** source: W:\Nette\Forms\tests\Forms.Latte/templates/forms.scope.latte */
8+
final class Template_8e0ff5f22b extends Latte\Runtime\Template
9+
{
10+
11+
public function main(array $ʟ_args): void
12+
{
13+
extract($ʟ_args);
14+
unset($ʟ_args);
15+
16+
$this->global->forms->begin($form = $this->global->uiControl['myForm']) /* pos 3:1 */;
17+
echo '
18+
';
19+
echo ($ʟ_label = $this->global->forms->item('name')->getLabel()) /* pos 4:2 */;
20+
echo '
21+
';
22+
echo $this->global->forms->item('name')->getControl() /* pos 5:2 */;
23+
echo '
24+
';
25+
echo $this->global->forms->item('email')->getControl() /* pos 6:2 */;
26+
echo "\n";
27+
$this->global->forms->end();
28+
29+
echo '
30+
31+
32+
';
33+
$this->global->forms->begin($form = $this->global->uiControl['myForm']) /* pos 12:1 */;
34+
echo '
35+
';
36+
echo ($ʟ_label = $this->global->forms->item('name')->getLabel()) /* pos 13:2 */;
37+
echo '
38+
';
39+
echo $this->global->forms->item('name')->getControl() /* pos 14:2 */;
40+
echo '
41+
';
42+
echo $this->global->forms->item('email')->getControl() /* pos 15:2 */;
43+
echo "\n";
44+
$this->global->forms->end();
45+
46+
echo '
47+
48+
49+
';
50+
$this->global->forms->begin($form = $this->global->uiControl['myForm']) /* pos 21:1 */;
51+
echo '
52+
';
53+
echo $this->global->forms->item('name')->getControl() /* pos 22:2 */;
54+
echo "\n";
55+
$this->global->forms->end();
56+
57+
echo '
58+
59+
60+
';
61+
$this->global->forms->begin($form = $this->global->uiControl['myForm']) /* pos 28:1 */;
62+
echo $this->global->forms->renderFormBegin(['class' => 'outer']) /* pos 28:1 */;
63+
echo '
64+
';
65+
echo $this->global->forms->item('name')->getControl() /* pos 29:2 */;
66+
echo '
67+
68+
';
69+
$this->global->forms->begin($form = $this->global->uiControl['myForm']) /* pos 31:2 */;
70+
echo '
71+
';
72+
echo $this->global->forms->item('email')->getControl() /* pos 32:3 */;
73+
echo '
74+
';
75+
$this->global->forms->end();
76+
77+
echo "\n";
78+
echo $this->global->forms->renderFormEnd() /* pos 34:1 */;
79+
$this->global->forms->end();
80+
81+
echo "\n";
82+
}
83+
}

tests/Forms.Latte/forms.scope.phpt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php declare(strict_types=1);
2+
3+
use Nette\Bridges\FormsLatte\FormsExtension;
4+
use Nette\Forms\Form;
5+
use Tester\Assert;
6+
7+
require __DIR__ . '/../bootstrap.php';
8+
9+
10+
$form = new Form;
11+
$form->addText('name', 'Name:');
12+
$form->addText('email', 'Email:');
13+
14+
$latte = new Latte\Engine;
15+
$latte->addExtension(new FormsExtension);
16+
$latte->addProvider('uiControl', ['myForm' => $form]);
17+
18+
Assert::matchFile(
19+
__DIR__ . '/expected/forms.scope.php',
20+
$latte->compile(__DIR__ . '/templates/forms.scope.latte'),
21+
);
22+
23+
Assert::matchFile(
24+
__DIR__ . '/expected/forms.scope.html',
25+
$latte->renderToString(__DIR__ . '/templates/forms.scope.latte'),
26+
);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{* {form scope name}: form context without emitting <form> tag. Equivalent to {formContext}. *}
2+
3+
{form scope myForm}
4+
{label name /}
5+
{input name}
6+
{input email}
7+
{/form}
8+
9+
10+
{* Same with {formContext} — produces identical output. *}
11+
12+
{formContext myForm}
13+
{label name /}
14+
{input name}
15+
{input email}
16+
{/formContext}
17+
18+
19+
{* Attributes are accepted but ignored in scope mode (no <form> tag to apply them to). *}
20+
21+
{form scope myForm, class: "ignored"}
22+
{input name}
23+
{/form}
24+
25+
26+
{* Nested use: outer {form} emits <form>, inner {form scope} only resolves inputs. *}
27+
28+
{form myForm, class: "outer"}
29+
{input name}
30+
31+
{form scope myForm}
32+
{input email}
33+
{/form}
34+
{/form}

0 commit comments

Comments
 (0)