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; ?> &mdash;</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(