1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Nette\Forms;
13:
14: use Nette;
15:
16:
17:
18: 19: 20: 21: 22:
23: final class InstantClientScript extends Nette\Object
24: {
25:
26: private $validateScripts;
27:
28:
29: private $toggleScript;
30:
31:
32: private $central;
33:
34:
35: private $form;
36:
37:
38:
39: public function __construct(Form $form)
40: {
41: $this->form = $form;
42: }
43:
44:
45:
46: public function enable()
47: {
48: $this->validateScripts = array();
49: $this->toggleScript = '';
50: $this->central = TRUE;
51:
52: foreach ($this->form->getControls() as $control) {
53: $script = $this->getValidateScript($control->getRules());
54: if ($script) {
55: $this->validateScripts[$control->getHtmlName()] = $script;
56: }
57: $this->toggleScript .= $this->getToggleScript($control->getRules());
58:
59: if ($control instanceof ISubmitterControl && $control->getValidationScope() !== TRUE) {
60: $this->central = FALSE;
61: }
62: }
63:
64: if ($this->validateScripts || $this->toggleScript) {
65: if ($this->central) {
66: $this->form->getElementPrototype()->onsubmit("return nette.validateForm(this)", TRUE);
67:
68: } else {
69: foreach ($this->form->getComponents(TRUE, 'Nette\Forms\ISubmitterControl') as $control) {
70: if ($control->getValidationScope()) {
71: $control->getControlPrototype()->onclick("return nette.validateForm(this)", TRUE);
72: }
73: }
74: }
75: }
76: }
77:
78:
79:
80: 81: 82: 83:
84: public function renderClientScript()
85: {
86: if (!$this->validateScripts && !$this->toggleScript) {
87: return;
88: }
89:
90: $formName = json_encode((string) $this->form->getElementPrototype()->id);
91: ob_start();
92: require __DIR__ . '/InstantClientScript.phtml';
93: return ob_get_clean();
94: }
95:
96:
97:
98: private function getValidateScript(Rules $rules)
99: {
100: $res = '';
101: foreach ($rules as $rule) {
102: if (!is_string($rule->operation)) continue;
103:
104: if (strcasecmp($rule->operation, 'Nette\\Forms\\InstantClientScript::javascript') === 0) {
105: $res .= "$rule->arg\n";
106: continue;
107: }
108:
109: $script = $this->getClientScript($rule->control, $rule->operation, $rule->arg);
110: if (!$script) continue;
111:
112: if ($rule->type === Rule::VALIDATOR && !empty($rule->message)) {
113: $message = Rules::formatMessage($rule, FALSE);
114: $res .= "$script\n"
115: . "if (" . ($rule->isNegative ? '' : '!') . "res) "
116: . "return " . json_encode((string) $message) . (strpos($message, '%value') === FALSE ? '' : ".replace('%value', val);\n") . ";\n";
117:
118: } elseif ($rule->type === Rule::CONDITION) {
119: $innerScript = $this->getValidateScript($rule->subRules);
120: if ($innerScript) {
121: $res .= "$script\nif (" . ($rule->isNegative ? '!' : '') . "res) {\n" . Nette\String::indent($innerScript) . "}\n";
122: if ($rule->control instanceof ISubmitterControl) {
123: $this->central = FALSE;
124: }
125: }
126: }
127: }
128: return $res;
129: }
130:
131:
132:
133: private function getToggleScript(Rules $rules, $cond = NULL)
134: {
135: $s = '';
136: foreach ($rules->getToggles() as $id => $visible) {
137: $s .= "visible = true; {$cond}\n"
138: . "nette.toggle(" . json_encode((string) $id) . ", " . ($visible ? '' : '!') . "visible);\n";
139: }
140: $formName = json_encode((string) $this->form->getElementPrototype()->id);
141: foreach ($rules as $rule) {
142: if ($rule->type === Rule::CONDITION && is_string($rule->operation)) {
143: $script = $this->getClientScript($rule->control, $rule->operation, $rule->arg);
144: if ($script) {
145: $res = $this->getToggleScript($rule->subRules, $cond . "$script visible = visible && " . ($rule->isNegative ? '!' : '') . "res;\n");
146: if ($res) {
147: $el = $rule->control->getControlPrototype();
148: if ($el->getName() === 'select') {
149: $el->onchange("nette.forms[$formName].toggle(this)", TRUE);
150: } else {
151: $el->onclick("nette.forms[$formName].toggle(this)", TRUE);
152:
153: }
154: $s .= $res;
155: }
156: }
157: }
158: }
159: return $s;
160: }
161:
162:
163:
164: private function getClientScript(IFormControl $control, $operation, $arg)
165: {
166: $operation = strtolower($operation);
167: $elem = 'form[' . json_encode($control->getHtmlName()) . ']';
168:
169: switch (TRUE) {
170: case $control instanceof HiddenField || $control->isDisabled():
171: return NULL;
172:
173: case $operation === ':filled' && $control instanceof RadioList:
174: return "res = (val = nette.getValue($elem)) !== null;";
175:
176: case $operation === ':submitted' && $control instanceof SubmitButton:
177: return "res = sender && sender.name==" . json_encode($control->getHtmlName()) . ";";
178:
179: case $operation === ':equal' && $control instanceof MultiSelectBox:
180: $tmp = array();
181: foreach ((is_array($arg) ? $arg : array($arg)) as $item) {
182: $tmp[] = "options[i].value==" . json_encode((string) $item);
183: }
184: $first = $control->isFirstSkipped() ? 1 : 0;
185: return "var options = $elem.options; res = false;\n"
186: . "for (var i=$first, len=options.length; i<len; i++)\n\t"
187: . "if (options[i].selected && (" . implode(' || ', $tmp) . ")) { res = true; break; }";
188:
189: case $operation === ':filled' && $control instanceof SelectBox:
190: return "res = $elem.selectedIndex >= " . ($control->isFirstSkipped() ? 1 : 0) . ";";
191:
192: case $operation === ':filled' && $control instanceof TextBase:
193: return "val = nette.getValue($elem); res = val!='' && val!=" . json_encode((string) $control->getEmptyValue()) . ";";
194:
195: case $operation === ':minlength' && $control instanceof TextBase:
196: return "res = (val = nette.getValue($elem)).length>=" . (int) $arg . ";";
197:
198: case $operation === ':maxlength' && $control instanceof TextBase:
199: return "res = (val = nette.getValue($elem)).length<=" . (int) $arg . ";";
200:
201: case $operation === ':length' && $control instanceof TextBase:
202: if (!is_array($arg)) {
203: $arg = array($arg, $arg);
204: }
205: return "val = nette.getValue($elem); res = " . ($arg[0] === NULL ? "true" : "val.length>=" . (int) $arg[0]) . " && "
206: . ($arg[1] === NULL ? "true" : "val.length<=" . (int) $arg[1]) . ";";
207:
208: case $operation === ':email' && $control instanceof TextBase:
209: return 'res = /^[^@\s]+@[^@\s]+\.[a-z]{2,10}$/i.test(val = nette.getValue('.$elem.'));';
210:
211: case $operation === ':url' && $control instanceof TextBase:
212: return 'res = /^.+\.[a-z]{2,6}(\\/.*)?$/i.test(val = nette.getValue('.$elem.'));';
213:
214: case $operation === ':regexp' && $control instanceof TextBase:
215: if (!preg_match('#^(/.*/)([imu]*)$#', $arg, $matches)) {
216: return NULL;
217: }
218: $arg = $matches[1] . str_replace('u', '', $matches[2]);
219: return "res = $arg.test(val = nette.getValue($elem));";
220:
221: case $operation === ':integer' && $control instanceof TextBase:
222: return "res = /^-?[0-9]+$/.test(val = nette.getValue($elem));";
223:
224: case $operation === ':float' && $control instanceof TextBase:
225: return "res = /^-?[0-9]*[.,]?[0-9]+$/.test(val = nette.getValue($elem));";
226:
227: case $operation === ':range' && $control instanceof TextBase:
228: return "val = nette.getValue($elem); res = " . ($arg[0] === NULL ? "true" : "parseFloat(val)>=" . json_encode((float) $arg[0])) . " && "
229: . ($arg[1] === NULL ? "true" : "parseFloat(val)<=" . json_encode((float) $arg[1])) . ";";
230:
231: case $operation === ':filled' && $control instanceof FormControl:
232: return "res = (val = nette.getValue($elem)) != '';";
233:
234: case $operation === ':valid' && $control instanceof FormControl:
235: return "res = !this[" . json_encode($control->getHtmlName()) . "](sender);";
236:
237: case $operation === ':equal' && $control instanceof FormControl:
238: if ($control instanceof Checkbox) $arg = (bool) $arg;
239: $tmp = array();
240: foreach ((is_array($arg) ? $arg : array($arg)) as $item) {
241: if ($item instanceof IFormControl) {
242: $tmp[] = "val==nette.getValue(form[" . json_encode($item->getHtmlName()) . "])";
243: } else {
244: $tmp[] = "val==" . json_encode($item);
245: }
246: }
247: return "val = nette.getValue($elem); res = (" . implode(' || ', $tmp) . ");";
248: }
249: }
250:
251:
252:
253: public static function javascript()
254: {
255: return TRUE;
256: }
257:
258: }
259: