You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -2,43 +2,208 @@ Znovupoužití formulářů na více místech
2
2
**************************************
3
3
4
4
.[perex]
5
-
Jak použít stejný formulář na více místech a neduplikovat kód? To je v Nette opravdu snadné a máte na výběr víc způsobů.
5
+
V Nette máte k dispozici několik možností, jak použít stejný formulář na více místech a neduplikovat kód. V tomto článku si ukážeme různá řešení, včetně těch, kterým byste se měli vyhnout.
6
6
7
7
8
-
Továrna na formulář
9
-
===================
8
+
Továrna na formuláře
9
+
====================
10
10
11
-
Vytvoříme si třídu, která umí formulář vyrobit. Takové třídě se říká továrna. V místě, kde budeme chtít formulář použít (např. v presenteru), si továrnu [vyžádáme jako závislosti|dependency-injection:passing-dependencies].
11
+
Jedním ze základních přístupů k použití stejné komponenty na více místech je vytvoření metody nebo třídy, která tuto komponentu generuje, a následné volání této metody na různých místech aplikace. Takové metodě nebo třídě se říká *továrna*. Nezaměňujte prosím s návrhovým vzorem *factory method*, který popisuje specifický způsob využití továren a s tímto tématem nesouvisí.
12
12
13
-
Součástí továrny je i kód, který po úspěšném odeslaní formuláře předá data k dalšímu zpracování. Obvykle do modelové vrstvy. Zároveň zkontroluje, zda vše proběhlo v pořádku, a případné chyby [předá zpět |forms:validation#Chyby při zpracování] do formuláře. Model v následujícím příkladu reprezentuje třída `Facade`:
13
+
Jako příklad si vytvoříme továrnu, která bude sestavovat editační formulář:
14
14
15
15
```php
16
16
use Nette\Application\UI\Form;
17
17
18
-
class EditFormFactory
18
+
class FormFactory
19
+
{
20
+
public function createEditForm(): Form
21
+
{
22
+
$form = new Form;
23
+
$form->addText('title', 'Titulek:');
24
+
// zde se přidávají další formulářová pole
25
+
$form->addSubmit('send', 'Odeslat');
26
+
return $form;
27
+
}
28
+
}
29
+
```
30
+
31
+
Nyní můžete použít tuto továrnu na různých místech ve vaší aplikaci, například v presenterech nebo komponentách. A to tak, že si ji [vyžádáme jako závislost|dependency-injection:passing-dependencies]. Nejprve tedy třídu zapíšeme do konfiguračního souboru:
32
+
33
+
```neon
34
+
services:
35
+
- FormFactory
36
+
```
37
+
38
+
A poté ji použijeme v presenteru:
39
+
40
+
41
+
```php
42
+
class MyPresenter extends Nette\Application\UI\Presenter
19
43
{
20
44
public function __construct(
21
-
private Facade $facade,
45
+
private FormFactory $formFactory,
46
+
) {
47
+
}
48
+
49
+
protected function createComponentEditForm(): Form
50
+
{
51
+
$form = $this->formFactory->createEditForm();
52
+
$form->onSuccess[] = function () {
53
+
// zpracování odeslaných dat
54
+
};
55
+
return $form;
56
+
}
57
+
}
58
+
```
59
+
60
+
Formulářovou továrnu můžete rozšířit o další metody pro vytváření dalších druhů formulářů podle potřeby vaší aplikace. A samozřejmě můžeme přidat i metodu, která vytvoří základní formulář bez prvků, a tu budou ostatní metody využívat:
61
+
62
+
```php
63
+
class FormFactory
64
+
{
65
+
public function createForm(): Form
66
+
{
67
+
$form = new Form;
68
+
return $form;
69
+
}
70
+
71
+
public function createEditForm(): Form
72
+
{
73
+
$form = $this->createForm();
74
+
$form->addText('title', 'Titulek:');
75
+
// zde se přidávají další formulářová pole
76
+
$form->addSubmit('send', 'Odeslat');
77
+
return $form;
78
+
}
79
+
}
80
+
```
81
+
82
+
Metoda `createForm()` zatím nedělá nic užitečného, ale to se rychle změní.
83
+
84
+
85
+
Závislosti továrny
86
+
==================
87
+
88
+
Časem se ukáže, že potřebujeme, aby formuláře byly multijazyčné. To znamená, že všem formulářům musíme nastavit tzv. [translator|forms:rendering#Překládání]. Za tím účelem upravíme třídu `FormFactory` tak, aby přijímala objekt `Translator` jako závislost v konstruktoru, a předáme jej formuláři:
89
+
90
+
```php
91
+
use Nette\Localization\Translator;
92
+
93
+
class FormFactory
94
+
{
95
+
public function __construct(
96
+
private Translator $translator,
22
97
) {
23
98
}
24
99
25
-
public function create(/* parametry */): Form
100
+
public function createForm(): Form
26
101
{
27
102
$form = new Form;
103
+
$form->setTranslator($this->translator);
104
+
return $form;
105
+
}
106
+
107
+
// ...
108
+
}
109
+
```
110
+
111
+
Jelikož metodu `createForm()` volají i ostatní metody tvořící specifické formuláře, stačí translator nastavit jen v ní. A máme hotovo. Není potřeba měnit kód žádného presenteru nebo komponenty, což je skvělé.
112
+
113
+
114
+
Více továrních tříd
115
+
===================
28
116
29
-
// přidáme prvky do formuláře
117
+
Alternativně můžete vytvořit více tříd pro každý formulář, který chcete použít ve vaší aplikaci.
118
+
Tento přístup může zvýšit čitelnost kódu a usnadnit správu formulářů. Původní `FormFactory` necháme vytvářet jen čistý formulář se základní konfigurací (například s podporou překladů) a pro editační formulář vytvoříme novou továrnu `EditFormFactory`.
30
119
120
+
```php
121
+
class FormFactory
122
+
{
123
+
public function __construct(
124
+
private Translator $translator,
125
+
) {
126
+
}
127
+
128
+
public function create(): Form
129
+
{
130
+
$form = new Form;
131
+
$form->setTranslator($this->translator);
132
+
return $form;
133
+
}
134
+
}
135
+
136
+
137
+
// ✅ použití kompozice
138
+
class EditFormFactory
139
+
{
140
+
public function __construct(
141
+
private FormFactory $formFactory,
142
+
) {
143
+
}
144
+
145
+
public function create(): Form
146
+
{
147
+
$form = $this->formFactory->create();
148
+
// zde se přidávají další formulářová pole
31
149
$form->addSubmit('send', 'Odeslat');
32
-
$form->onSuccess[] = [$this, 'processForm'];
150
+
return $form;
151
+
}
152
+
}
153
+
```
33
154
155
+
Velmi důležité je, aby vazba mezi třídami `FormFactory` a `EditFormFactory` byla realizována kompozicí, nikoliv objektovou dědičností:
156
+
157
+
```php
158
+
// ⛔ TAKHLE NE! SEM DĚDIČNOST NEPATŘÍ
159
+
class EditFormFactory extends FormFactory
160
+
{
161
+
public function create(): Form
162
+
{
163
+
$form = parent::create();
164
+
$form->addText('title', 'Titulek:');
165
+
// zde se přidávají další formulářová pole
166
+
$form->addSubmit('send', 'Odeslat');
34
167
return $form;
35
168
}
169
+
}
170
+
```
171
+
172
+
Použití dedičnosti by bylo v tomto případě zcela kontraproduktivní. Na problémy byste narazili velmi rychle. Třeba ve chvíli, kdybyste chtěli přidat metodě `create()` parametry; PHP by zahlásilo chybu, že se její signatura liší od rodičovské.
173
+
Nebo při předávání závislosti do třídy `EditFormFactory` přes konstruktor. Nastala by situace, které říkáme [constructor hell |dependency-injection:passing-dependencies#Constructor hell].
174
+
175
+
Obecně je lepší dávat přednost kompozici před dědičností.
36
176
37
-
public function processForm(Form $form, array $values): void
177
+
178
+
Obsluha formuláře
179
+
=================
180
+
181
+
Obsluha formuláře, která se zavolá po úspěšném odeslání, může být také součástí tovární třídy. Bude fungovat tak, že odeslaná data předá modelu ke zpracování. Případné chyby [předá zpět |forms:validation#Chyby při zpracování] do formuláře. Model v následujícím příkladu reprezentuje třída `Facade`:
182
+
183
+
```php
184
+
class EditFormFactory
185
+
{
186
+
public function __construct(
187
+
private FormFactory $formFactory,
188
+
private Facade $facade,
189
+
) {
190
+
}
191
+
192
+
public function create(): Form
193
+
{
194
+
$form = $this->formFactory->create();
195
+
$form->addText('title', 'Titulek:');
196
+
// zde se přidávají další formulářová pole
197
+
$form->addSubmit('send', 'Odeslat');
198
+
$form->onSuccess[] = [$this, 'processForm'];
199
+
return $form;
200
+
}
201
+
202
+
public function processForm(Form $form, array $data): void
38
203
{
39
204
try {
40
-
// zpracování formuláře
41
-
$this->facade->process($values);
205
+
// zpracování odeslaných dat
206
+
$this->facade->process($data);
42
207
43
208
} catch (AnyModelException $e) {
44
209
$form->addError('...');
@@ -47,17 +212,7 @@ class EditFormFactory
47
212
}
48
213
```
49
214
50
-
Továrna může být samozřejmě parametrická, tj. může přijímat parametery, které ovlivní podobu vytvářeného formuláře.
51
-
52
-
Nyní si ukážeme předání továrny do presenteru. Nejprve ji zapíšeme do konfiguračního souboru:
53
-
54
-
```neon
55
-
services:
56
-
- EditFormFactory
57
-
```
58
-
59
-
A poté vyžádáme v presenteru. Tam také následuje další krok zpracování odeslaného formuláře a tím je přesměrování na další stránku:
60
-
215
+
Samotné přesměrování ale necháme na presenteru. Ten přidá události `onSuccess` další handler, který přesmerování provede. Díky tomu bude možné formulář použít v různých presenterech a v každém přesměrovat jinam.
61
216
62
217
```php
63
218
class MyPresenter extends Nette\Application\UI\Presenter
@@ -70,23 +225,47 @@ class MyPresenter extends Nette\Application\UI\Presenter
70
225
protected function createComponentEditForm(): Form
71
226
{
72
227
$form = $this->formFactory->create();
73
-
74
-
$form->onSuccess[] = function (Form $form) {
75
-
$this->redirect('this');
228
+
$form->onSuccess[] = function () {
229
+
$this->flashMessage('Záznam byl uložen');
230
+
$this->redirect('Homepage:');
76
231
};
77
-
78
232
return $form;
79
233
}
80
234
}
81
235
```
82
236
83
-
Tím, že přesměrování řeší až handler v presenteru, lze komponentu použít na více místech a na každém přesměrovat jinam.
237
+
Toto řešení využívá vlastnost formulářů, že když se nad formulářem nebo jeho prvkem zavolá `addError()`, už další handler `onSuccess` se nevolá.
238
+
239
+
240
+
Dědění od třídy Form
241
+
====================
242
+
243
+
Sestavený formulář nemá být potomkem formuláře. Jinými slovy, nepoužívejte toto řešení:
244
+
245
+
```php
246
+
// ⛔ TAKHLE NE! SEM DĚDIČNOST NEPATŘÍ
247
+
class EditForm extends Form
248
+
{
249
+
public function __construct(Translator $translator)
250
+
{
251
+
parent::__construct();
252
+
$form->addText('title', 'Titulek:');
253
+
// zde se přidávají další formulářová pole
254
+
$form->addSubmit('send', 'Odeslat');
255
+
$form->setTranslator($translator);
256
+
}
257
+
}
258
+
```
259
+
260
+
Místo sestavování formuláře v konstruktoru použijte továrnu.
261
+
262
+
Je potřeba si uvědomit, že třída `Form` je v první řadě nástrojem pro sestavení formuláře, tedy *form builder*. A sestavený formulář lze chápat jako její produkt. Jenže produkt není specifickým případem builderu, není mezi nimi vazba *is a* tvořící základ dědičnosti.
84
263
85
264
86
265
Komponenta s formulářem
87
266
=======================
88
267
89
-
Další možností je vytvořit novou [komponentu|application:components], jejíž součástí bude formulář. To nám dává možnost například renderovat formulář specifickým způsobem, neboť součástí komponenty je i šablona.
268
+
Zcela jiný přístup představuje tvorba [komponenty|application:components], jejíž součástí je formulář. To dává nové možnosti, například renderovat formulář specifickým způsobem, neboť součástí komponenty je i šablona.
90
269
Nebo lze využít signály pro AJAXovou komunikaci a donačítání informací do formuláře, například pro napovídání, atd.
91
270
92
271
@@ -105,35 +284,33 @@ class EditControl extends Nette\Application\UI\Control
105
284
protected function createComponentForm(): Form
106
285
{
107
286
$form = new Form;
108
-
109
-
// přidáme prvky do formuláře
110
-
287
+
$form->addText('title', 'Titulek:');
288
+
// zde se přidávají další formulářová pole
111
289
$form->addSubmit('send', 'Odeslat');
112
290
$form->onSuccess[] = [$this, 'processForm'];
113
291
114
292
return $form;
115
293
}
116
294
117
-
public function processForm(Form $form, array $values): void
295
+
public function processForm(Form $form, array $data): void
118
296
{
119
297
try {
120
-
// zpracování formuláře
121
-
$this->facade->process($values);
298
+
// zpracování odeslaných dat
299
+
$this->facade->process($data);
122
300
123
301
} catch (AnyModelException $e) {
124
302
$form->addError('...');
125
303
return;
126
304
}
127
305
128
306
// vyvolání události
129
-
$this->onSave($this, $values);
307
+
$this->onSave($this, $data);
130
308
}
131
309
}
132
310
```
133
311
134
312
Ještě vytvoříme továrnu, která bude tuto komponentu vyrábět. Stačí [zapsat její rozhraní|application:components#Komponenty se závislostmi]:
135
313
136
-
137
314
```php
138
315
interface EditControlFactory
139
316
{
@@ -173,5 +350,4 @@ class MyPresenter extends Nette\Application\UI\Presenter
0 commit comments