1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\Forms\Rendering;
9:
10: use Nette;
11: use Nette\Utils\Html;
12: use Nette\Utils\IHtmlString;
13:
14:
15: 16: 17:
18: class DefaultFormRenderer implements Nette\Forms\IFormRenderer
19: {
20: use Nette\SmartObject;
21:
22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57:
58: public $wrappers = [
59: 'form' => [
60: 'container' => null,
61: ],
62:
63: 'error' => [
64: 'container' => 'ul class=error',
65: 'item' => 'li',
66: ],
67:
68: 'group' => [
69: 'container' => 'fieldset',
70: 'label' => 'legend',
71: 'description' => 'p',
72: ],
73:
74: 'controls' => [
75: 'container' => 'table',
76: ],
77:
78: 'pair' => [
79: 'container' => 'tr',
80: '.required' => 'required',
81: '.optional' => null,
82: '.odd' => null,
83: '.error' => null,
84: ],
85:
86: 'control' => [
87: 'container' => 'td',
88: '.odd' => null,
89:
90: 'description' => 'small',
91: 'requiredsuffix' => '',
92: 'errorcontainer' => 'span class=error',
93: 'erroritem' => '',
94:
95: '.required' => 'required',
96: '.text' => 'text',
97: '.password' => 'text',
98: '.file' => 'text',
99: '.email' => 'text',
100: '.number' => 'text',
101: '.submit' => 'button',
102: '.image' => 'imagebutton',
103: '.button' => 'button',
104: ],
105:
106: 'label' => [
107: 'container' => 'th',
108: 'suffix' => null,
109: 'requiredsuffix' => '',
110: ],
111:
112: 'hidden' => [
113: 'container' => null,
114: ],
115: ];
116:
117:
118: protected $form;
119:
120:
121: protected $counter;
122:
123:
124: 125: 126: 127: 128: 129:
130: public function render(Nette\Forms\Form $form, $mode = null)
131: {
132: if ($this->form !== $form) {
133: $this->form = $form;
134: }
135:
136: $s = '';
137: if (!$mode || $mode === 'begin') {
138: $s .= $this->renderBegin();
139: }
140: if (!$mode || strtolower($mode) === 'ownerrors') {
141: $s .= $this->renderErrors();
142:
143: } elseif ($mode === 'errors') {
144: $s .= $this->renderErrors(null, false);
145: }
146: if (!$mode || $mode === 'body') {
147: $s .= $this->renderBody();
148: }
149: if (!$mode || $mode === 'end') {
150: $s .= $this->renderEnd();
151: }
152: return $s;
153: }
154:
155:
156: 157: 158: 159:
160: public function renderBegin()
161: {
162: $this->counter = 0;
163:
164: foreach ($this->form->getControls() as $control) {
165: $control->setOption('rendered', false);
166: }
167:
168: if ($this->form->isMethod('get')) {
169: $el = clone $this->form->getElementPrototype();
170: $query = parse_url($el->action, PHP_URL_QUERY);
171: $el->action = str_replace("?$query", '', $el->action);
172: $s = '';
173: foreach (preg_split('#[;&]#', $query, -1, PREG_SPLIT_NO_EMPTY) as $param) {
174: $parts = explode('=', $param, 2);
175: $name = urldecode($parts[0]);
176: if (!isset($this->form[$name])) {
177: $s .= Html::el('input', ['type' => 'hidden', 'name' => $name, 'value' => urldecode($parts[1])]);
178: }
179: }
180: return $el->startTag() . ($s ? "\n\t" . $this->getWrapper('hidden container')->setHtml($s) : '');
181:
182: } else {
183: return $this->form->getElementPrototype()->startTag();
184: }
185: }
186:
187:
188: 189: 190: 191:
192: public function renderEnd()
193: {
194: $s = '';
195: foreach ($this->form->getControls() as $control) {
196: if ($control->getOption('type') === 'hidden' && !$control->getOption('rendered')) {
197: $s .= $control->getControl();
198: }
199: }
200: if (iterator_count($this->form->getComponents(true, Nette\Forms\Controls\TextInput::class)) < 2) {
201: $s .= '<!--[if IE]><input type=IEbug disabled style="display:none"><![endif]-->';
202: }
203: if ($s) {
204: $s = $this->getWrapper('hidden container')->setHtml($s) . "\n";
205: }
206:
207: return $s . $this->form->getElementPrototype()->endTag() . "\n";
208: }
209:
210:
211: 212: 213: 214:
215: public function renderErrors(Nette\Forms\IControl $control = null, $own = true)
216: {
217: $errors = $control
218: ? $control->getErrors()
219: : ($own ? $this->form->getOwnErrors() : $this->form->getErrors());
220: if (!$errors) {
221: return '';
222: }
223: $container = $this->getWrapper($control ? 'control errorcontainer' : 'error container');
224: $item = $this->getWrapper($control ? 'control erroritem' : 'error item');
225:
226: foreach ($errors as $error) {
227: $item = clone $item;
228: if ($error instanceof IHtmlString) {
229: $item->addHtml($error);
230: } else {
231: $item->setText($error);
232: }
233: $container->addHtml($item);
234: }
235: return "\n" . $container->render($control ? 1 : 0);
236: }
237:
238:
239: 240: 241: 242:
243: public function renderBody()
244: {
245: $s = $remains = '';
246:
247: $defaultContainer = $this->getWrapper('group container');
248: $translator = $this->form->getTranslator();
249:
250: foreach ($this->form->getGroups() as $group) {
251: if (!$group->getControls() || !$group->getOption('visual')) {
252: continue;
253: }
254:
255: $container = $group->getOption('container', $defaultContainer);
256: $container = $container instanceof Html ? clone $container : Html::el($container);
257:
258: $id = $group->getOption('id');
259: if ($id) {
260: $container->id = $id;
261: }
262:
263: $s .= "\n" . $container->startTag();
264:
265: $text = $group->getOption('label');
266: if ($text instanceof IHtmlString) {
267: $s .= $this->getWrapper('group label')->addHtml($text);
268:
269: } elseif ($text != null) {
270: if ($translator !== null) {
271: $text = $translator->translate($text);
272: }
273: $s .= "\n" . $this->getWrapper('group label')->setText($text) . "\n";
274: }
275:
276: $text = $group->getOption('description');
277: if ($text instanceof IHtmlString) {
278: $s .= $text;
279:
280: } elseif ($text != null) {
281: if ($translator !== null) {
282: $text = $translator->translate($text);
283: }
284: $s .= $this->getWrapper('group description')->setText($text) . "\n";
285: }
286:
287: $s .= $this->renderControls($group);
288:
289: $remains = $container->endTag() . "\n" . $remains;
290: if (!$group->getOption('embedNext')) {
291: $s .= $remains;
292: $remains = '';
293: }
294: }
295:
296: $s .= $remains . $this->renderControls($this->form);
297:
298: $container = $this->getWrapper('form container');
299: $container->setHtml($s);
300: return $container->render(0);
301: }
302:
303:
304: 305: 306: 307: 308:
309: public function renderControls($parent)
310: {
311: if (!($parent instanceof Nette\Forms\Container || $parent instanceof Nette\Forms\ControlGroup)) {
312: throw new Nette\InvalidArgumentException('Argument must be Nette\Forms\Container or Nette\Forms\ControlGroup instance.');
313: }
314:
315: $container = $this->getWrapper('controls container');
316:
317: $buttons = null;
318: foreach ($parent->getControls() as $control) {
319: if ($control->getOption('rendered') || $control->getOption('type') === 'hidden' || $control->getForm(false) !== $this->form) {
320:
321:
322: } elseif ($control->getOption('type') === 'button') {
323: $buttons[] = $control;
324:
325: } else {
326: if ($buttons) {
327: $container->addHtml($this->renderPairMulti($buttons));
328: $buttons = null;
329: }
330: $container->addHtml($this->renderPair($control));
331: }
332: }
333:
334: if ($buttons) {
335: $container->addHtml($this->renderPairMulti($buttons));
336: }
337:
338: $s = '';
339: if (count($container)) {
340: $s .= "\n" . $container . "\n";
341: }
342:
343: return $s;
344: }
345:
346:
347: 348: 349: 350:
351: public function renderPair(Nette\Forms\IControl $control)
352: {
353: $pair = $this->getWrapper('pair container');
354: $pair->addHtml($this->renderLabel($control));
355: $pair->addHtml($this->renderControl($control));
356: $pair->class($this->getValue($control->isRequired() ? 'pair .required' : 'pair .optional'), true);
357: $pair->class($control->hasErrors() ? $this->getValue('pair .error') : null, true);
358: $pair->class($control->getOption('class'), true);
359: if (++$this->counter % 2) {
360: $pair->class($this->getValue('pair .odd'), true);
361: }
362: $pair->id = $control->getOption('id');
363: return $pair->render(0);
364: }
365:
366:
367: 368: 369: 370: 371:
372: public function renderPairMulti(array $controls)
373: {
374: $s = [];
375: foreach ($controls as $control) {
376: if (!$control instanceof Nette\Forms\IControl) {
377: throw new Nette\InvalidArgumentException('Argument must be array of Nette\Forms\IControl instances.');
378: }
379: $description = $control->getOption('description');
380: if ($description instanceof IHtmlString) {
381: $description = ' ' . $description;
382:
383: } elseif ($description != null) {
384: if ($control instanceof Nette\Forms\Controls\BaseControl) {
385: $description = $control->translate($description);
386: }
387: $description = ' ' . $this->getWrapper('control description')->setText($description);
388:
389: } else {
390: $description = '';
391: }
392:
393: $control->setOption('rendered', true);
394: $el = $control->getControl();
395: if ($el instanceof Html && $el->getName() === 'input') {
396: $el->class($this->getValue("control .$el->type"), true);
397: }
398: $s[] = $el . $description;
399: }
400: $pair = $this->getWrapper('pair container');
401: $pair->addHtml($this->renderLabel($control));
402: $pair->addHtml($this->getWrapper('control container')->setHtml(implode(' ', $s)));
403: return $pair->render(0);
404: }
405:
406:
407: 408: 409: 410:
411: public function renderLabel(Nette\Forms\IControl $control)
412: {
413: $suffix = $this->getValue('label suffix') . ($control->isRequired() ? $this->getValue('label requiredsuffix') : '');
414: $label = $control->getLabel();
415: if ($label instanceof Html) {
416: $label->addHtml($suffix);
417: if ($control->isRequired()) {
418: $label->class($this->getValue('control .required'), true);
419: }
420: } elseif ($label != null) {
421: $label .= $suffix;
422: }
423: return $this->getWrapper('label container')->setHtml($label);
424: }
425:
426:
427: 428: 429: 430:
431: public function renderControl(Nette\Forms\IControl $control)
432: {
433: $body = $this->getWrapper('control container');
434: if ($this->counter % 2) {
435: $body->class($this->getValue('control .odd'), true);
436: }
437:
438: $description = $control->getOption('description');
439: if ($description instanceof IHtmlString) {
440: $description = ' ' . $description;
441:
442: } elseif ($description != null) {
443: if ($control instanceof Nette\Forms\Controls\BaseControl) {
444: $description = $control->translate($description);
445: }
446: $description = ' ' . $this->getWrapper('control description')->setText($description);
447:
448: } else {
449: $description = '';
450: }
451:
452: if ($control->isRequired()) {
453: $description = $this->getValue('control requiredsuffix') . $description;
454: }
455:
456: $control->setOption('rendered', true);
457: $el = $control->getControl();
458: if ($el instanceof Html && $el->getName() === 'input') {
459: $el->class($this->getValue("control .$el->type"), true);
460: }
461: return $body->setHtml($el . $description . $this->renderErrors($control));
462: }
463:
464:
465: 466: 467: 468:
469: protected function getWrapper($name)
470: {
471: $data = $this->getValue($name);
472: return $data instanceof Html ? clone $data : Html::el($data);
473: }
474:
475:
476: 477: 478: 479:
480: protected function getValue($name)
481: {
482: $name = explode(' ', $name);
483: $data = &$this->wrappers[$name[0]][$name[1]];
484: return $data;
485: }
486: }
487: