diff --git a/include/ajax.search.php b/include/ajax.search.php index d52d7ca4784db657856df3e351f1fc1967679522..d228018e911f963d9ec8b201aca8d2921e201c70 100644 --- a/include/ajax.search.php +++ b/include/ajax.search.php @@ -74,7 +74,7 @@ class SearchAjaxAPI extends AjaxController { $field->form->getLocal('title'), $field->getLocal('label') )); $fields = SavedSearch::getSearchField($impl, $name); - $form = new Form($fields); + $form = new SimpleForm($fields); // Check the box to search the field by default if ($F = $form->getField("{$name}+search")) $F->value = true; diff --git a/include/ajax.tasks.php b/include/ajax.tasks.php index 31671f9e11043976f9a3113446b5efa65bbf5884..cd1f58e50f303fdc376abc6428e316c658f5391b 100644 --- a/include/ajax.tasks.php +++ b/include/ajax.tasks.php @@ -356,7 +356,7 @@ class TasksAjaxAPI extends AjaxController { Http::response(404, __('No such task')); $info=$errors=array(); - $task_note_form = new Form(array( + $note_form = new SimpleForm(array( 'attachments' => new FileUploadField(array('id'=>'attach', 'name'=>'attach:note', 'configuration' => array('extensions'=>''))) diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php index e81e257f4825ffbc76deec0f73bbf36efdf3f65c..06d0c461afb6fbb020ec949b0fb2841f13b26374 100644 --- a/include/class.dynamic_forms.php +++ b/include/class.dynamic_forms.php @@ -57,6 +57,10 @@ class DynamicForm extends VerySimpleModel { return $base; } + function getId() { + return $this->id; + } + /** * Fetch a list of field implementations for the fields defined in this * form. This method should *always* be preferred over diff --git a/include/class.filter_action.php b/include/class.filter_action.php index 7de00efe9e82d79e974d3bd2411c60161920baf6..fc3f356f7495342c633429e1bdb2cfa151bfe3b9 100644 --- a/include/class.filter_action.php +++ b/include/class.filter_action.php @@ -138,7 +138,7 @@ abstract class TriggerAction { foreach ($options as $f) { $f->set('id', $uid++); } - $this->_cform = new Form($options, $source); + $this->_cform = new SimpleForm($options, $source); if (!$source) { foreach ($this->_cform->getFields() as $name=>$f) { if ($config && isset($config[$name])) diff --git a/include/class.forms.php b/include/class.forms.php index 0d06b77c0c99870163cd5137a467d58e7bdf0184..252fe84cf1cd3d5693a99763461f854dd7efea0e 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -19,6 +19,9 @@ * data for a ticket */ class Form { + + static $id = 0; + var $fields = array(); var $title = ''; var $instructions = ''; @@ -39,15 +42,36 @@ class Form { $this->title = $options['title']; if (isset($options['instructions'])) $this->instructions = $options['instructions']; + if (isset($options['id'])) + $this->id = $options['id']; + // Use POST data if source was not specified $this->_source = ($source) ? $source : $_POST; } + + function getId() { + return static::$id; + } + function data($source) { foreach ($this->fields as $name=>$f) if (isset($source[$name])) $f->value = $source[$name]; } + function setFields($fields) { + + if (!is_array($fields)) + return; + + $this->fields = $fields; + foreach ($fields as $k=>$f) { + $f->setForm($this); + if (!$f->get('name') && $k) + $f->set('name', $k); + } + } + function getFields() { return $this->fields; } @@ -163,6 +187,42 @@ class Form { } } + function emitJavascript($options=array()) { + + // Check if we need to emit javascript + if (!($fid=$this->getId())) + return; + ?> + <script type="text/javascript"> + $(function() { + <?php + //XXX: We ONLY want to watch field on this form. We'll only + // watch form inputs if form_id is specified. Current FORM API + // doesn't generate the entire form (just fields) + if ($fid) { + ?> + $(document).off('change.<?php echo $fid; ?>'); + $(document).on('change.<?php echo $fid; ?>', + 'form#<?php echo $fid; ?> :input', + function() { + //Clear any current errors... + var errors = $('#field'+$(this).attr('id')+'_error'); + if (errors.length) + errors.slideUp('fast', function (){ + $(this).remove(); + }); + //TODO: Validation input inplace or via ajax call + // and set any new errors AND visibilty changes + } + ); + <?php + } + ?> + }); + </script> + <?php + } + static function emitMedia($url, $type) { if ($url[0] == '/') $url = ROOT_PATH . substr($url, 1); @@ -221,6 +281,28 @@ class Form { } } } + + /* + * Initialize a generic static form + */ + static function instantiate() { + $r = new ReflectionClass(get_called_class()); + return $r->newInstanceArgs(func_get_args()); + } +} + +/** + * SimpleForm + * Wrapper for inline/static forms. + * + */ +class SimpleForm extends Form { + + function __construct($fields=array(), $source=null, $options=array()) { + parent::__construct($source, $options); + $this->setFields($fields); + } + } require_once(INCLUDE_DIR . "class.json.php"); @@ -340,6 +422,10 @@ class FormField { $this->_clean = $this->_widget = null; } + function getValue() { + return $this->getWidget()->getValue(); + } + function errors() { return $this->_errors; } @@ -818,7 +904,7 @@ class FormField { $clazz = $type[1]; $T = new $clazz($this->ht); $config = $this->getConfiguration(); - $this->_cform = new Form($T->getConfigurationOptions(), $source); + $this->_cform = new SimpleForm($T->getConfigurationOptions(), $source); if (!$source) { foreach ($this->_cform->getFields() as $name=>$f) { if ($config && isset($config[$name])) @@ -1008,7 +1094,7 @@ class TextareaField extends FormField { } function searchable($value) { - $value = preg_replace(array('`<br(\s*)?/?>`i', '`</div>`i'), "\n", $value); + $value = preg_replace(array('`<br(\s*)?/?>`i', '`</div>`i'), "\n", $value); //<?php $value = Format::htmldecode(Format::striptags($value)); return Format::searchable($value); } @@ -2298,7 +2384,7 @@ class InlineFormField extends FormField { function getInlineForm($data=false) { $form = $this->get('form'); if (is_array($form)) { - $form = new Form($form, $data ?: $this->value ?: $this->getSource()); + $form = new SimpleForm($form, $data ?: $this->value ?: $this->getSource()); } return $form; } @@ -2582,7 +2668,7 @@ class ChoicesWidget extends Widget { echo $def_val; ?> —</option> <?php } - $this->emitChoices($choices); ?> + $this->emitChoices($choices, $values); ?> </select> <?php if ($config['multiselect']) { @@ -2597,10 +2683,10 @@ class ChoicesWidget extends Widget { } } - function emitChoices($choices) { + function emitChoices($choices, $values=array()) { reset($choices); if (is_array(current($choices)) || current($choices) instanceof Traversable) - return $this->emitComplexChoices($choices); + return $this->emitComplexChoices($choices, $values); foreach ($choices as $key => $name) { if (!$have_def && $key == $def_key) @@ -2612,7 +2698,7 @@ class ChoicesWidget extends Widget { } } - function emitComplexChoices($choices) { + function emitComplexChoices($choices, $values=array()) { foreach ($choices as $label => $group) { ?> <optgroup label="<?php echo $label; ?>"><?php foreach ($group as $key => $name) { @@ -2637,12 +2723,23 @@ class ChoicesWidget extends Widget { // Assume multiselect $values = array(); $choices = $this->field->getChoices(); - if (is_array($value)) { - foreach($value as $k => $v) { - if (isset($choices[$v])) - $values[$v] = $choices[$v]; - elseif (($i=$this->field->lookupChoice($v))) - $values += $i; + + if ($choices && is_array($value)) { + // Complex choices + if (is_array(current($choices)) + || current($choices) instanceof Traversable) { + foreach ($choices as $label => $group) { + foreach ($group as $k => $v) + if (in_array($k, $value)) + $values[$k] = $v; + } + } else { + foreach($value as $k => $v) { + if (isset($choices[$v])) + $values[$v] = $choices[$v]; + elseif (($i=$this->field->lookupChoice($v))) + $values += $i; + } } } diff --git a/include/class.plugin.php b/include/class.plugin.php index 4df5688435dc2eb1ae63c1c9cbdac1a1f3c8ffeb..c71d86524df95d44be3ebd3f598029bc17eb7074 100644 --- a/include/class.plugin.php +++ b/include/class.plugin.php @@ -32,7 +32,7 @@ class PluginConfig extends Config { */ function getForm() { if (!isset($this->form)) { - $this->form = new Form($this->getOptions()); + $this->form = new SimpleForm($this->getOptions()); if ($_SERVER['REQUEST_METHOD'] != 'POST') $this->form->data($this->getInfo()); } diff --git a/include/class.search.php b/include/class.search.php index 7225f652e7ab3397ca79fcd6c687013c0b313733..d41f0a59a3a6a871fef236d018cbdde2d3555ffb 100644 --- a/include/class.search.php +++ b/include/class.search.php @@ -630,7 +630,7 @@ class SavedSearch extends VerySimpleModel { $fields = array_merge($fields, self::getSearchField($field, $name)); } - $form = new Form($fields, $source); + $form = new SimpleForm($fields, $source); $form->addValidator(function($form) { $selected = 0; foreach ($form->getFields() as $F) { diff --git a/include/class.task.php b/include/class.task.php index 599be600983e6d6109d37b9bbf9724e88200c85e..f081eb0d13540fa9e847157568b096d0d08d8101 100644 --- a/include/class.task.php +++ b/include/class.task.php @@ -639,7 +639,7 @@ class TaskForm extends DynamicForm { static function getInternalForm($source=null) { if (!isset(static::$internalForm)) - static::$internalForm = new Form(self::getInternalFields(), $source); + static::$internalForm = new SimpleForm(self::getInternalFields(), $source); return static::$internalForm; } diff --git a/include/staff/templates/dynamic-form-simple.tmpl.php b/include/staff/templates/dynamic-form-simple.tmpl.php index cd53f0cd8950126c08f27e18f11f09eb7f92ce0e..8ec0cb2726a6c9b0b33c4d90c5a069938549e920 100644 --- a/include/staff/templates/dynamic-form-simple.tmpl.php +++ b/include/staff/templates/dynamic-form-simple.tmpl.php @@ -1,33 +1,43 @@ -<?php - echo $form->getMedia(); - foreach ($form->getFields() as $name=>$f) { ?> - <div class="flush-left custom-field" id="field<?php echo $f->getWidget()->id; - ?>" <?php if (!$f->isVisible()) echo 'style="display:none;"'; ?>> - <div class="field-label <?php if ($f->get('required')) echo 'required'; ?>"> - <label for="<?php echo $f->getWidget()->name; ?>"> - <?php if ($f->get('label')) { ?> - <?php echo Format::htmlchars($f->get('label')); ?>: - <?php } ?> - <?php if ($f->get('required')) { ?> - <span class="error">*</span> - <?php } ?> - </label> - <?php - if ($f->get('hint')) { ?> - <br/><em style="color:gray;display:inline-block"><?php - echo Format::viewableImages($f->get('hint')); ?></em> - <?php - } ?> - </div><div> - <?php - $f->render(); - ?> - </div> +<div class="form-simple"> + <?php + echo $form->getMedia(); + foreach ($form->getFields() as $name=>$f) { ?> + <div class="flush-left custom-field" id="field<?php echo $f->getWidget()->id; + ?>" <?php if (!$f->isVisible()) echo 'style="display:none;"'; ?>> + <div class="field-label <?php if ($f->get('required')) echo 'required'; ?>"> + <label for="<?php echo $f->getWidget()->name; ?>"> + <?php if ($f->get('label')) { ?> + <?php echo Format::htmlchars($f->get('label')); ?>: + <?php } ?> + <?php if ($f->get('required')) { ?> + <span class="error">*</span> + <?php } ?> + </label> + <?php + if ($f->get('hint')) { ?> + <em style="color:gray;display:block"><?php + echo Format::viewableImages($f->get('hint')); ?></em> + <?php + } ?> + </div><div> + <?php + $f->render($options); + ?> + </div> + <?php + if ($f->errors()) { ?> + <div id="field<?php echo $f->getWidget()->id; ?>_error"> <?php foreach ($f->errors() as $e) { ?> <div class="error"><?php echo $e; ?></div> - <?php } ?> + <?php + } ?> </div> <?php - } -?> + } ?> + </div> + <?php + } + $form->emitJavascript($options); + ?> +</div> diff --git a/scp/canned.php b/scp/canned.php index d35ec00c6b58aa207b6cb68d1f6240f64d79764a..34edc39a069b8a2524de622f555b52b28d127d15 100644 --- a/scp/canned.php +++ b/scp/canned.php @@ -30,7 +30,7 @@ $canned=null; if($_REQUEST['id'] && !($canned=Canned::lookup($_REQUEST['id']))) $errors['err']=sprintf(__('%s: Unknown or invalid ID.'), __('canned response')); -$canned_form = new Form(array( +$canned_form = new SimpleForm(array( 'attachments' => new FileUploadField(array('id'=>'attach', 'configuration'=>array('extensions'=>false, 'size'=>$cfg->getMaxFileSize()) diff --git a/scp/css/scp.css b/scp/css/scp.css index 279fb557b963772535531845d7744cf91e912bcb..0f7077ae1be1c33108f2ef77d6fd6f20283a7721 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -2177,6 +2177,11 @@ td.indented { #dynamic-actions > tr > td { padding: 5px; } + .no-margin { margin: 0 !important; } + +.form-simple select, .form-simple input, .form-simple textarea { + margin-left: 0; +} diff --git a/scp/faq.php b/scp/faq.php index 03c5f160118ba06081f1e53b33afc867a84d9b10..09b0d6bcb04a0b1cb0c9cfca40786b4c7b26625d 100644 --- a/scp/faq.php +++ b/scp/faq.php @@ -46,7 +46,7 @@ if ($langs = $cfg->getSecondaryLanguages()) { } } -$faq_form = new Form($form_fields, $_POST); +$faq_form = new SimpleForm($form_fields, $_POST); if ($_POST) { $errors=array(); diff --git a/scp/tasks.php b/scp/tasks.php index 21ec96fd162769e56f63216bdb26f74d397b2fc3..c80a28ec45a9b6421cc364d031f5db19fa0ae801 100644 --- a/scp/tasks.php +++ b/scp/tasks.php @@ -26,7 +26,7 @@ if ($_REQUEST['id']) { } // Configure form for file uploads -$note_form = new Form(array( +$note_form = new SimpleForm(array( 'attachments' => new FileUploadField(array('id'=>'attach', 'name'=>'attach:note', 'configuration' => array('extensions'=>''))) diff --git a/scp/tickets.php b/scp/tickets.php index 4d15d30dee7ce88608d9ab9991436433321fd126..c4a9591e25049e1a9c7292d089ce5a8c2a695ee4 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -40,12 +40,12 @@ if ($_REQUEST['uid']) $user = User::lookup($_REQUEST['uid']); // Configure form for file uploads -$response_form = new Form(array( +$response_form = new SimpleForm(array( 'attachments' => new FileUploadField(array('id'=>'attach', 'name'=>'attach:response', 'configuration' => array('extensions'=>''))) )); -$note_form = new Form(array( +$note_form = new SimpleForm(array( 'attachments' => new FileUploadField(array('id'=>'attach', 'name'=>'attach:note', 'configuration' => array('extensions'=>''))) diff --git a/setup/doc/forms.md b/setup/doc/forms.md index c3545096c5e6daaee3ff394eb4b490f9a5a84daa..f1520cd3712cb8093fbb83787027bc15db3b051c 100644 --- a/setup/doc/forms.md +++ b/setup/doc/forms.md @@ -16,7 +16,7 @@ constructor. The simplest way to create forms is to instanciate the Form instance directly: - $form = new Form(array( + $form = new SimpleForm(array( 'email' => new TextboxField(array('label'=>'Email Address')), ); @@ -34,7 +34,7 @@ the cleaned values from the form fields based on the data from the request. To create a class that defines the fields statically, one might write a trampoline constructor: - class UserForm extends Form { + class UserForm extends SimpleForm { function __construct() { $args = func_get_args(); $fields = array(