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