Commit 8c642640 authored by Carsten  Rose's avatar Carsten Rose
Browse files

#3063, Radios / Checkboxes als Buttons (Bootstrap)

Implemented for Checkbox.

Index.rst, AbstractBuildForm.php: split buildCheckbox() in constructCheckbox(Simple|Multi)Plain() and constructCheckbox(Simple|Multi)Button.

AbstractBuildForm.php, OnArray.php: new function removeEmptyElementsFromArray(). Replace old check of isset() (which seems never have been worked) against ==''.
parent fbbab765
...@@ -1056,6 +1056,34 @@ Checkboxes can be rendered in mode: ...@@ -1056,6 +1056,34 @@ Checkboxes can be rendered in mode:
* Value: '', 0, 1 - The check boxes will be aligned vertical. * Value: '', 0, 1 - The check boxes will be aligned vertical.
* Value: >1 - The check boxes will be aligned horizontal, with a linebreak every 'value' elements. * Value: >1 - The check boxes will be aligned horizontal, with a linebreak every 'value' elements.
* *FormElement.parameter*:
* *emptyHide*: Existence of this item hides an entry with an empty string. This is useful for e.g. Enums, which have an empty
entry, but the empty value should not be selectable.
* *emptyItemAtStart*: Existence of this item inserts an empty entry at the beginning of the selectlist.
* *emptyItemAtEnd*: Existence of this item inserts an empty entry at the end of the selectlist.
* *buttonClass*: Instead of the plain HTML checkbox fields, Bootstrap
`buttons <http://getbootstrap.com/javascript/#buttons-checkbox-radio>`_. are rendered as `checkbox` elements. Use
one of the following `classes <http://getbootstrap.com/css/#buttons-options>`_:
* `btn-default` (default, grey),
* `btn-primary` (blue),
* `btn-success` (green),
* `btn-info` (light blue),
* `btn-warning` (orange),
* `btn-danger` (red).
With a given *buttonClass*, all buttons (=radios) are rendered horizontal. A value in *FormElement.maxlength* has no effect.
* *No preselection*:
* If there is a default configured on a table column, such a value is selected by default. If the user should actively
choose an option, the 'preselection' can be omitted by specifying an explicit definition on the FormElement field `value`::
{{<columnname>:RZ}}
For existing records the shown value is as expected the value of the record. For new records, it's the value `0`,
which is typically not one of the ENUM / SET values and therefore nothing is selected.
Type: date Type: date
^^^^^^^^^^ ^^^^^^^^^^
...@@ -1168,10 +1196,10 @@ Type: radio ...@@ -1168,10 +1196,10 @@ Type: radio
* *FormElement.parameter*: * *FormElement.parameter*:
* *emptyHide*: Existence of this item hides an entry with an empty string. This is useful for e.g. Enums, which have an empty
entry, but the empty value should not be selectable.
* *emptyItemAtStart*: Existence of this item inserts an empty entry at the beginning of the selectlist. * *emptyItemAtStart*: Existence of this item inserts an empty entry at the beginning of the selectlist.
* *emptyItemAtEnd*: Existence of this item inserts an empty entry at the end of the selectlist. * *emptyItemAtEnd*: Existence of this item inserts an empty entry at the end of the selectlist.
* *emptyHide*: Existence of this item hides the empty entry. This is useful for e.g. Enums, which have an empty
entry but the empty value should not be an option to be selected.
* *buttonClass*: Instead of the plain radio fields, Bootstrap * *buttonClass*: Instead of the plain radio fields, Bootstrap
`buttons <http://getbootstrap.com/javascript/#buttons-checkbox-radio>`_. are rendered as `radio` elements. Use `buttons <http://getbootstrap.com/javascript/#buttons-checkbox-radio>`_. are rendered as `radio` elements. Use
one of the following `classes <http://getbootstrap.com/css/#buttons-options>`_: one of the following `classes <http://getbootstrap.com/css/#buttons-options>`_:
...@@ -1183,10 +1211,6 @@ Type: radio ...@@ -1183,10 +1211,6 @@ Type: radio
* `btn-danger` (red). * `btn-danger` (red).
With a given *buttonClass*, all buttons (=radios) are rendered horizontal. A value in *FormElement.maxlength* has no effect. With a given *buttonClass*, all buttons (=radios) are rendered horizontal. A value in *FormElement.maxlength* has no effect.
* *No preselection*: * *No preselection*:
* If there is a default configured on a table column, such a value is selected by default. If the user should actively * If there is a default configured on a table column, such a value is selected by default. If the user should actively
......
...@@ -1072,6 +1072,15 @@ abstract class AbstractBuildForm { ...@@ -1072,6 +1072,15 @@ abstract class AbstractBuildForm {
$itemKey = $itemValue; $itemKey = $itemValue;
} }
if (isset($formElement['emptyHide'])) {
// if (isset($itemValue['']))
// unset($itemValue['']);
// if (isset($itemKey['']))
// unset($itemKey['']);
$itemKey = OnArray::removeEmptyElementsFromArray($itemKey);
$itemValue = OnArray::removeEmptyElementsFromArray($itemValue);
}
if (isset($formElement['emptyItemAtStart'])) { if (isset($formElement['emptyItemAtStart'])) {
array_unshift($itemKey, ''); array_unshift($itemKey, '');
array_unshift($itemValue, ''); array_unshift($itemValue, '');
...@@ -1082,13 +1091,6 @@ abstract class AbstractBuildForm { ...@@ -1082,13 +1091,6 @@ abstract class AbstractBuildForm {
$itemKey[] = ''; $itemKey[] = '';
} }
if (isset($formElement['emptyHide'])) {
if (isset($itemValue['']))
unset($itemValue['']);
if (isset($itemKey['']))
unset($itemKey['']);
}
} }
/** /**
...@@ -1170,7 +1172,94 @@ abstract class AbstractBuildForm { ...@@ -1170,7 +1172,94 @@ abstract class AbstractBuildForm {
} }
/** /**
* Build a Checkbox based on two values. * Build a Checkbox based on two values. Either in HTML plain layout or with Bootstrap Button class.
*
* @param array $formElement
* @param $htmlFormElementId
* @param $value
* @param array $json
* @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE
* @return string
* @throws CodeException
* @throws \qfq\UserFormException
*/
public function buildCheckboxSingle(array $formElement, $htmlFormElementId, $attribute, $value, array &$json, $mode = FORM_LOAD) {
if (isset($formElement[FE_BUTTON_CLASS])) {
if ($formElement[FE_BUTTON_CLASS] == '') {
$formElement[FE_BUTTON_CLASS] = 'btn-default';
}
return $this->constructCheckboxSingleButton($formElement, $htmlFormElementId, $attribute, $value, $json);
} else {
return $this->constructCheckboxSinglePlain($formElement, $htmlFormElementId, $attribute, $value, $json);
}
}
/**
* Build a Checkbox based on two values with Bootstrap Button class.
*
* <div class="btn-group" data-toggle="buttons">
* <input type="hidden" name="$htmlFormElementId" value="$valueUnChecked">
* <label class="btn btn-primary active">
* <input type="checkbox" autocomplete="off" name="$htmlFormElementId" value="$valueChecked"checked> Checkbox 1 (pre-checked)
* </label>
* </div>
*
* @param array $formElement
* @param $htmlFormElementId
* @param $attribute
* @param $value
* @param array $json
* @return string
*/
public function constructCheckboxSingleButton(array $formElement, $htmlFormElementId, $attribute, $value, array &$json) {
$html = '';
$valueJson = false;
$attribute .= Support::doAttribute('name', $htmlFormElementId);
$attribute .= Support::doAttribute('value', $formElement['checked'], false);
$attribute .= Support::doAttribute('title', $formElement['tooltip']);
$attribute .= Support::doAttribute('data-load', ($formElement[FE_DYNAMIC_UPDATE] === 'yes') ? 'data-load' : '');
$attribute .= Support::doAttribute('autocomplete', 'off');
$classActive = '';
if ($formElement['checked'] === $value) {
$attribute .= Support::doAttribute('checked', 'checked');
$valueJson = true;
$classActive = ' active';
}
$attribute .= $this->getAttributeList($formElement, ['autofocus']);
$attribute .= $this->getAttributeList($formElement, [F_FE_DATA_PATTERN_ERROR, F_FE_DATA_REQUIRED_ERROR, F_FE_DATA_MATCH_ERROR, F_FE_DATA_ERROR]);
$html = $this->buildNativeHidden($htmlFormElementId, $formElement['unchecked']);
$htmlElement = '<input ' . $attribute . '>';
if (isset($formElement['label2'])) {
$htmlElement .= $formElement['label2'];
} else {
$htmlElement .= $formElement['checked'];
}
$html .= Support::wrapTag("<label class='btn " . $formElement[FE_BUTTON_CLASS] . "$classActive'>",
$htmlElement, true);
$html = Support::wrapTag('<div class="btn-group" data-toggle="buttons">', $html);
$json = $this->getJsonElementUpdate($htmlFormElementId, $valueJson, $formElement[FE_MODE]);
return $html;
}
/**
* Build a single HTML plain checkbox based on two values.
* Create a 'hidden' input field and a 'checkbox' input field - both with the same HTML 'name'.
* HTML does not submit an unchecked checkbox. Then only the 'hidden' input field is submitted.
*
* Format: <input type="hidden" name="$htmlFormElementId" value="$valueUnChecked">
* <input name="$htmlFormElementId" type="radio" [autofocus="autofocus"]
* [required="required"] [disabled="disabled"] value="<value>" [checked="checked"] >
* *
* @param array $formElement * @param array $formElement
* @param $htmlFormElementId * @param $htmlFormElementId
...@@ -1179,7 +1268,7 @@ abstract class AbstractBuildForm { ...@@ -1179,7 +1268,7 @@ abstract class AbstractBuildForm {
* @param array $json * @param array $json
* @return string * @return string
*/ */
public function buildCheckboxSingle(array $formElement, $htmlFormElementId, $attribute, $value, array &$json) { public function constructCheckboxSinglePlain(array $formElement, $htmlFormElementId, $attribute, $value, array &$json) {
$html = ''; $html = '';
$valueJson = false; $valueJson = false;
...@@ -1211,6 +1300,32 @@ abstract class AbstractBuildForm { ...@@ -1211,6 +1300,32 @@ abstract class AbstractBuildForm {
return $html; return $html;
} }
/**
* Build a Checkbox based on two values. Either in HTML plain layout or with Bootstrap Button class.
*
* @param array $formElement
* @param $htmlFormElementId
* @param $value
* @param array $json
* @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE
* @return string
* @throws CodeException
* @throws \qfq\UserFormException
*/
public function buildCheckboxMulti(array $formElement, $htmlFormElementId, $attributeBase, $value, array $itemKey, array $itemValue, array &$json) {
if (isset($formElement[FE_BUTTON_CLASS])) {
if ($formElement[FE_BUTTON_CLASS] == '') {
$formElement[FE_BUTTON_CLASS] = 'btn-default';
}
return $this->constructCheckboxMultiButton($formElement, $htmlFormElementId, $attributeBase, $value, $itemKey, $itemValue, $json);
} else {
return $this->constructCheckboxMultiPlain($formElement, $htmlFormElementId, $attributeBase, $value, $itemKey, $itemValue, $json);
}
}
/** /**
* Build as many Checkboxes as items. * Build as many Checkboxes as items.
* *
...@@ -1226,7 +1341,73 @@ abstract class AbstractBuildForm { ...@@ -1226,7 +1341,73 @@ abstract class AbstractBuildForm {
* @param array $json * @param array $json
* @return string * @return string
*/ */
public function buildCheckboxMulti(array $formElement, $htmlFormElementId, $attributeBase, $value, array $itemKey, array $itemValue, array &$json) { public function constructCheckboxMultiButton(array $formElement, $htmlFormElementId, $attributeBase, $value, array $itemKey, array $itemValue, array &$json) {
$json = array();
// Defines which of the checkboxes will be checked.
$values = explode(',', $value);
// $attributeBase .= Support::doAttribute('name', $htmlFormElementId);
$attributeBase .= Support::doAttribute('data-load', ($formElement[FE_DYNAMIC_UPDATE] === 'yes') ? 'data-load' : '');
$attributeBase .= $this->getAttributeList($formElement, [F_FE_DATA_PATTERN_ERROR, F_FE_DATA_REQUIRED_ERROR, F_FE_DATA_MATCH_ERROR, F_FE_DATA_ERROR]);
$html = $this->buildNativeHidden(HelperFormElement::prependFormElementIdCheckBoxMulti($htmlFormElementId, 'h'), '');
$attribute = $attributeBase;
if (isset($formElement['autofocus'])) {
$attribute .= Support::doAttribute('autofocus', $formElement['autofocus']);
}
for ($ii = 0, $jj = 1; $ii < count($itemKey); $ii++, $jj++) {
$jsonValue = false;
$classActive = '';
$htmlFormElementIdUniq = HelperFormElement::prependFormElementIdCheckBoxMulti($htmlFormElementId, $ii);
$attribute .= Support::doAttribute('name', $htmlFormElementIdUniq);
$attribute .= Support::doAttribute('value', $itemKey[$ii]);
// Check if the given key is found in field.
if (false !== array_search($itemKey[$ii], $values)) {
$attribute .= Support::doAttribute('checked', 'checked');
$jsonValue = true;
$classActive = ' active';
}
// '&nbsp;' - This is necessary to correctly align an empty input.
$value = ($itemValue[$ii] === '') ? '&nbsp;' : $itemValue[$ii];
$htmlElement = '<input ' . $attribute . '>' . $value;
$html .= Support::wrapTag("<label class='btn " . $formElement[FE_BUTTON_CLASS] . "$classActive'>",
$htmlElement, true);
$json[] = $this->getJsonElementUpdate($htmlFormElementIdUniq, $jsonValue, $formElement[FE_MODE]);
// Init for the next checkbox
$attribute = $attributeBase;
}
$html = Support::wrapTag('<div class="btn-group" data-toggle="buttons">', $html);
return $html;
}
/**
* Build as many Checkboxes as items.
*
* Layout: The Bootstrap Layout needs very special setup, the checkboxes are wrapped differently with <div class=checkbox>
* depending of if they aligned horizontal or vertical.
*
* @param array $formElement
* @param $htmlFormElementId
* @param $attributeBase
* @param $value
* @param array $itemKey
* @param array $itemValue
* @param array $json
* @return string
*/
public function constructCheckboxMultiPlain(array $formElement, $htmlFormElementId, $attributeBase, $value, array $itemKey, array $itemValue, array &$json) {
$json = array(); $json = array();
// Defines which of the checkboxes will be checked. // Defines which of the checkboxes will be checked.
...@@ -1292,9 +1473,6 @@ abstract class AbstractBuildForm { ...@@ -1292,9 +1473,6 @@ abstract class AbstractBuildForm {
} }
// if (isset($formElement[CHECKBOX_ORIENTATION]) && $formElement[CHECKBOX_ORIENTATION] !== 'vertical')
// $html = Support::wrapTag("<div class='checkbox'>", $html, true);
//
return $html; return $html;
} }
...@@ -1393,6 +1571,7 @@ abstract class AbstractBuildForm { ...@@ -1393,6 +1571,7 @@ abstract class AbstractBuildForm {
$attributeBase .= Support::doAttribute('name', $htmlFormElementId); $attributeBase .= Support::doAttribute('name', $htmlFormElementId);
$attributeBase .= Support::doAttribute('type', $formElement[FE_TYPE]); $attributeBase .= Support::doAttribute('type', $formElement[FE_TYPE]);
$attributeBase .= Support::doAttribute('data-load', ($formElement[FE_DYNAMIC_UPDATE] === 'yes') ? 'data-load' : ''); $attributeBase .= Support::doAttribute('data-load', ($formElement[FE_DYNAMIC_UPDATE] === 'yes') ? 'data-load' : '');
$attributeBase .= Support::doAttribute('autocomplete', 'off');
$attribute = $attributeBase; $attribute = $attributeBase;
if (isset($formElement['autofocus'])) { if (isset($formElement['autofocus'])) {
......
...@@ -138,4 +138,33 @@ class OnArray { ...@@ -138,4 +138,33 @@ class OnArray {
return $arr; return $arr;
} }
/**
* Iterates over $arr and removes all empty (='') elements.
*
* @param array $arr
* @return array
*/
public static function removeEmptyElementsFromArray(array $arr) {
$new = array();
// for($ii=0; $ii<count($arr); $ii++) {
// if($arr[$ii]!='') {
// $new[]=$arr[$ii];
// }
// }
$ii = 0;
foreach ($arr as $key => $value) {
if ($value != '') {
if (is_int($key)) {
$key = $ii;
$ii++;
}
$new[$key] = $value;
}
}
return $new;
}
} }
\ No newline at end of file
...@@ -43,6 +43,50 @@ class OnArrayTest extends \PHPUnit_Framework_TestCase { ...@@ -43,6 +43,50 @@ class OnArrayTest extends \PHPUnit_Framework_TestCase {
$raw = ['hello', '"next"', '"without trailing', 'without leading"', ' with whitespace ', '" with tick and whitespace "', '']; $raw = ['hello', '"next"', '"without trailing', 'without leading"', ' with whitespace ', '" with tick and whitespace "', ''];
$expected = ['hello', 'next', 'without trailing', 'without leading', ' with whitespace ', ' with tick and whitespace ', '']; $expected = ['hello', 'next', 'without trailing', 'without leading', ' with whitespace ', ' with tick and whitespace ', ''];
$this->assertEquals(OnArray::trimArray($raw, '"'), $expected); $this->assertEquals($expected, OnArray::trimArray($raw, '"'));
}
public function testRemoveEmptyElementsFromArray() {
$this->assertEquals(array(), OnArray::removeEmptyElementsFromArray(array()));
$expected = array('Hello world');
$this->assertEquals($expected, OnArray::removeEmptyElementsFromArray($expected));
$expected = array('Hello world', 'Blue sky');
$this->assertEquals($expected, OnArray::removeEmptyElementsFromArray($expected));
$raw2 = array('Hello world', '', 'Blue sky');
$this->assertEquals($expected, OnArray::removeEmptyElementsFromArray($raw2));
$raw2 = array('Hello world', 'Blue sky', '');
$this->assertEquals($expected, OnArray::removeEmptyElementsFromArray($raw2));
$raw2 = array('', 'Hello world', 'Blue sky');
$this->assertEquals($expected, OnArray::removeEmptyElementsFromArray($raw2));
$expected = array('first' => 'Hello world');
$this->assertEquals($expected, OnArray::removeEmptyElementsFromArray($expected));
$expected = array('first' => 'Hello world', 'second' => 'Blue sky');
$this->assertEquals($expected, OnArray::removeEmptyElementsFromArray($expected));
$raw2 = array('first' => 'Hello world', 'second' => '', 'third' => 'Blue sky');
$expected = array('first' => 'Hello world', 'third' => 'Blue sky');
$this->assertEquals($expected, OnArray::removeEmptyElementsFromArray($raw2));
$raw2 = array('first' => 'Hello world', 'second' => 'Blue sky', 'third' => '');
$expected = array('first' => 'Hello world', 'second' => 'Blue sky');
$this->assertEquals($expected, OnArray::removeEmptyElementsFromArray($raw2));
$raw2 = array('first' => '', 'second' => 'Hello world', 'third' => 'Blue sky');
$expected = array('second' => 'Hello world', 'third' => 'Blue sky');
$this->assertEquals($expected, OnArray::removeEmptyElementsFromArray($raw2));
$raw2 = array('first' => '', '' => 'Hello world', 'third' => 'Blue sky');
$expected = array('' => 'Hello world', 'third' => 'Blue sky');
$this->assertEquals($expected, OnArray::removeEmptyElementsFromArray($raw2));
} }
} }
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment