37 require_once realpath(dirname(__FILE__).
"/auto_form_layout.inc");
38 require_once realpath(dirname(__FILE__).
"/validation.inc");
39 require_once realpath(dirname(__FILE__).
"/field_renderers.inc");
168 global $auto_form_defaults;
170 $this->subordinate =
false;
174 foreach($auto_form_defaults as $field => $value)
176 $this->$field = $value;
181 throw new FakoliException(
"Form has no target object");
184 $this->data =& $target;
187 $this->
id = ($id !=
"") ? $id : get_class($this->data).
"_form";
189 $this->validator->id = $this->id;
191 $this->fields = $this->data->getFields();
193 $filter = $this->data->getFilter();
196 foreach($this->fields as $field => $type)
198 if ($filter && $filter->isExcluded($field))
continue;
203 $aliases = $this->data->getFieldAliases();
207 foreach($aliases as $field => $alias)
209 $this->
alias($field, $alias);
219 $this->
annotate($field, $annotation);
223 $pks = $this->data->getPrimaryKeyList();
229 $hiddenFields = $this->data->getHiddenFields();
230 foreach($hiddenFields as $h)
248 $this->dataSet = $params;
265 for($i = 0; $i < func_num_args(); ++$i)
267 $this->fieldOrder[] = func_get_arg($i);
280 if (!array_key_exists($field, $this->fields))
return null;
282 $type = $this->fields[$field];
284 if ($type ==
"Timestamp")
return null;
291 throw new FakoliException(
"AutoForm::createRenderer(): Unknown type '$type'");
313 $filter = $this->data->getFilter();
315 foreach(array_keys($this->fields) as $field)
317 if ($filter && $filter->isExcluded($field))
continue;
318 if ($this->
readonly[$field])
continue;
325 $renderer->addValidatorsToForm($field,
$required);
329 foreach($this->additional as $r)
332 $required = (array_search($r[
'field'], $this->
required) === FALSE) ?
false :
true;
333 $r[
'renderer']->addValidatorsToForm($r[
'field'],
$required);
350 $this->subordinate =
true;
355 if ($this->validatorsConfigured)
return;
359 foreach($this->validator->validators as $v)
365 $this->validatorsConfigured =
true;
383 $filter = $obj->getFilter();
384 $pk = $obj->getPrimaryKey();
387 foreach(array_keys($this->fields) as $field)
389 if ($field != $pk && !($filter && $filter->isExcluded($field)))
394 $renderer->renderScript($field);
399 foreach($this->additional as $r)
401 $r[
'renderer']->renderScript($r[
'field']);
404 echo
"<script type='text/javascript'>\n";
405 echo $this->validator->writeScript();
407 echo
"function onSubmit{$this->id}(form)\n{\n";
409 echo
"\tif (validate_{$this->id} != null && !validate_{$this->id}(form)) return false;\n";
411 foreach(array_keys($this->fields) as $field)
413 if ($field != $pk && !($filter && $filter->isExcluded($field)))
418 $renderer->renderOnSubmitHandler($field);
423 foreach($this->additional as $r)
425 $r[
'renderer']->renderOnSubmitHandler($r[
'field']);
428 if ($this->onSubmitHandler)
430 echo
"\t if (!{$this->onSubmitHandler}(form)) return false;\n;";
433 if ($this->confirmPrompt)
435 echo
"\t if (!confirm(\"".jsSafe($this->confirmPrompt).
"\")) return false;\n";
438 if ($this->interstitialMessage)
440 echo
"\tinterstitial('".jsSafe($this->interstitialMessage).
"', '".
jsSafe($this->interstitialSpinner).
"');\n;";
443 echo
"\treturn true;\n}\n\n";
445 echo
"\twindow.addEvent('domready', function() {\n";
447 if ($this->allowPartialSave && $this->partialSaveContainerID)
449 $managerOptions =
"{partialSaveContainer: '{$this->partialSaveContainerID}', partialSaveLabel: '{$this->partialSaveLabel}'}";
453 $managerOptions =
"{}";
456 echo
"\tnew AutoFormManager('{$this->id}', {$managerOptions});\n";
458 if ($this->ajaxSubmitHandler && !$this->readOnlyForm)
461 document.id(
'<?echo $this->id?>').iFrameFormRequest(
463 'onRequest':
function() {
return onSubmit<?echo $this->
id?>(document.id(
'<?echo $this->id?>')); },
473 foreach($this->collapsibleGroups as $group => $toggle)
475 if (is_bool($toggle))
477 $groupID = $this->
id .
"_" .
codify($group) .
"_group";
478 echo
"\tdocument.id('$groupID').getElement('legend').addEvent('click', function(e) { var g = document.id('$groupID'); if (g.hasClass('collapsed')) { g.removeClass('collapsed'); g.addClass('expanded'); } else { g.removeClass('expanded'); g.addClass('collapsed'); } });\n";
479 echo
"\tdocument.id('$groupID').addClass('toggleSelf');\n";
483 $groupID = $this->
id .
"_" .
codify($group) .
"_group";
484 $toggleID = $this->
id .
"_" . $toggle;
489 echo
"\tdocument.id('$toggleID').addEvent('click', function(e) { AutoFormManager.toggleGroup('$groupID', this.checked); });\n";
493 echo
"\tdocument.id('$toggleID').addEvent('change', function(e) { AutoFormManager.toggleGroup('$groupID', (this.value != '0')); });\n";
499 $script = ob_get_contents();
514 if (array_key_exists($field, $this->overrides))
516 if ($this->overrides[$field][
'renderer'])
return $this->overrides[$field][
'renderer'];
519 if (array_key_exists($field, $this->renderers))
521 return $this->renderers[$field];
525 foreach($this->additional as $r)
527 $renderer = $r[
'renderer'];
528 $rField = $r[
'field'];
530 $rField = $renderer->field;
531 if($rField == $field)
536 trace(
"AutoForm::getRenderer(): Unknown field: $field", 2);
549 function override($field, $label =
"", $renderer =
null)
551 if (!isset($this->overrides[$field]))
553 $this->overrides[$field] = array(
'label' => $label,
'renderer' => $renderer);
557 if ($label) $this->overrides[$field][
'label'] = $label;
558 if ($renderer) $this->overrides[$field][
'renderer'] = $renderer;
571 if ($type ==
"Timestamp")
return;
576 $this->
override($field, $label, $renderer);
580 throw new FakoliException(
"AutoForm::createRenderer(): Unknown type '$type'");
594 function add($renderer, $field =
"", $label =
"")
596 if (is_string($renderer))
604 throw new FakoliException(
"Unknown Type - {$renderer}");
608 array_push($this->additional, array(
'renderer' => $renderer,
'field' => $field));
609 if ($field && $label)
611 $this->
alias($field, $label);
626 foreach(func_get_args() as
$hidden)
643 $exceptions = array_combine(func_get_args(), func_get_args());
644 foreach($this->data->getFields() as $name => $type)
646 if (!array_key_exists($name, $exceptions))
648 $this->hidden[$name] =
true;
660 return array_key_exists($field, $this->hidden) ? $this->hidden[$field] :
false;
672 $this->annotations[$field] = $text;
686 for($i = 0; $i < func_num_args(); $i += 2)
688 $field = func_get_arg($i);
689 $label = func_get_arg($i + 1);
690 $this->
override($field, $label);
697 function related($class, $constraint =
"", $format =
null, $field =
null, $label =
null, $allowAdd =
false, $allowNone =
true)
700 if (!$format) $format = $obj->default_format;
701 if (!$field) $field = $obj->getPrimaryKey();
702 if (!$label) $label = $obj->prettifyClassName();
704 if (!$this->data->hasField($field))
706 throw new FakoliException(get_class($this->data).
" has no field '{$field}'");
711 throw new FakoliException(
"Format not specified for related $class");
714 return new RelatedItemSelectFieldRenderer($this, $field, $label, $class, $constraint, $format, $obj->getPrimaryKey(), $allowAdd, $allowNone);
728 function crossReference($xrefClass, $options, $format, $field, $label =
null, $xrefField =
null)
730 if (!$label) $label =
prettify($field);
731 return new CrossReferenceSelectFieldRenderer($this, $field, $label, $options, $format, $xrefClass, $xrefField);
742 function dropdown($field, $label =
"", $options =
null, $onchange =
null, $grouped =
false)
744 return new SelectFieldRenderer($this, $field, $label, $options, $onchange, $grouped);
757 return $this->
dropdown($field, $label, $options, $onchange,
true);
768 function radio($field, $label, $options, $onchange =
null)
779 function checklist($field, $label, $options, $grouped =
false)
789 function toggle($field, $label =
"", $ragged =
false)
792 $this->
override($field, $label, $renderer);
803 $group = (array_key_exists($name, $this->groups)) ? $this->groups[$name] : array();
804 for($i = 1; $i < func_num_args(); ++$i)
806 $group[] = func_get_arg($i);
809 $this->groups[$name] = $group;
819 if (!array_key_exists($name, $this->groups))
821 $this->groups[$name] = array();
824 for($i = 1; $i < func_num_args(); ++$i)
826 $this->groups[$name][] = func_get_arg($i);
839 $this->groupDescriptions[
"{$name}:{$position}"] = $description;
849 $key =
"{$name}:{$position}";
850 if (array_key_exists($key, $this->groupDescriptions))
return $this->groupDescriptions[$key];
861 $this->collapsibleGroups[$name] = $toggleControl;
871 $this->groupCSSClasses[$name] = $cssClass;
881 return array_key_exists($name, $this->groupCSSClasses) ? $this->groupCSSClasses[$name] :
"";
891 unset($this->groups[$name]);
899 foreach(func_get_args() as $naked)
939 array_push($this->
required, $required);
949 foreach($this->additional as $r)
951 $renderer = $r[
'renderer'];
952 $rField = $r[
'field'];
953 trace(
"## $field == $rField", 3);
954 if($rField == $field)
968 return (array_search($field, $this->
required) !== FALSE) ? true :
false;
977 function requiredIfChecked($field, $checkbox, $fieldTitle =
"", $checkboxTitle =
"", $message =
"")
979 if ($this->data->hasField($field))
983 $checkboxTitle ? $checkboxTitle : $this->
prettifyFieldName($checkbox), $message));
997 if ($this->data->hasField($field))
1001 $subFieldTitle, $message));
1016 if ($this->data->hasField($field))
1035 function dateRange($field, $min =
"", $max =
"", $message =
null)
1037 if ($this->data->hasField($field))
1050 for($i = 0; $i < func_num_args(); $i += 2)
1052 $field = func_get_arg($i);
1053 $message = func_get_arg($i + 1);
1055 if ($this->data->hasField($field))
1064 if ($this->data->hasField($field))
1073 function range($field, $min, $max, $message =
null)
1075 if ($this->data->hasField($field))
1090 function match($field, $title, $match, $matchTitle, $caseSensitive =
true)
1092 $this->validator->add(
new MatchValidator($field, $title, $match, $matchTitle, $caseSensitive));
1112 $numArgs = func_num_args();
1116 foreach($this->data->getFields() as $field => $type)
1118 if ($this->data->hasField($field))
1126 for($i = 0; $i < $numArgs; ++$i)
1128 if ($this->data->hasField($field))
1146 function button($text, $url, $confirm =
null, $isScript =
false, $cssClass =
null)
1148 $this->buttons[] = array(
'text' => $text,
'url' => $url,
'confirm' => $confirm,
'isScript' => $isScript,
'cssClass' => $cssClass);
1159 if ($failure ==
null)
1161 $failure =
"function() {document.id('{$form->id}_error').set('text','Failed to communicate with server'); }";
1164 $this->ajaxSubmitHandler = $success;
1165 $this->ajaxFailureHandler = $failure;
1178 $this->allowPartialSave =
true;
1179 $this->partialSaveContainerID = $containerID;
1180 $this->partialSaveLabel = $label;
1189 return (isset($_POST[
"#FORMID#"]) && $_POST[
"#FORMID#"] == $this->
id);
1200 if ($this->onDrawStart)
1202 if (call_user_func_array($this->onDrawStart, array($this)) ===
false)
1208 $obj =& $this->data;
1209 $pk = $obj->getPrimaryKey();
1214 $submitLabel = ($obj->get($pk) !=
"" && $obj->get($pk) != 0) ?
"Update ".$obj->prettifyClassName() :
"Add ".$obj->prettifyClassName();
1221 if (!$this->subordinate)
1223 echo
"<form id='{$this->id}' method='{$this->method}' action='{$this->action}' enctype='multipart/form-data'";
1224 if (!$this->ajaxSubmitHandler)
1226 echo
" onsubmit='return onSubmit{$this->id}(this);'";
1230 echo
" data-mode='ajax'";
1237 echo
"<input type='hidden' name='#FORMID#' value='{$this->id}'/>";
1240 $csrfToken = $_SESSION[
"csrfToken"];
1244 $_SESSION[
"csrfToken"] = $csrfToken;
1247 echo
"<input type='hidden' name='#SESSIONTIE#' value='{$csrfToken}'/>\n";
1249 foreach(array_keys($this->hidden) as
$hidden)
1251 echo
"<input id='{$this->id}_{$hidden}' type='hidden' name='$hidden' value='".htmlSafe($obj->get(
$hidden)).
"'/>\n";
1254 if ($this->layout->externalErrorBox) $this->layout->errorBox();
1256 $this->layout->startUngrouped();
1258 if ($this->buttons_at_top && !$this->subordinate && !$this->hideButtons)
1260 $this->layout->startButtonLine();
1266 $this->layout->endButtonLine();
1269 if (!$this->layout->externalErrorBox) $this->layout->errorBox();
1271 if ($this->markRequiredFields && $this->validator->hasRequiredFields())
1273 $this->layout->requiredFields($this->requiredFieldsText);
1278 if (!$this->subordinate && !$this->hideButtons)
1280 $this->layout->startButtonLine();
1285 $this->layout->endButtonLine();
1288 $this->layout->endUngrouped();
1290 $this->layout->finalizeLayout();
1292 if (!$this->subordinate)
1297 if ($this->onDrawComplete)
1299 call_user_func_array($this->onDrawComplete, array($this));
1308 $submitID = $this->
id.
"_submitButton";
1310 if ($this->useLinkSubmit)
1312 echo
"<a id='{$submitID}' class='{$this->buttonCSS}' name='Submit' onclick='if (onSubmit{$this->id}(document.forms.{$this->id})) return document.forms.{$this->id}.submit(); else return false;'>$submitLabel</a>";
1316 echo
"<input type='submit' id='{$submitID}' class='{$this->buttonCSS}' name='Submit' value='$submitLabel' ondblclick='return false;'/>";
1319 if ($obj->get($pk) && $this->allowDelete)
1324 echo
" <input type='submit' class='{$this->buttonCSS}' name='{$this->id}_delete'
1325 value='$deleteLabel'
1326 onclick='return confirm(\"{$deleteMessage}\");'/>";
1332 $this->renderedFields = array();
1334 if($this->additional_at_top)
1337 if(!$this->additional_at_top)
1348 $filter = $obj->getFilter();
1350 if (count($this->groups) > 0)
1352 $this->layout->endUngrouped();
1354 foreach($this->groups as $legend =>
$fields)
1356 $collapsible = array_key_exists($legend, $this->collapsibleGroups);
1359 $cf = $this->collapsibleGroups[$legend];
1361 $collapsed = is_bool($cf) ? $cf : !$this->data->get($cf);
1364 $this->layout->startGroup($legend, $collapsible, $collapsed);
1368 if ($field != $pk && !array_key_exists($field, $this->hidden) && !array_key_exists($field, $this->renderedFields) &&
1369 ($this->
hasAdditional($field) || !($filter && $filter->isExcluded($field))))
1375 $this->layout->endGroup();
1378 $this->layout->startUngrouped();
1382 foreach($this->fieldOrder as $field)
1384 if ($field != $pk && !array_key_exists($field, $this->hidden) && !array_key_exists($field, $this->renderedFields) &&
1385 ($this->
hasAdditional($field) || !($filter && $filter->isExcluded($field))))
1391 foreach(array_keys($this->fields) as $field)
1393 if ($field != $pk && !array_key_exists($field, $this->hidden) && !array_key_exists($field, $this->renderedFields) &&
1394 ($this->
hasAdditional($field) || !($filter && $filter->isExcluded($field))))
1407 if ($this->
readonly[$field] || $this->readOnlyForm)
1409 if (!$this->hideEmptyFields || !$this->data->hasField($field) || $this->data->get($field))
1411 $renderer->renderReadOnly($field);
1418 $renderer->renderScript($field);
1420 $renderer->renderField($field);
1423 $this->renderedFields[$field] =
true;
1433 foreach($this->additional as $r)
1435 $renderer = $r[
'renderer'];
1437 $field = $r[
'field'];
1439 $field = $renderer->field;
1441 if(array_key_exists($field, $this->renderedFields))
1444 if ($this->
readonly[$field] || $this->readOnlyForm)
1446 $renderer->renderReadOnly($field);
1450 $renderer->renderField($field);
1461 foreach($this->buttons as $button)
1463 $url = ($button[
'isScript']) ? $button[
'url'] :
"go('{$button['url']}');";
1465 if ($button[
'confirm'])
1467 $link =
"if (confirm('".jsSafe($button[
'confirm']).
"')) $url; return false;";
1471 $link =
"$url; return false;";
1475 if ($button[
'cssClass'])
1477 $css .=
" ".$button[
'cssClass'];
1480 echo
" <input type='button' class='{$css}' onclick=\"$link\" value=\"{$button['text']}\"/>";
1491 $this->readOnlyForm =
true;
1493 $this->layout->startUngrouped();
1495 $obj =& $this->data;
1499 $this->layout->endUngrouped();
1501 $this->layout->finalizeLayout();
1514 if (array_key_exists($field, $this->overrides))
1516 $l = $this->overrides[$field][
'label'];
1517 if ($l !=
"")
return $l;
1520 $field = preg_replace(
"/([a-z])([A-Z0-9])/",
"$1 $2", $field);
1521 $field = str_replace(
"_",
" ", $field);
1523 switch($this->capitalizationMode)
1527 $field = strtolower($field);
1532 $field = strtoupper($field);
1537 $field = ucfirst($field);
1543 $field = ucwords($field);
1558 $this->data->load($id);
1572 $obj =& $this->data;
1575 $filter = $obj->getFilter();
1576 $pk = $obj->getPrimaryKey();
1578 if (isset($_POST[
"#FORMID#"]))
1580 trace(
"FORM ID: {$this->id} - {$_POST["#FORMID#
"]}", 3);
1581 $this->
id = $_POST[
"#FORMID#"];
1584 if (!isset($_POST[
"#SESSIONTIE#"]) || $_POST[
"#SESSIONTIE#"] != $_SESSION[
"csrfToken"])
1586 trace(
"Invalid Session Token - {$_POST["#SESSIONTIE#
"]} vs {$_SESSION["csrfToken
"]}", 3);
1587 throw new FakoliException(
"Invalid Session Token");
1590 if ($_POST[
"{$this->id}_delete"])
1592 if(!$this->onDelete)
1598 call_user_func_array($this->onDelete, array($obj));
1600 $this->deleted =
true;
1604 $this->msg = $this->validator->validate();
1611 if (!$this->data->getPrimaryKeyValue() & $this->onInsertPreProcess)
1613 if (call_user_func_array($this->onInsertPreProcess, array($this)) ===
false)
1621 if ($this->onSavePreProcess)
1623 if (call_user_func_array($this->onSavePreProcess, array($this)) ===
false)
1631 if ($this->customSaveHandler)
1633 if (!call_user_func_array($this->customSaveHandler, array($this)))
1647 if ($this->onSaveComplete)
1649 call_user_func_array($this->onSaveComplete, array($this));
1658 if ($this->allowPartialSave)
1660 $headers = getallheaders();
1661 trace(print_r($headers,
true), 3);
1663 if ($headers[
"X-Partial-Save"])
1665 $result = array(
"status" => $success ?
'success' :
'error',
1666 "primary_key" => $this->data->getPrimaryKey(),
1667 "primary_key_value" => $this->data->get($this->data->getPrimaryKey()));
1671 $result[
"error"] = $this->msg;
1683 foreach (array_keys($this->fields) as $field)
1685 if ($field != $pk && !array_key_exists($field, $this->hidden) &&
1686 !($filter && $filter->isExcluded($field)))
1691 $renderer->preProcess($field);
1696 foreach($this->additional as $r)
1698 $renderer = $r[
'renderer'];
1699 $field = $r[
'field'];
1701 $renderer->preProcess($field);
1707 foreach (array_keys($this->fields) as $field)
1709 if ($field != $pk && !array_key_exists($field, $this->hidden) &&
1710 !($filter && $filter->isExcluded($field)))
1715 $renderer->postProcess($field);
1720 foreach($this->additional as $r)
1722 $renderer = $r[
'renderer'];
1723 $field = $r[
'field'];
1725 $renderer->postProcess($field);
1729 function remove(&$obj)
1749 $this->msg = $this->validator->validate();
1750 return ($this->msg ==
"");
1767 if (substr(phpVersion(), 0, 2) ==
"4.")
1769 foreach(array_keys($this->renderers) as $renderer)
1771 $this->renderers[$renderer]->parent =& $this;
1773 foreach(array_keys($this->overrides) as $override)
1775 $this->overrides[$override][
"renderer"]->parent =& $this;
Field renderer for boolean data fields.
CheckListFieldRenderer: Renders the specified list of options as a list of checkboxes from which mult...
Field renderer for boolean data fields.
Use Currency3 if you want to have 3 decimal places displayed.
Field renderer for currency data fields.
Field renderer for date data fields.
Field renderer for date of birth data fields.
Field renderer for date data fields.
Field renderer for HTML data fields.
Tests whether two fields contain the same value.
Field renderer for numeric data fields.
Field renderer for password data fields.
Field renderer for zipcode data fields.
Field renderer for numeric rating data fields.
Tests wheteher a field's value matches the supplied regular expression.
Provides a very simple digital signature implementation.
Field renderer for string data fields.
Field renderer for text data fields.
Field renderer for time data fields.
Field renderer for boolean data fields.
The ValidationEngine takes an array of validator objects in its constructor, and is then able to gene...
Field renderer for zipcode data fields.
codify($name)
Takes a text string and converts it into a code-compliant format, suitable for use as a variable name...
plainGUID()
Generates a version 4 GUID with no punctutation.
trace($msg, $lvl=3, $callStack=null)
Send output to the trace log.
jsSafe($str, $escapeEntities=false)
Utility function to escape a string correctly for use in a Javascript client-side call.
prettify($name)
Takes a variable or field name and converts it into a human-readable version (assuming that the origi...
ajaxReturn($msg=null)
Returns a string output and exits the script cleanly.