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