diff --git a/application/views/helpers/FormExtensibleSet.php b/application/views/helpers/FormExtensibleSet.php
new file mode 100644
index 00000000..befc9d46
--- /dev/null
+++ b/application/views/helpers/FormExtensibleSet.php
@@ -0,0 +1,212 @@
+_getInfo($name, $value, $attribs);
+ extract($info); // name, value, attribs, options, listsep, disable
+
+ if (array_key_exists('multiOptions', $attribs)) {
+ $multiOptions = $attribs['multiOptions'];
+ } else {
+ $multiOptions = null;
+ }
+
+ if (array_key_exists('sorted', $attribs)) {
+ $sorted = (bool) $attribs['sorted'];
+ } else {
+ $sorted = false;
+ }
+
+ $disableStr = ' disabled="disabled"';
+
+ // build the element
+ $disabled = '';
+ if ($disable) {
+ // disabled
+ $disabled = $disableStr;
+ }
+
+ $elements = array();
+ $v = $this->view;
+ $values = array('group a', 'group b');
+ $name = $v->escape($name);
+ $id = $v->escape($id);
+
+ $cnt = 0;
+ $total = 0;
+ if (is_array($value)) {
+ $total = count($value);
+
+ foreach ($value as $val) {
+ if (! strlen($val)) {
+ continue;
+ }
+
+ if ($multiOptions !== null) {
+ if (array_key_exists($val, $multiOptions)) {
+ unset($multiOptions[$val]);
+ } else {
+ continue; // Value no longer valid
+ }
+ }
+
+ $suff = '_' . $cnt;
+ $htm = '
';
+
+ if ($sorted) {
+ $htm .= $this->renderDownButton($name, $cnt, ($cnt === $total - 1 ? $disableStr : $disabled))
+ . $this->renderUpButton($name, $cnt, ($cnt === 0 ? $disableStr : $disabled));
+ }
+ $htm .= $this->renderDeleteButton($name, $cnt, $disabled)
+ . ''
+ . '_htmlAttribs($attribs)
+ . ' />'
+ . '';
+
+ $elements[] = $htm;
+ $cnt++;
+ }
+ }
+
+ $suff = '_' . $cnt;
+ if ($multiOptions) {
+ if (count($multiOptions) > 1 || strlen(key($multiOptions))) {
+ $htm = ''
+ . ''
+ . $this->renderAddButton($name, $disabled)
+ . ''
+ . $v->formSelect(
+ $name . '[]',
+ null,
+ array(
+ 'class' => 'autosubmit' . ($cnt === 0 ? '' : ' extend-set'),
+ 'multiple' => false
+ ),
+ $multiOptions
+ );
+
+ $elements[] = $htm;
+ }
+ } else {
+
+ $elements[] = '_htmlAttribs($attribs)
+ . $this->getClosingBracket()
+ . $this->renderAddButton($name, $disabled)
+ . '';
+ }
+
+ return '' . "\n "
+ . implode("\n ", $elements)
+ . "
\n";
+ }
+
+ public function moveUp($key)
+ {
+ var_dump("$key up");
+ }
+
+ public function moveDown($key)
+ {
+ var_dump("$key down");
+ }
+
+ public function removeKey($key)
+ {
+ var_dump("$key remove");
+ }
+
+ private function renderText($name, $id, $suff, $attribs, $disabled)
+ {
+ $v = $this->view;
+
+ return '_htmlAttribs($attribs)
+ . ' />';
+ }
+
+ private function renderAddButton($name, $disabled)
+ {
+ $v = $this->view;
+
+ return '';
+ }
+
+ private function renderDeleteButton($name, $cnt, $disabled)
+ {
+ $v = $this->view;
+
+ return '';
+ }
+
+ private function renderUpButton($name, $cnt, $disabled)
+ {
+ $v = $this->view;
+
+ return '';
+ }
+
+ private function renderDownButton($name, $cnt, $disabled)
+ {
+ $v = $this->view;
+
+ return '';
+ }
+}
diff --git a/library/Director/Web/Form/Element/ExtensibleSet.php b/library/Director/Web/Form/Element/ExtensibleSet.php
new file mode 100644
index 00000000..3b4ec311
--- /dev/null
+++ b/library/Director/Web/Form/Element/ExtensibleSet.php
@@ -0,0 +1,34 @@
+setValue($value);
+ if ($this->isRequired() && empty($value)) {
+ // TODO: translate
+ $this->addError('You are required to choose at least one element');
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/library/Director/Web/Form/Element/FormElement.php b/library/Director/Web/Form/Element/FormElement.php
new file mode 100644
index 00000000..c3278596
--- /dev/null
+++ b/library/Director/Web/Form/Element/FormElement.php
@@ -0,0 +1,9 @@
+