From fbbab7656679cb79c3526161348279b167feab3b Mon Sep 17 00:00:00 2001
From: Carsten  Rose <carsten.rose@math.uzh.ch>
Date: Mon, 27 Feb 2017 23:01:49 +0100
Subject: [PATCH] #3063, Radios / Checkboxes als Buttons (Bootstrap)
 Implemented for Radios. Checkbox is still open.

Index.rst, AbstractBuildForm.php, BuildFormBootstrap.php, Constants.php: split buildRadio() in constructRadioPlain() and constructRadioButton. After several tries to make a base function for both construct variants, CR decided that the code is much more easier to read with to complete separate layout functions.

AbstractBuildForm.php: Notes of input elements as well as the text of a 'Note' FormElement are now wrapped in the class 'qfq-note' - this aligns the text to the label baseline.
---
 extension/Documentation/UsersManual/Index.rst |  22 ++-
 extension/qfq/qfq/AbstractBuildForm.php       | 152 ++++++++++++++----
 extension/qfq/qfq/BuildFormBootstrap.php      |  19 ---
 extension/qfq/qfq/Constants.php               |   1 +
 4 files changed, 140 insertions(+), 54 deletions(-)

diff --git a/extension/Documentation/UsersManual/Index.rst b/extension/Documentation/UsersManual/Index.rst
index 71d269b68..f1d91a97a 100644
--- a/extension/Documentation/UsersManual/Index.rst
+++ b/extension/Documentation/UsersManual/Index.rst
@@ -8,6 +8,8 @@
 ..      ,,
 ..
 .. --------------------------------------------------
+.. External Links: `Bootstrap <http://getbootstrap.com/>`_:
+..
 .. -*- coding: utf-8 -*- with BOM.
 
 .. include:: ../Includes.txt
@@ -1140,7 +1142,7 @@ Type: radio
 
 * Radio Buttons will be built from one of three sources:
 
-  * 'sql1': E.g. *{{!SELECT type AS label FROM car }}* or *{{!SELECT type AS label, typeNr AS id FROM car}}* or *{{!SHOW tables}}*.
+  1. 'sql1': E.g. *{{!SELECT type AS label FROM car }}* or *{{!SELECT type AS label, typeNr AS id FROM car}}* or *{{!SHOW tables}}*.
 
     * Resultset format 'named': column 'label' and optional a column 'id'.
     * Resultset format 'index':
@@ -1148,15 +1150,16 @@ Type: radio
       * One column in resultset >> first column represents *label*
       * Two or more columns in resultset >> first column represents *id* and second column represents *label*.
 
-  * *FormElement.parameter*:
+  2. *FormElement.parameter*:
 
     * *itemList* = `<attribute>` E.g.: *itemList=red,blue,orange* or *itemList=1:red,2:blue:3:orange*
 
-  * Definition of the *enum* or *set* field (only labels, ids are not possible).
+  3. Definition of the *enum* or *set* field (only labels, ids are not possible).
 
 
 * *FormElement.maxlength* = `<value>`
 
+   * Applies only to 'plain' radio elements (not the Bootstrap 'buttonClass' from below)
    * *vertical* or *horizontal* alignment:
 
      * `<value>`: '', 0, 1 - The radios will be aligned *vertical*.
@@ -1169,6 +1172,19 @@ Type: radio
   * *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
