ExtensibleSet: completely refactored based on ipl

This commit is contained in:
Thomas Gelf 2017-06-18 14:08:37 +02:00
parent 6e57baf273
commit 04e22f5a23
4 changed files with 499 additions and 268 deletions

View File

@ -1,266 +0,0 @@
<?php
use Icinga\Module\Director\IcingaConfig\ExtensibleSet;
/**
* View helper for extensible sets
*
* Avoid complaints about class names:
* @codingStandardsIgnoreStart
*/
class Zend_View_Helper_FormExtensibleSet extends Zend_View_Helper_FormElement
{
private $currentId;
/**
* 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 function formExtensibleSet($name, $value = null, $attribs = null)
{
$info = $this->_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 = '<li><span class="inline-buttons">';
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)
. '</span>'
. '<input type="text"'
. ' name="' . $name . '[]"'
. ' id="' . $id . $suff . '"'
. ' value="' . $v->escape($val) . '"'
. $disabled
. $this->_htmlAttribs($attribs)
. ' />'
. '</li>';
$elements[] = $htm;
$cnt++;
}
}
$suff = $this->suffix($cnt);
if ($multiOptions) {
if (count($multiOptions) > 1 || strlen(key($multiOptions))) {
$htm = '<li>'
. '<span class="inline-buttons">'
. $this->renderAddButton($name, $disabled)
. '</span>'
. $v->formSelect(
$name . '[]',
null,
array(
'class' => 'autosubmit' . ($cnt === 0 ? '' : ' extend-set'),
'multiple' => false,
'id' => $id . $suff
),
$multiOptions
);
$elements[] = $htm;
}
} else {
$elements[] = '<li><input type="text"'
. ' name="' . $name . '[]"'
. ($cnt === 0 ? '' : ' class="extend-set"')
. ' id="' . $id . $suff . '"'
. ' placeholder="' . $v->translate('Add a new one...') . '"'
. $disabled
. $this->_htmlAttribs($attribs)
. $this->getClosingBracket()
. '<span class="inline-buttons">'
. $this->renderAddButton($name, $disabled)
. '</span>'
. '</li>';
}
if (array_key_exists('description', $attribs)) {
$description = '<p class="description">' . $v->escape($attribs['description']) . '</p>';
} else {
$description = '';
}
return '<ul class="extensible-set'
. ($sorted ? ' sortable' : '')
. '">' . "\n "
. implode("\n ", $elements)
. $description
. "</ul>\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 '<input type="text"'
. ' name="' . $name . '[]"'
. ' id="' . $id . $suff . '"'
. ' value="' . $v->escape($val) . '"'
. $disabled
. $this->_htmlAttribs($attribs)
. ' />';
}
private function renderAddButton($name, $disabled)
{
$v = $this->view;
return '<input type="submit" class="related-action action-add"'
. ' name="' . $name . '___ADD"'
. ' value="&#xe805;"'
. ' title="' . $v->translate('Add a new entry') . '"'
. $disabled
. ' />';
}
private function renderDeleteButton($name, $cnt, $disabled)
{
$v = $this->view;
return '<input type="submit" class="related-action action-remove"'
. ' name="' . $name . '_' . $cnt . '__REMOVE' . '"'
. ' value="&#xe804;"'
. ' title="' . $v->translate('Remove this entry') . '"'
. $disabled
. ' />';
}
private function renderUpButton($name, $cnt, $disabled)
{
$v = $this->view;
return '<input type="submit" class="related-action action-move-up"'
. ' name="' . $name . '_' . $cnt . '__MOVE_UP"'
. ' value="&#xe825;"'
. ' title="' . $v->translate('Move up') . '"'
. $disabled
. ' />';
}
private function renderDownButton($name, $cnt, $disabled)
{
$v = $this->view;
return '<input type="submit" class="related-action action-move-down"'
. ' name="' . $name . '_' . $cnt . '__MOVE_DOWN"'
. ' value="&#xe828;"'
. ' title="' . $v->translate('Move down') . '"'
. $disabled
. ' />';
}
}

View File

@ -0,0 +1,23 @@
<?php
use Icinga\Module\Director\Web\Form\IplElement\ExtensibleSetElement;
/**
* View helper for extensible sets
*
* @codingStandardsIgnoreStart
*/
class Zend_View_Helper_FormIplExtensibleSet extends Zend_View_Helper_FormElement
{
private $currentId;
/**
* @codingStandardsIgnoreEnd
* @return string The element HTML.
*/
public function formIplExtensibleSet($name, $value = null, $attribs = null)
{
return ExtensibleSetElement::fromZfDingens($name, $value, $attribs);
}
}

View File

@ -13,7 +13,7 @@ class ExtensibleSet extends FormElement
* Default form view helper to use for rendering
* @var string
*/
public $helper = 'formExtensibleSet';
public $helper = 'formIplExtensibleSet';
// private $multiOptions;
@ -63,7 +63,7 @@ class ExtensibleSet extends FormElement
$value = null;
}
return parent::_filterValue($value, $key);
parent::_filterValue($value, $key);
}
public function isValid($value, $context = null)

View File

@ -0,0 +1,474 @@
<?php
namespace Icinga\Module\Director\Web\Form\IplElement;
use Icinga\Exception\ProgrammingError;
use Icinga\Module\Director\IcingaConfig\ExtensibleSet as Set;
use Icinga\Module\Director\Web\Form\IconHelper;
use ipl\Html\BaseElement;
use ipl\Html\Html;
use ipl\Translation\TranslationHelper;
class ExtensibleSetElement extends BaseElement
{
use TranslationHelper;
protected $tag = 'ul';
/** @var Set */
protected $set;
private $id;
private $name;
private $value;
private $description;
private $multiOptions;
private $validOptions;
private $chosenOptionCount = 0;
private $suggestionContext;
private $sorted = false;
private $disabled = false;
private $remainingAttribs;
protected $defaultAttributes = [
'class' => '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;
}
}