1: <?php
2:
3: /**
4: * This file is part of the Nette Framework (https://nette.org)
5: * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6: */
7:
8: namespace Nette\Forms;
9:
10: use Nette;
11:
12:
13: /**
14: * Container for form controls.
15: *
16: * @author David Grudl
17: *
18: * @property Nette\Utils\ArrayHash $values
19: * @property-read \ArrayIterator $controls
20: * @property-read Form $form
21: */
22: class Container extends Nette\ComponentModel\Container implements \ArrayAccess
23: {
24: /** @var callable[] function (Container $sender); Occurs when the form is validated */
25: public $onValidate;
26:
27: /** @var ControlGroup */
28: protected $currentGroup;
29:
30: /** @var bool */
31: private $validated;
32:
33:
34: /********************* data exchange ****************d*g**/
35:
36:
37: /**
38: * Fill-in with default values.
39: * @param array|\Traversable values used to fill the form
40: * @param bool erase other default values?
41: * @return self
42: */
43: public function setDefaults($values, $erase = FALSE)
44: {
45: $form = $this->getForm(FALSE);
46: if (!$form || !$form->isAnchored() || !$form->isSubmitted()) {
47: $this->setValues($values, $erase);
48: }
49: return $this;
50: }
51:
52:
53: /**
54: * Fill-in with values.
55: * @param array|\Traversable values used to fill the form
56: * @param bool erase other controls?
57: * @return self
58: */
59: public function setValues($values, $erase = FALSE)
60: {
61: if ($values instanceof \Traversable) {
62: $values = iterator_to_array($values);
63:
64: } elseif (!is_array($values)) {
65: throw new Nette\InvalidArgumentException(sprintf('First parameter must be an array, %s given.', gettype($values)));
66: }
67:
68: foreach ($this->getComponents() as $name => $control) {
69: if ($control instanceof IControl) {
70: if (array_key_exists($name, $values)) {
71: $control->setValue($values[$name]);
72:
73: } elseif ($erase) {
74: $control->setValue(NULL);
75: }
76:
77: } elseif ($control instanceof self) {
78: if (array_key_exists($name, $values)) {
79: $control->setValues($values[$name], $erase);
80:
81: } elseif ($erase) {
82: $control->setValues(array(), $erase);
83: }
84: }
85: }
86: return $this;
87: }
88:
89:
90: /**
91: * Returns the values submitted by the form.
92: * @param bool return values as an array?
93: * @return Nette\Utils\ArrayHash|array
94: */
95: public function getValues($asArray = FALSE)
96: {
97: $values = $asArray ? array() : new Nette\Utils\ArrayHash;
98: foreach ($this->getComponents() as $name => $control) {
99: if ($control instanceof IControl && !$control->isOmitted()) {
100: $values[$name] = $control->getValue();
101:
102: } elseif ($control instanceof self) {
103: $values[$name] = $control->getValues($asArray);
104: }
105: }
106: return $values;
107: }
108:
109:
110: /********************* validation ****************d*g**/
111:
112:
113: /**
114: * Is form valid?
115: * @return bool
116: */
117: public function isValid()
118: {
119: if (!$this->validated) {
120: if ($this->getErrors()) {
121: return FALSE;
122: }
123: $this->validate();
124: }
125: return !$this->getErrors();
126: }
127:
128:
129: /**
130: * Performs the server side validation.
131: * @param IControl[]
132: * @return void
133: */
134: public function validate(array $controls = NULL)
135: {
136: foreach ($controls === NULL ? $this->getControls() : $controls as $control) {
137: $control->validate();
138: }
139: $this->onValidate($this);
140: $this->validated = TRUE;
141: }
142:
143:
144: /**
145: * Returns all validation errors.
146: * @return array
147: */
148: public function getErrors()
149: {
150: $errors = array();
151: foreach ($this->getControls() as $control) {
152: $errors = array_merge($errors, $control->getErrors());
153: }
154: return array_unique($errors);
155: }
156:
157:
158: /********************* form building ****************d*g**/
159:
160:
161: /**
162: * @return self
163: */
164: public function setCurrentGroup(ControlGroup $group = NULL)
165: {
166: $this->currentGroup = $group;
167: return $this;
168: }
169:
170:
171: /**
172: * Returns current group.
173: * @return ControlGroup
174: */
175: public function getCurrentGroup()
176: {
177: return $this->currentGroup;
178: }
179:
180:
181: /**
182: * Adds the specified component to the IContainer.
183: * @param Nette\ComponentModel\IComponent
184: * @param string
185: * @param string
186: * @return self
187: * @throws Nette\InvalidStateException
188: */
189: public function addComponent(Nette\ComponentModel\IComponent $component, $name, $insertBefore = NULL)
190: {
191: parent::addComponent($component, $name, $insertBefore);
192: if ($this->currentGroup !== NULL && $component instanceof IControl) {
193: $this->currentGroup->add($component);
194: }
195: return $this;
196: }
197:
198:
199: /**
200: * Iterates over all form controls.
201: * @return \ArrayIterator
202: */
203: public function getControls()
204: {
205: return $this->getComponents(TRUE, 'Nette\Forms\IControl');
206: }
207:
208:
209: /**
210: * Returns form.
211: * @param bool throw exception if form doesn't exist?
212: * @return Form
213: */
214: public function getForm($need = TRUE)
215: {
216: return $this->lookup('Nette\Forms\Form', $need);
217: }
218:
219:
220: /********************* control factories ****************d*g**/
221:
222:
223: /**
224: * Adds single-line text input control to the form.
225: * @param string control name
226: * @param string label
227: * @param int width of the control (deprecated)
228: * @param int maximum number of characters the user may enter
229: * @return Nette\Forms\Controls\TextInput
230: */
231: public function addText($name, $label = NULL, $cols = NULL, $maxLength = NULL)
232: {
233: $control = new Controls\TextInput($label, $maxLength);
234: $control->setAttribute('size', $cols);
235: return $this[$name] = $control;
236: }
237:
238:
239: /**
240: * Adds single-line text input control used for sensitive input such as passwords.
241: * @param string control name
242: * @param string label
243: * @param int width of the control (deprecated)
244: * @param int maximum number of characters the user may enter
245: * @return Nette\Forms\Controls\TextInput
246: */
247: public function addPassword($name, $label = NULL, $cols = NULL, $maxLength = NULL)
248: {
249: $control = new Controls\TextInput($label, $maxLength);
250: $control->setAttribute('size', $cols);
251: return $this[$name] = $control->setType('password');
252: }
253:
254:
255: /**
256: * Adds multi-line text input control to the form.
257: * @param string control name
258: * @param string label
259: * @param int width of the control
260: * @param int height of the control in text lines
261: * @return Nette\Forms\Controls\TextArea
262: */
263: public function addTextArea($name, $label = NULL, $cols = NULL, $rows = NULL)
264: {
265: $control = new Controls\TextArea($label);
266: $control->setAttribute('cols', $cols)->setAttribute('rows', $rows);
267: return $this[$name] = $control;
268: }
269:
270:
271: /**
272: * Adds control that allows the user to upload files.
273: * @param string control name
274: * @param string label
275: * @param bool allows to upload multiple files
276: * @return Nette\Forms\Controls\UploadControl
277: */
278: public function addUpload($name, $label = NULL, $multiple = FALSE)
279: {
280: return $this[$name] = new Controls\UploadControl($label, $multiple);
281: }
282:
283:
284: /**
285: * Adds control that allows the user to upload multiple files.
286: * @param string control name
287: * @param string label
288: * @return Nette\Forms\Controls\UploadControl
289: */
290: public function addMultiUpload($name, $label = NULL)
291: {
292: return $this[$name] = new Controls\UploadControl($label, TRUE);
293: }
294:
295:
296: /**
297: * Adds hidden form control used to store a non-displayed value.
298: * @param string control name
299: * @param mixed default value
300: * @return Nette\Forms\Controls\HiddenField
301: */
302: public function addHidden($name, $default = NULL)
303: {
304: $control = new Controls\HiddenField;
305: $control->setDefaultValue($default);
306: return $this[$name] = $control;
307: }
308:
309:
310: /**
311: * Adds check box control to the form.
312: * @param string control name
313: * @param string caption
314: * @return Nette\Forms\Controls\Checkbox
315: */
316: public function addCheckbox($name, $caption = NULL)
317: {
318: return $this[$name] = new Controls\Checkbox($caption);
319: }
320:
321:
322: /**
323: * Adds set of radio button controls to the form.
324: * @param string control name
325: * @param string label
326: * @param array options from which to choose
327: * @return Nette\Forms\Controls\RadioList
328: */
329: public function addRadioList($name, $label = NULL, array $items = NULL)
330: {
331: return $this[$name] = new Controls\RadioList($label, $items);
332: }
333:
334:
335: /**
336: * Adds set of checkbox controls to the form.
337: * @return Nette\Forms\Controls\CheckboxList
338: */
339: public function addCheckboxList($name, $label = NULL, array $items = NULL)
340: {
341: return $this[$name] = new Controls\CheckboxList($label, $items);
342: }
343:
344:
345: /**
346: * Adds select box control that allows single item selection.
347: * @param string control name
348: * @param string label
349: * @param array items from which to choose
350: * @param int number of rows that should be visible
351: * @return Nette\Forms\Controls\SelectBox
352: */
353: public function addSelect($name, $label = NULL, array $items = NULL, $size = NULL)
354: {
355: $control = new Controls\SelectBox($label, $items);
356: if ($size > 1) {
357: $control->setAttribute('size', (int) $size);
358: }
359: return $this[$name] = $control;
360: }
361:
362:
363: /**
364: * Adds select box control that allows multiple item selection.
365: * @param string control name
366: * @param string label
367: * @param array options from which to choose
368: * @param int number of rows that should be visible
369: * @return Nette\Forms\Controls\MultiSelectBox
370: */
371: public function addMultiSelect($name, $label = NULL, array $items = NULL, $size = NULL)
372: {
373: $control = new Controls\MultiSelectBox($label, $items);
374: if ($size > 1) {
375: $control->setAttribute('size', (int) $size);
376: }
377: return $this[$name] = $control;
378: }
379:
380:
381: /**
382: * Adds button used to submit form.
383: * @param string control name
384: * @param string caption
385: * @return Nette\Forms\Controls\SubmitButton
386: */
387: public function addSubmit($name, $caption = NULL)
388: {
389: return $this[$name] = new Controls\SubmitButton($caption);
390: }
391:
392:
393: /**
394: * Adds push buttons with no default behavior.
395: * @param string control name
396: * @param string caption
397: * @return Nette\Forms\Controls\Button
398: */
399: public function addButton($name, $caption = NULL)
400: {
401: return $this[$name] = new Controls\Button($caption);
402: }
403:
404:
405: /**
406: * Adds graphical button used to submit form.
407: * @param string control name
408: * @param string URI of the image
409: * @param string alternate text for the image
410: * @return Nette\Forms\Controls\ImageButton
411: */
412: public function addImage($name, $src = NULL, $alt = NULL)
413: {
414: return $this[$name] = new Controls\ImageButton($src, $alt);
415: }
416:
417:
418: /**
419: * Adds naming container to the form.
420: * @param string name
421: * @return Container
422: */
423: public function addContainer($name)
424: {
425: $control = new self;
426: $control->currentGroup = $this->currentGroup;
427: return $this[$name] = $control;
428: }
429:
430:
431: /********************* interface \ArrayAccess ****************d*g**/
432:
433:
434: /**
435: * Adds the component to the container.
436: * @param string component name
437: * @param Nette\ComponentModel\IComponent
438: * @return void
439: */
440: public function offsetSet($name, $component)
441: {
442: $this->addComponent($component, $name);
443: }
444:
445:
446: /**
447: * Returns component specified by name. Throws exception if component doesn't exist.
448: * @param string component name
449: * @return Nette\ComponentModel\IComponent
450: * @throws Nette\InvalidArgumentException
451: */
452: public function offsetGet($name)
453: {
454: return $this->getComponent($name, TRUE);
455: }
456:
457:
458: /**
459: * Does component specified by name exists?
460: * @param string component name
461: * @return bool
462: */
463: public function offsetExists($name)
464: {
465: return $this->getComponent($name, FALSE) !== NULL;
466: }
467:
468:
469: /**
470: * Removes component from the container.
471: * @param string component name
472: * @return void
473: */
474: public function offsetUnset($name)
475: {
476: $component = $this->getComponent($name, FALSE);
477: if ($component !== NULL) {
478: $this->removeComponent($component);
479: }
480: }
481:
482:
483: /**
484: * Prevents cloning.
485: */
486: public function __clone()
487: {
488: throw new Nette\NotImplementedException('Form cloning is not supported yet.');
489: }
490:
491: }
492: