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