+    `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>`_:
+    * `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*:
diff --git a/extension/qfq/qfq/AbstractBuildForm.php b/extension/qfq/qfq/AbstractBuildForm.php
index 5714ce9a7..e63878b2f 100644
--- a/extension/qfq/qfq/AbstractBuildForm.php
+++ b/extension/qfq/qfq/AbstractBuildForm.php
@@ -1326,8 +1326,6 @@ abstract class AbstractBuildForm {
     /**
      * Build HTML 'radio' element.
      *
-     * Checkboxes will only be submitted, if they are checked. Therefore, a hidden element with the unchecked value will be transfered first.
-     *
      * Format: <input type="hidden" name="$htmlFormElementId" value="$valueUnChecked">
      *         <input name="$htmlFormElementId" type="radio" [autofocus="autofocus"]
      *            [required="required"] [disabled="disabled"] value="<value>" [checked="checked"] >
@@ -1342,6 +1340,118 @@ abstract class AbstractBuildForm {
      * @throws \qfq\UserFormException
      */
     public function buildRadio(array $formElement, $htmlFormElementId, $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->constructRadioButton($formElement, $htmlFormElementId, $value, $json, $mode);
+        } else {
+            return $this->constructRadioPlain($formElement, $htmlFormElementId, $value, $json, $mode);
+        }
+    }
+
+    /**
+     * Build Bootstrap Button 'radio' element.
+     *
+     * <div class="btn-group" data-toggle="buttons">
+     *
+     *   <label class="btn btn-primary active">
+     *     <input type="radio" name="options" id="option1" autocomplete="off" checked> Radio 1 (preselected)
+     *   </label>
+     *
+     *   <label class="btn btn-primary">
+     *     <input type="radio" name="options" id="option2" autocomplete="off"> Radio 2
+     *   </label>
+     *
+     *   <label class="btn btn-primary">
+     *     <input type="radio" name="options" id="option3" autocomplete="off"> Radio 3
+     *   </label>
+     *
+     * </div>
+     *
+     * @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
+     */
+    private function constructRadioButton(array $formElement, $htmlFormElementId, $value, array &$json, $mode = FORM_LOAD) {
+        $itemKey = array();
+        $itemValue = array();
+
+        // Fill $itemKey & $itemValue
+        $this->getKeyValueListFromSqlEnumSpec($formElement, $itemKey, $itemValue);
+
+        $attributeBase = $this->getAttributeFeMode($formElement[FE_MODE]);
+        $attributeBase .= $this->getAttributeList($formElement, [F_FE_DATA_PATTERN_ERROR, F_FE_DATA_REQUIRED_ERROR, F_FE_DATA_MATCH_ERROR, F_FE_DATA_ERROR]);
+        $attributeBase .= Support::doAttribute('name', $htmlFormElementId);
+        $attributeBase .= Support::doAttribute('type', $formElement[FE_TYPE]);
+        $attributeBase .= Support::doAttribute('data-load', ($formElement[FE_DYNAMIC_UPDATE] === 'yes') ? 'data-load' : '');
+
+        $attribute = $attributeBase;
+        if (isset($formElement['autofocus'])) {
+            $attribute .= Support::doAttribute('autofocus', $formElement['autofocus']);
+        }
+
+        $html = $this->buildNativeHidden($htmlFormElementId, $value);
+
+        for ($ii = 0; $ii < count($itemValue); $ii++) {
+            $classActive = '';
+
+            $attribute .= Support::doAttribute('value', $itemKey[$ii]);
+            $attribute .= Support::doAttribute('title', $formElement['tooltip']);
+
+            if ($itemKey[$ii] === $value) {
+                $attribute .= Support::doAttribute('checked', 'checked');
+                $classActive = ' active';
+            }
+
+            $htmlElement = '<input ' . $attribute . '>' . $itemValue[$ii];
+
+            $htmlElement = Support::wrapTag("<label class='btn " . $formElement[FE_BUTTON_CLASS] . "$classActive'>", $htmlElement);
+
+            $html .= $htmlElement;
+
+            // Init for the next round
+            $attribute = $attributeBase;
+        }
+
+        $html = Support::wrapTag('<div class="btn-group" data-toggle="buttons">', $html);
+
+        $json = $this->getJsonElementUpdate($htmlFormElementId, $value, $formElement[FE_MODE]);
+
+        return $html;
+    }
+
+    /**
+     * Build plain HTML 'radio' element.
+     *
+     * 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 $htmlFormElementId
+     * @param $value
+     * @param array $json
+     * @param string $mode FORM_LOAD | FORM_UPDATE | FORM_SAVE
+     * @return string
+     * @throws CodeException
+     * @throws \qfq\UserFormException
+     */
+    private function constructRadioPlain(array $formElement, $htmlFormElementId, $value, array &$json, $mode = FORM_LOAD) {
+
+        if (isset($formElement[FE_BUTTON_CLASS])) {
+            return $this->constructRadioButton($formElement, $htmlFormElementId, $value, $json, $mode);
+        }
+
+
         $itemKey = array();
         $itemValue = array();
 
@@ -1355,22 +1465,20 @@ abstract class AbstractBuildForm {
         $attributeBase .= Support::doAttribute('data-load', ($formElement[FE_DYNAMIC_UPDATE] === 'yes') ? 'data-load' : '');
 
         $jj = 0;
-        $flagFirst = true;
 
         $orientation = ($formElement['maxLength'] > 1) ? ALIGN_HORIZONTAL : ALIGN_VERTICAL;
         $radioClass = ($orientation === ALIGN_HORIZONTAL) ? 'radio-inline' : 'radio';
         $br = '';
 
+        $attribute = $attributeBase;
+        if (isset($formElement['autofocus'])) {
+            $attribute .= Support::doAttribute('autofocus', $formElement['autofocus']);
+        }
+
         $html = $this->buildNativeHidden($htmlFormElementId, $value);
 
         for ($ii = 0; $ii < count($itemValue); $ii++) {
             $jj++;
-            $attribute = $attributeBase; //
-            if ($flagFirst) {
-                $flagFirst = false;
-                if (isset($formElement['autofocus']))
-                    $attribute .= Support::doAttribute('autofocus', $formElement['autofocus']);
-            }
 
             $attribute .= Support::doAttribute('value', $itemKey[$ii]);
             $attribute .= Support::doAttribute('title', $formElement['tooltip']);
@@ -1402,6 +1510,8 @@ abstract class AbstractBuildForm {
             $htmlElement = Support::wrapTag("<div class='$radioClass'>", $htmlElement) . $br;
 
             $html .= $htmlElement;
+
+            $attribute = $attributeBase;
         }
 
         $json = $this->getJsonElementUpdate($htmlFormElementId, $value, $formElement[FE_MODE]);
@@ -2279,7 +2389,7 @@ abstract class AbstractBuildForm {
      * @return mixed
      */
     public function buildNote(array $formElement, $htmlFormElementId, $value, array &$json, $mode = FORM_LOAD) {
-        return $value;
+        return Support::wrapTag("<div class='qfq-note'>", $value);
     }
 
     /**
@@ -2525,26 +2635,4 @@ EOT;
 
     abstract public function buildRowNative(array $formElement, $htmlElement, $htmlFormElementId);
 
-
-    /**
-     * Create a delete link.
-     *
-     * @param $table
-     * @param $recordId
-     * @param $symbol
-     * @param $toolTip
-     * @return string
-     */
-//    private function createDeleteLink($table, $recordId, $symbol, $toolTip) {
-//
-//        if ($this->showDebugInfo) {
-//            $toolTip .= PHP_EOL . "table = '$table'" . PHP_EOL . "id = '$recordId'";
-//        }
-//
-//        $url = $this->createDeleteUrl('', $table, $recordId);
-//
-//        return Support::wrapTag('<a ' . Support::doAttribute('href', $url) . ' title="' . $toolTip . '">', $symbol);
-//
-//    }
-
 }
\ No newline at end of file
diff --git a/extension/qfq/qfq/BuildFormBootstrap.php b/extension/qfq/qfq/BuildFormBootstrap.php
index 844f18889..37158c7ce 100644
--- a/extension/qfq/qfq/BuildFormBootstrap.php
+++ b/extension/qfq/qfq/BuildFormBootstrap.php
@@ -405,25 +405,6 @@ EOF;
         return $html;
     }
 
-    /**
-     * @param array $formElement
-     * @param string $htmlElement
-     * @return string
-     */
-//    public function buildRowNative(array $formElement, $htmlElement, $htmlFormElementId) {
-//        $html = '';
-//
-//        $htmlLabel = $this->buildLabel($htmlFormElementId, $formElement[FE_LABEL]);
-//
-//        $html .= $this->wrapItem(WRAP_SETUP_LABEL, $htmlLabel);
-//        $html .= $this->wrapItem(WRAP_SETUP_INPUT, $htmlElement);
-//        $html .= $this->wrapItem(WRAP_SETUP_NOTE, $formElement[FE_NOTE], true);
-//
-//        $html = $this->wrapItem(WRAP_SETUP_ELEMENT, $html);
-//
-//        return $html;
-//    }
-
     /**
      * @param array $formElement Complete FormElement, especially some FE_WRAP
      * @param string $htmlElement Content to wrap.
diff --git a/extension/qfq/qfq/Constants.php b/extension/qfq/qfq/Constants.php
index 3470e2e16..a37baf1fd 100644
--- a/extension/qfq/qfq/Constants.php
+++ b/extension/qfq/qfq/Constants.php
@@ -567,6 +567,7 @@ const FE_TEMPLATE_GROUP_REMOVE_TEXT = 'tgRemoveText';
 const FE_TEMPLATE_GROUP_CLASS = 'tgClass';
 const FE_TEMPLATE_GROUP_DEFAULT_MAX_LENGTH = 5;
 const FE_TEMPLATE_GROUP_NAME_PATTERN = '%d';
+const FE_BUTTON_CLASS = 'buttonClass';
 const RETYPE_FE_NAME_EXTENSION = 'RETYPE';
 
 // FormElement Types
-- 
GitLab