From 04e22f5a23faa2f533d5b369297a660150d09b2e Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Sun, 18 Jun 2017 14:08:37 +0200 Subject: [PATCH] ExtensibleSet: completely refactored based on ipl --- .../views/helpers/FormExtensibleSet.php | 266 ---------- .../views/helpers/FormIplExtensibleSet.php | 23 + .../Web/Form/Element/ExtensibleSet.php | 4 +- .../Form/IplElement/ExtensibleSetElement.php | 474 ++++++++++++++++++ 4 files changed, 499 insertions(+), 268 deletions(-) delete mode 100644 application/views/helpers/FormExtensibleSet.php create mode 100644 application/views/helpers/FormIplExtensibleSet.php create mode 100644 library/Director/Web/Form/IplElement/ExtensibleSetElement.php diff --git a/application/views/helpers/FormExtensibleSet.php b/application/views/helpers/FormExtensibleSet.php deleted file mode 100644 index 76304128..00000000 --- a/application/views/helpers/FormExtensibleSet.php +++ /dev/null @@ -1,266 +0,0 @@ -_getInfo($name, $value, $attribs); - extract($info); // name, value, attribs, options, listsep, disable - if (array_key_exists('multiOptions', $attribs)) { - $multiOptions = $attribs['multiOptions']; - unset($attribs['multiOptions']); - $validOptions = $this->flattenOptions($multiOptions); - } else { - $multiOptions = null; - } - - if (array_key_exists('sorted', $attribs)) { - $sorted = (bool) $attribs['sorted']; - unset($attribs['sorted']); - } else { - $sorted = false; - } - - $disableStr = ' disabled="disabled"'; - - // build the element - $disabled = ''; - if ($disable) { - // disabled - $disabled = $disableStr; - } - - $elements = array(); - $v = $this->view; - $name = $v->escape($name); - $id = $v->escape($id); - - if ($value instanceof ExtensibleSet) { - $value = $value->toPlainObject(); - } - - if (is_array($value)) { - $value = array_filter($value, 'strlen'); - } - - - $cnt = 0; - $total = 0; - if (is_array($value)) { - $total = count($value); - - foreach ($value as $val) { - if ($multiOptions !== null) { - if (in_array($val, $validOptions)) { - $multiOptions = $this->removeOption($multiOptions, $val); - } else { - continue; // Value no longer valid - } - } - - $suff = $this->suffix($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 = $this->suffix($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, - 'id' => $id . $suff - ), - $multiOptions - ); - - $elements[] = $htm; - } - } else { - $elements[] = '
  • _htmlAttribs($attribs) - . $this->getClosingBracket() - . '' - . $this->renderAddButton($name, $disabled) - . '' - . '
  • '; - } - - if (array_key_exists('description', $attribs)) { - $description = '

    ' . $v->escape($attribs['description']) . '

    '; - } else { - $description = ''; - } - - return '\n"; - } - - private function flattenOptions($options) - { - $flat = array(); - - foreach ($options as $key => $option) { - if (is_array($option)) { - foreach ($option as $k => $o) { - $flat[] = $k; - } - } else { - $flat[] = $key; - } - } - - return $flat; - } - - private function removeOption($options, $option) - { - $unset = array(); - foreach ($options as $key => & $value) { - if (is_array($value)) { - $value = $this->removeOption($value, $option); - if (empty($value)) { - $unset[] = $key; - } - } elseif ($key === $option) { - $unset[] = $key; - } - } - - foreach ($unset as $key) { - unset($options[$key]); - } - - return $options; - } - - private function suffix($cnt) - { - if ($cnt === 0) { - return ''; - } else { - return '_' . $cnt; - } - } - - 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/application/views/helpers/FormIplExtensibleSet.php b/application/views/helpers/FormIplExtensibleSet.php new file mode 100644 index 00000000..c7820163 --- /dev/null +++ b/application/views/helpers/FormIplExtensibleSet.php @@ -0,0 +1,23 @@ + 'extensible-set' + ]; + + protected function __construct($name) + { + $this->name = $this->id = $name; + } + + private function setMultiOptions($options) + { + $this->multiOptions = $options; + $this->validOptions = $this->flattenOptions($options); + } + + protected function isValidOption($option) + { + if ($this->validOptions === null) { + if ($this->suggestionContext === null) { + return true; + } else { + // TODO: ask suggestionContext, if any + return true; + } + } else { + return in_array($option, $this->validOptions); + } + } + + private function disable($disable = true) + { + $this->disabled = (bool) $disable; + } + + private function isDisabled() + { + return $this->disabled; + } + + private function isSorted() + { + return $this->sorted; + } + + public function setValue($value) + { + if ($value instanceof Set) { + $value = $value->toPlainObject(); + } + + if (is_array($value)) { + $value = array_filter($value, 'strlen'); + } + + if (null !== $value && ! is_array($value)) { + throw new ProgrammingError( + 'Got unexpected value, no array: %s', + var_export($value, 1) + ); + } + + $this->value = $value; + return $this; + } + + protected function extractZfInfo(& $attribs = null) + { + if ($attribs === null) { + return; + } + + foreach (['id', 'name', 'descriptions'] as $key) { + if (array_key_exists($key, $attribs)) { + $this->$key = $attribs[$key]; + unset($attribs[$key]); + } + } + if (array_key_exists('disable', $attribs)) { + $this->disable($attribs['disable']); + unset($attribs['disable']); + } + + if (array_key_exists('value', $attribs)) { + $this->setValue($attribs['value']); + unset($attribs['value']); + } + + if (array_key_exists('multiOptions', $attribs)) { + $this->setMultiOptions($attribs['multiOptions']); + unset($attribs['multiOptions']); + } + + if (array_key_exists('sorted', $attribs)) { + $this->sorted = (bool) $attribs['sorted']; + unset($attribs['sorted']); + } + + if (array_key_exists('description', $attribs)) { + $this->description = $attribs['description']; + unset($attribs['description']); + } + + if (array_key_exists('suggest', $attribs)) { + $this->suggestionContext = $attribs['suggest']; + unset($attribs['suggest']); + } + + if (! empty($attribs)) { + $this->remainingAttribs = $attribs; + } + } + + /** + * Generates an 'extensible set' element. + * + * @codingStandardsIgnoreEnd + * + * @param string|array $name If a string, the element name. If an + * array, all other parameters are ignored, and the array elements + * are used in place of added parameters. + * + * @param mixed $value The element value. + * + * @param array $attribs Attributes for the element tag. + * + * @return string The element XHTML. + */ + public static function fromZfDingens($name, $value = null, $attribs = null) + { + $el = new static($name); + $el->extractZfInfo($attribs); + $el->setValue($value); + return $el->render(); + } + + protected function assemble() + { + $this->addChosenOptions(); + $this->addAddMore(); + + if ($this->isSorted()) { + $this->attributes()->add('class', 'sortable'); + } + if (null !== $this->description) { + $this->addDescription($this->description); + } + } + + private function eventuallyAddAutosuggestion(BaseElement $element) + { + if ($this->suggestionContext !== null) { + $attrs = $element->attributes(); + $attrs->add('class', 'director-suggest'); + $attrs->set([ + 'autocomplete' => 'off', + 'autocorrect' => 'off', + 'autocapitalize' => 'off', + 'spellcheck' => 'false', + 'data-suggestion-context' => $this->suggestionContext, + ]); + } + + return $element; + } + + private function hasAvailableMultiOptions() + { + return count($this->multiOptions) > 1 || strlen(key($this->multiOptions)); + } + + private function addAddMore() + { + $cnt = $this->chosenOptionCount; + + if ($this->multiOptions) { + if (! $this->hasAvailableMultiOptions()) { + return; + } + $field = Html::tag('select', ['class' => 'autosubmit']); + $field->add(Html::tag('option', [ + 'value' => '' + ], $this->translate('- add more -'))); + + foreach ($this->multiOptions as $key => $label) { + if ($key === null) { + $key = ''; + } + $option = Html::tag('option', ['value' => $key], $label); + $field->add($option); + } + } else { + $field = Html::tag('input', [ + 'type' => 'text', + 'placeholder' => $this->translate('Add a new one...'), + ]); + } + $field->addAttributes([ + 'id' => $this->id . $this->suffix($cnt), + 'name' => $this->name . '[]', + ]); + $this->eventuallyAddAutosuggestion( + $this->addRemainingAttributes( + $this->eventuallyDisable($field) + ) + ); + if ($cnt !== 0) { // TODO: was === 0?! + $field->attributes()->add('class', 'extend-set'); + } + + $this->add(Html::tag('li', null, [ + $this->createAddNewButton(), + $field + ])); + } + + private function createAddNewButton() + { + return $this->newInlineButtons( + $this->eventuallyDisable($this->renderAddButton()) + ); + } + + private function addChosenOptions() + { + if (null === $this->value) { + return; + } + $total = count($this->value); + + foreach ($this->value as $val) { + if ($this->multiOptions !== null) { + if ($this->isValidOption($val)) { + $this->multiOptions = $this->removeOption( + $this->multiOptions, + $val + ); + // TODO: + // $this->removeOption($val); + } + } + + $text = Html::tag('input', [ + 'type' => 'text', + 'name' => $this->name . '[]', + 'id' => $this->id . $this->suffix($this->chosenOptionCount), + 'value' => $val + ]); + + $this->addRemainingAttributes($this->eventuallyDisable($text)); + $this->add(Html::tag('li', null, [ + $this->getOptionButtons($this->chosenOptionCount, $total), + $text + ])); + $this->chosenOptionCount++; + } + } + + private function addRemainingAttributes(BaseElement $element) + { + if ($this->remainingAttribs !== null) { + $element->attributes()->add($this->remainingAttribs); + } + + return $element; + } + + private function eventuallyDisable(BaseElement $element) + { + if ($this->isDisabled()) { + $this->disableElement($element); + } + + return $element; + } + + private function disableElement(BaseElement $element) + { + $element->attributes()->set('disabled', 'disabled'); + return $element; + } + + private function disableIf(BaseElement $element, $condition) + { + if ($condition) { + $this->disableElement($element); + } + + return $element; + } + + private function getOptionButtons($cnt, $total) + { + if ($this->isDisabled()) { + return []; + } + $first = $cnt === 0; + $last = $cnt === $total - 1; + $name = $this->name; + $buttons = $this->newInlineButtons(); + if ($this->isSorted()) { + $buttons->add([ + $this->disableIf($this->renderDownButton($name, $cnt), $last), + $this->disableIf($this->renderUpButton($name, $cnt), $first) + ]); + } + + $buttons->add($this->renderDeleteButton($name, $cnt)); + + return $buttons; + } + + protected function newInlineButtons($content = null) + { + return Html::tag('span', ['class' => 'inline-buttons'], $content); + } + + protected function addDescription($description) + { + $this->add( + Html::tag('p', ['class' => 'description'], $description) + ); + } + + private function flattenOptions($options) + { + $flat = array(); + + foreach ($options as $key => $option) { + if (is_array($option)) { + foreach ($option as $k => $o) { + $flat[] = $k; + } + } else { + $flat[] = $key; + } + } + + return $flat; + } + + private function removeOption($options, $option) + { + $unset = array(); + foreach ($options as $key => & $value) { + if (is_array($value)) { + $value = $this->removeOption($value, $option); + if (empty($value)) { + $unset[] = $key; + } + } elseif ($key === $option) { + $unset[] = $key; + } + } + + foreach ($unset as $key) { + unset($options[$key]); + } + + return $options; + } + + private function suffix($cnt) + { + if ($cnt === 0) { + return ''; + } else { + return '_' . $cnt; + } + } + + private function renderAddButton() + { + return $this->createRelatedAction( + 'add', + $this->name, + $this->translate('Add a new entry'), + 'plus' + ); + } + + private function renderDeleteButton($name, $cnt) + { + return $this->createRelatedAction( + 'remove', + $name . '_' . $cnt, + $this->translate('Remove this entry'), + 'cancel' + ); + } + + private function renderUpButton($name, $cnt) + { + return $this->createRelatedAction( + 'move-up', + $name . '_' . $cnt, + $this->translate('Move up'), + 'up-big' + ); + } + + private function renderDownButton($name, $cnt) + { + return $this->createRelatedAction( + 'move-down', + $name . '_' . $cnt, + $this->translate('Move down'), + 'down-big' + ); + } + + protected function makeActionName($name, $action) + { + return $name . '__' . str_replace('-', '_', strtoupper($action)); + } + + protected function createRelatedAction( + $action, + $name, + $title, + $icon + ) { + $input = Html::tag('input', [ + 'type' => 'submit', + 'class' => ['related-action', 'action-' . $action], + 'name' => $this->makeActionName($name, $action), + 'value' => IconHelper::instance()->iconCharacter($icon), + 'title' => $title + ]); + + return $input; + } +}