BuildFormBootstrap.php 26.5 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 1/25/16
 * Time: 10:00 PM
 */

namespace qfq;

use qfq;
12
use qfq\UserFormException;
13
14

require_once(__DIR__ . '/../qfq/Constants.php');
15
require_once(__DIR__ . '/../qfq/helper/OnArray.php');
16
require_once(__DIR__ . '/../qfq/AbstractBuildForm.php');
17
require_once(__DIR__ . '/../qfq/exceptions/UserFormException.php');
18

Carsten  Rose's avatar
Carsten Rose committed
19
20
21
22
/**
 * Class BuildFormBootstrap
 * @package qfq
 */
23
24
class BuildFormBootstrap extends AbstractBuildForm {

25
26
27
28
29
30
31
32
33
34
    private $isFirstPill;

    /**
     * @param array $formSpec
     * @param array $feSpecAction
     * @param array $feSpecNative
     */
    public function __construct(array $formSpec, array $feSpecAction, array $feSpecNative) {
        parent::__construct($formSpec, $feSpecAction, $feSpecNative);
        $this->isFirstPill = true;
35
36

        // Set some defaults
37
38
39
//        if (!isset($this->formSpec['class'])) {
//            $this->formSpec['class'] = 'container';
//        }
Carsten  Rose's avatar
Carsten Rose committed
40

41
//        $this->formSpec['class'] = 'none';
42
43
    }

Carsten  Rose's avatar
Carsten Rose committed
44
45
46
    /**
     *
     */
47
    public function fillWrap() {
48
49
50
51

//        $this->wrap[WRAP_SETUP_OUTER][WRAP_SETUP_START] = '<div class="tab-content">';
//        $this->wrap[WRAP_SETUP_OUTER][WRAP_SETUP_END] = '</div>';

52
53
        $this->wrap[WRAP_SETUP_TITLE][WRAP_SETUP_START] = "<div class='row hidden-xs'><div class='col-md-12'><h1>";
        $this->wrap[WRAP_SETUP_TITLE][WRAP_SETUP_END] = "</h1></div></div>";
54

55
        // Element: Label + Input + Note
56
        $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS] = "form-group clearfix";
57
        $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_START] = "<div class='" . $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS] . "'>";
58
        $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_END] = "</div>";
59

60
61
        $this->wrap[WRAP_SETUP_SUBRECORD][WRAP_SETUP_START] = "<div class='col-md-12'>";
        $this->wrap[WRAP_SETUP_SUBRECORD][WRAP_SETUP_END] = "</div>";
62

63
64
        $this->wrap[WRAP_SETUP_IN_FIELDSET][WRAP_SETUP_START] = "";
        $this->wrap[WRAP_SETUP_IN_FIELDSET][WRAP_SETUP_END] = "";
65

66
67
68
        $this->wrap[WRAP_SETUP_IN_TEMPLATE_GROUP][WRAP_SETUP_START] = "";
        $this->wrap[WRAP_SETUP_IN_TEMPLATE_GROUP][WRAP_SETUP_END] = "";

69
70
//        $this->feDivClass['radio'] = 'radio';
//        $this->feDivClass['checkbox'] = 'checkbox';
71
72
    }

73
74
75
76
77
78
79
80
81
82
    /**
     * @param string $addClass
     * @return string
     */
    public function getRowOpenTag($addClass = '') {
        $class = Support::doAttribute('class', [$this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_CLASS], $addClass]);

        return "<div $class>";

    }
83

84
85
86
87
88
89
    /**
     * @param $label
     * @param $input
     * @param $note
     */
    public function fillWrapLabelInputNote($label, $input, $note) {
90
        $this->wrap[WRAP_SETUP_LABEL][WRAP_SETUP_START] = "<div class='col-md-$label qfq-label'>";
91
92
93
        $this->wrap[WRAP_SETUP_LABEL][WRAP_SETUP_END] = "</div>";
        $this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_START] = "<div class='col-md-$input'>";
        $this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_END] = "</div>";
Carsten  Rose's avatar
Carsten Rose committed
94
        $this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_START] = "<div class='col-md-$note qfq-note'>";
95
96
97
98
        $this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_END] = "</div>";

    }

Carsten  Rose's avatar
Carsten Rose committed
99
100
101
    /**
     * @return string
     */
102
103
104
105
    public function getProcessFilter() {
        return FORM_ELEMENTS_NATIVE_SUBRECORD;
    }

Carsten  Rose's avatar
Carsten Rose committed
106
107
108
    /**
     * @return string
     */
109
110
111
112
113
114
115
116
    public function doSubrecords() {
        return '';
    }

    /**
     * @return string
     */
    public function head() {
117
        $html = '';
118

119
        $html .= '<div ' . Support::doAttribute('class', $this->formSpec[F_CLASS], TRUE) . '>'; // main <div class=...> around everything, Whole FORM; class="container" or class="container-fluid"
120

121
        //TODO: nicer error reporting - make test with 'unknown index' here - unset($this->formSpec['title']) - See #3424
122
123
124
        $title = Support::wrapTag('<div class="hidden-xs col-sm-6 col-md-8">', Support::wrapTag('<h3>', $this->formSpec['title']));
        $button = Support::wrapTag('<div class="col-xs-12 col-sm-6 col-md-4">', $this->buildButtons());
        $html .= Support::wrapTag('<div class="row">', $title . $button);
125
126


127
128
        $pill = $this->buildPillNavigation(OnArray::filter($this->feSpecNative, 'type', 'pill'));
        $html .= Support::wrapTag('<div class="row">', $pill);
129

130
131
        $html .= $this->getFormTag();

132
133
134
135
136
        $class = ['tab-content', $this->formSpec[F_CLASS_BODY]];
        if ($pill == '') {
            $class[] = 'col-md-12';
        }
        $html .= "<div " . Support::doAttribute('class', $class) . ">";
137
138
139
140

        return $html;
    }

141
142
143
144
145
146
    /**
     * Creates a Checkbox, which toggles 'hide'/'unhide' via JS, on all elements with class= CLASS_FORM_ELEMENT_EDIT.
     *
     * @return string - the rendered Checkbox
     */
    private function buildShowEditFormElementCheckbox() {
Carsten  Rose's avatar
Carsten Rose committed
147
        // EditFormElement Icons
148
149
        $js = '$(".' . CLASS_FORM_ELEMENT_EDIT . '").toggleClass("hidden")';
        $element = "<input type='checkbox' onchange='" . $js . "'>" .
150
            Support::wrapTag("<span title='Toggle: Edit form element icons' class='" . GLYPH_ICON . ' ' . GLYPH_ICON_TASKS . "'>", '');
151
        $element = Support::wrapTag('<label class="btn btn-default navbar-btn">', $element);
Carsten  Rose's avatar
Carsten Rose committed
152

153
154
155
        return Support::wrapTag('<div class="btn-group" data-toggle="buttons">', $element);
    }

Carsten  Rose's avatar
Carsten Rose committed
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
    /**
     * Creates a button to open 'CopyForm' with the current form as source.
     *
     * @return string - the rendered button
     */
    private function buildButtonCopyForm() {

        // Show copy icon only on form 'form' and only if there is a form loaded (id>0)
        if ($this->formSpec[COLUMN_ID] != 1) {
            return '';
        }
        // current loaded form.
        $formId = $this->store->getVar(COLUMN_ID, STORE_RECORD . STORE_ZERO);

        $queryStringArray = [
            'id' => $this->store->getVar(SYSTEM_EDIT_FORM_PAGE, STORE_SYSTEM),
            'form' => 'copyForm',
            'r' => 0,
            'idSrc' => $formId,
        ];
        $queryString = Support::arrayToQueryString($queryStringArray);
        $sip = $this->store->getSipInstance();
        $url = $sip->queryStringToSip($queryString);

        $toolTip = "Duplicate form" . PHP_EOL . PHP_EOL . OnArray::toString($queryStringArray, ' = ', PHP_EOL, "'");
        $status = ($formId == 0) ? 'disabled' : '';

        return $this->buildButtonAnchor($url, 'form-view-' . $this->formSpec[F_ID], '', $toolTip, GLYPH_ICON_DUPLICATE, $status, 'btn btn-default navbar-btn');
    }

Carsten  Rose's avatar
Carsten Rose committed
186
187
188
189
190
191
192
    /**
     * Creates a link to open current form loaded in FormEditor
     *
     * @return string - the rendered Checkbox
     */
    private function buildViewForm() {

193
194
195
196
        $form = false;
        $url = '';
        $status = '';

Carsten  Rose's avatar
Carsten Rose committed
197
198
199
200
201
        switch ($this->formSpec[F_NAME]) {
            case 'form':
                $form = $this->store->getVar(F_NAME, STORE_RECORD);
                break;
            case 'formElement':
202
203
204
205
                if (false !== ($formId = $this->store->getVar(FE_FORM_ID, STORE_SIP . STORE_RECORD))) {
                    $row = $this->db->sql("SELECT f.name FROM Form AS f WHERE id=" . $formId, ROW_EXPECT_1);
                    $form = current($row);
                }
Carsten  Rose's avatar
Carsten Rose committed
206
207
208
209
210
211
                break;
            default:
                return '';
        }

        if ($form === false) {
212
213
214
215
216
217
218
219
220
221
222
223
224
225
            $toolTip = "Form not 'form' or 'formElement'";
            $status = 'disabled';
        } else {

            $queryStringArray = [
                'id' => $this->store->getVar(SYSTEM_EDIT_FORM_PAGE, STORE_SYSTEM),
                'form' => $form,
                'r' => 0
            ];
            $queryString = Support::arrayToQueryString($queryStringArray);
            $sip = $this->store->getSipInstance();
            $url = $sip->queryStringToSip($queryString);

            $toolTip = "View current form with r=0" . PHP_EOL . PHP_EOL . OnArray::toString($queryStringArray, ' = ', PHP_EOL, "'");
Carsten  Rose's avatar
Carsten Rose committed
226
227
        }

228
        return $this->buildButtonAnchor($url, 'form-view-' . $this->formSpec[F_ID], '', $toolTip, GLYPH_ICON_VIEW, $status, 'btn btn-default navbar-btn');
Carsten  Rose's avatar
Carsten Rose committed
229
230
231
232
    }

    //        glyphicon glyphicon-eye-open

233
    /**
234
     * Build Buttons panel on top right corner of form.
235
     * Simulate Submit Button: http://www.javascript-coder.com/javascript-form/javascript-form-submit.phtml
236
237
238
239
240
     *
     * @return string
     */
    private function buildButtons() {
        $buttonNew = '';
241
242
243
244
        $buttonDelete = '';
        $buttonClose = '';
        $buttonSave = '';
        $buttonEditForm = '';
Carsten  Rose's avatar
Carsten Rose committed
245
246
247
        $recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP);

        // Button: FormEdit
248
        if ($this->showDebugInfoFlag) {
249
            $toolTip = "Edit form" . PHP_EOL . PHP_EOL . OnArray::toString($this->store->getStore(STORE_SIP), ' = ', PHP_EOL, "'");
250
            $url = $this->createFormEditorUrl(FORM_NAME_FORM, $this->formSpec[F_ID]);
251

Carsten  Rose's avatar
Carsten Rose committed
252
253
            $buttonEditForm = $this->buildViewForm() .
                $this->buildShowEditFormElementCheckbox() .
Carsten  Rose's avatar
Carsten Rose committed
254
                $this->buildButtonCopyForm() .
255
                $this->buildButtonAnchor($url, 'form-edit-button', '', $toolTip, GLYPH_ICON_TOOL, '', 'btn btn-default navbar-btn');
256
257
258
        }

        // Button: Save
Carsten  Rose's avatar
Carsten Rose committed
259
        if (Support::findInSet(FORM_BUTTON_SAVE, $this->formSpec[F_SHOW_BUTTON]) && $this->formSpec[F_SUBMIT_BUTTON_TEXT] === '') {
260
261
            $toolTip = $this->formSpec[F_SAVE_BUTTON_TOOLTIP];
            // In debugMode every button link should show the information behind the SIP.
262
            if ($this->showDebugInfoFlag) {
263
                $toolTip .= PHP_EOL . "table = '" . $this->formSpec[F_TABLE_NAME] . "'" . PHP_EOL . "r = '" . $recordId . "'";
264
265
            }

266
267
            $buttonSave = $this->buildButtonCode('save-button', $this->formSpec[F_SAVE_BUTTON_TEXT], $toolTip,
                $this->formSpec[F_SAVE_BUTTON_GLYPH_ICON], '', $this->formSpec[F_BUTTON_ON_CHANGE_CLASS], $this->formSpec[F_SAVE_BUTTON_CLASS]);
268
269
270
        }

        // Button: Close
Carsten  Rose's avatar
Carsten Rose committed
271
        if (Support::findInSet(FORM_BUTTON_CLOSE, $this->formSpec[F_SHOW_BUTTON])) {
272
273
274
            $buttonClose = $this->buildButtonCode('close-button', $this->formSpec[F_CLOSE_BUTTON_TEXT],
                $this->formSpec[F_CLOSE_BUTTON_TOOLTIP],
                $this->formSpec[F_CLOSE_BUTTON_GLYPH_ICON], '', '', $this->formSpec[F_CLOSE_BUTTON_CLASS]);
Carsten  Rose's avatar
Carsten Rose committed
275
        }
276

Carsten  Rose's avatar
Carsten Rose committed
277
        // Button: Delete
Carsten  Rose's avatar
Carsten Rose committed
278
        if (Support::findInSet(FORM_BUTTON_DELETE, $this->formSpec[F_SHOW_BUTTON])) {
279
            $toolTip = $this->formSpec[F_DELETE_BUTTON_TOOLTIP];
280

281
            if ($this->showDebugInfoFlag && $recordId > 0) {
282
                $toolTip .= PHP_EOL . "form = '" . $this->formSpec[F_FINAL_DELETE_FORM] . "'" . PHP_EOL . "r = '" . $recordId . "'";
Carsten  Rose's avatar
Carsten Rose committed
283
            }
284
            $disabled = ($recordId > 0) ? '' : 'disabled';
Carsten  Rose's avatar
Carsten Rose committed
285

286
287
            $buttonDelete = $this->buildButtonCode('delete-button', $this->formSpec[F_DELETE_BUTTON_TEXT], $toolTip,
                $this->formSpec[F_DELETE_BUTTON_GLYPH_ICON], $disabled, '', $this->formSpec[F_DELETE_BUTTON_CLASS]);
288
289
        }

Carsten  Rose's avatar
Carsten Rose committed
290
        // Button: New
Carsten  Rose's avatar
Carsten Rose committed
291
        if (Support::findInSet(FORM_BUTTON_NEW, $this->formSpec[F_SHOW_BUTTON])) {
292
            $url = $this->deriveNewRecordUrlFromExistingSip($toolTip);
Carsten  Rose's avatar
Carsten Rose committed
293

294
295
            $buttonNew = $this->buildButtonAnchor($url, 'form-new-button', $this->formSpec[F_NEW_BUTTON_TEXT],
                $this->formSpec[F_NEW_BUTTON_TOOLTIP], $this->formSpec[F_NEW_BUTTON_GLYPH_ICON], '', $this->formSpec[F_NEW_BUTTON_CLASS]);
296
297
        }

298
        // Arrangement: Edit Form / Save / Close / Delete / New
Carsten  Rose's avatar
Carsten Rose committed
299
        // Specified in reverse order cause 'pull-right' inverts the order. http://getbootstrap.com/css/#helper-classes-floats
300
301
302
303
304
        $html = '';
        $html .= Support::wrapTag('<div class="btn-group pull-right" role="group">', $buttonNew);
        $html .= Support::wrapTag('<div class="btn-group pull-right" role="group">', $buttonDelete);
        $html .= Support::wrapTag('<div class="btn-group pull-right" role="group">', $buttonSave . $buttonClose);
        $html .= Support::wrapTag('<div class="btn-group pull-right" role="group">', $buttonEditForm);
305
306
307

        $html = Support::wrapTag('<div class="btn-toolbar" role="toolbar">', $html);

308
309
310
        return $html;
    }

311
    /**
312
313
     * Generic function to create a button with a given $buttonHtmlId, $url, $title, $icon (=glyph), $disabled
     *
Carsten  Rose's avatar
Carsten Rose committed
314
     * @param string $url
315
316
317
     * @param string $buttonHtmlId
     * @param string $text
     * @param string $toolTip
Carsten  Rose's avatar
Carsten Rose committed
318
     * @param string $icon
319
     * @param string $disabled
320
     * @param string $class
321
322
     * @return string
     */
323
324
325
326
327
328
329
    private function buildButtonAnchor($url, $buttonHtmlId, $text, $toolTip, $icon, $disabled = '', $class = '') {

        if ($icon === '') {
            $element = $text;
        } else {
            $element = Support::wrapTag("<span " . Support::doAttribute('class', "glyphicon $icon") . ">", $text);
        }
Carsten  Rose's avatar
Carsten Rose committed
330
331

        $attribute = Support::doAttribute('href', $url);
332
333
334
        $attribute .= Support::doAttribute('id', $buttonHtmlId);
        $attribute .= Support::doAttribute('class', "$class $disabled");
        $attribute .= Support::doAttribute('title', $toolTip);
Carsten  Rose's avatar
Carsten Rose committed
335

336
        return Support::wrapTag("<a $attribute>", $element);
337
338
339
    }

    /**
340
341
     * Creates a button with the given attributes. If there is no $icon given, render the button without glyph.
     *
342
343
344
     * @param string $buttonHtmlId
     * @param string $text
     * @param string $tooltip
345
     * @param string $icon
346
347
348
     * @param string $disabled
     * @return string
     */
349
    private function buildButtonCode($buttonHtmlId, $text, $tooltip, $icon, $disabled = '', $buttonOnChangeClass = '', $class = '') {
350
351

        if ($icon === '') {
352
353
354
            $element = $text;
        } else {
            $element = "<span class='glyphicon $icon'>$text</span>";
355
356
        }

357
        $class = Support::doAttribute('class', $class);
358
        $dataClassOnChange = Support::doAttribute('data-class-on-change', $buttonOnChangeClass);
359
360
361
        $tooltip = Support::doAttribute('title', $tooltip);

        return "<button id='$buttonHtmlId' type='button' $class $dataClassOnChange $tooltip $disabled>$element</button>";
362
363
    }

Carsten  Rose's avatar
Carsten Rose committed
364
    /**
365
     * @param $pillArray
Carsten  Rose's avatar
Carsten Rose committed
366
     * @return string
367
     * @throws UserFormException
Carsten  Rose's avatar
Carsten Rose committed
368
     */
369
370
371
    private function buildPillNavigation($pillArray) {
        $pillButton = '';
        $pillDropdown = '';
372
        $htmlDropdown = '';
373
374
375
376
377
378

        if ($pillArray == null)
            return '';

        $maxVisiblePill = (isset($this->formSpec['maxVisiblePill']) && $this->formSpec['maxVisiblePill'] !== '') ? $this->formSpec['maxVisiblePill'] : 1000;

379
380
        $parameterLanguageFieldName = $this->store->getVar(SYSTEM_PARAMETER_LANGUAGE_FIELD_NAME, STORE_SYSTEM);

381
382
383
384
        // Iterate over all 'pill'
        $ii = 0;
        $active = 'class="active"';
        foreach ($pillArray as $formElement) {
385
386
387
388
389

            $formElement = $this->evaluate->parseArray($formElement);
            HelperFormElement::explodeParameter($formElement, F_PARAMETER);
            $formElement = HelperFormElement::setLanguage($formElement, $parameterLanguageFieldName);

390
391
            $ii++;

392
            if ($formElement[FE_NAME] === '' || $formElement[FE_LABEL] === '') {
393
                $this->store->setVar(SYSTEM_FORM_ELEMENT, Logger::formatFormElementName($formElement), STORE_SYSTEM);
394
                $this->store->setVar(SYSTEM_FORM_ELEMENT_COLUMN, 'name, label', STORE_SYSTEM);
395
                throw new UserFormException("Field 'name' and/or 'label' are empty", ERROR_NAME_LABEL_EMPTY);
396
397
            }

398
            // Anker for pill navigation
399
            $a = '<a ' . Support::doAttribute('href', '#' . $this->createAnker($formElement['id'])) . ' data-toggle="tab">' . $formElement[FE_LABEL] . '</a>';
400

401
402
403
404
405
406
407
            if ($ii <= $maxVisiblePill) {
                $pillButton .= '<li role="presentation" ' . $active . '>' . $a . '</li>';
            } else {
                $pillDropdown .= '<li>' . $a . '</li>';
            }
            $active = '';
        }
408

409
410
        // Pill Dropdown necessary?
        if ($ii > $maxVisiblePill) {
411
            $htmlDropdown = Support::wrapTag('<ul class="dropdown-menu qfq-form-pill ' . $this->formSpec[F_CLASS_PILL] . '">', $pillDropdown, true);
412
            $htmlDropdown = '<a class="dropdown-toggle" data-toggle="dropdown" href="#" role="button">more <span class="caret"></span></a>' . $htmlDropdown;
413
            $htmlDropdown = Support::wrapTag('<li role="presentation" class="dropdown">', $htmlDropdown, false);
414
        }
415

416
        $htmlDropdown = Support::wrapTag('<ul id="' . $this->getTabId() . '" class="nav nav-pills qfq-form-pill ' . $this->formSpec[F_CLASS_PILL] . '" role="tablist">', $pillButton . $htmlDropdown);
417
        $htmlDropdown = Support::wrapTag('<div class="col-md-12">', $htmlDropdown);
418

419
        return $htmlDropdown;
420
421
422
423
424
425
426
427
428
429
430
431
    }

    /**
     * Create an identifier for the pill navigation menu
     *
     * @param $id
     * @return string
     */
    private function createAnker($id) {
        return $this->formSpec['name'] . '_' . $id;
    }

Carsten  Rose's avatar
Carsten Rose committed
432
433
434
435
436
437
438
    /**
     * @return string
     */
    private function getTabId() {
        return 'qfqTabs';
    }

439
440
441
442
443
    /**
     * Builds the complete HTML '<form ...>'-tag
     *
     * @return string
     */
444
    public function getFormTag() {
445
446
447
448

        $attribute = $this->getFormTagAtrributes();

        $attribute['class'] = 'form-horizontal';
449
        $attribute['data-toggle'] = 'validator';
450
451
452
        if (isset($this->formSpec[F_SAVE_BUTTON_ACTIVE])) {
            $attribute[DATA_ENABLE_SAVE_BUTTON] = 'true';
        }
453
454

        $honeypot = $this->getHoneypotVars();
455
        $md5 = $this->buildInputRecordHashMd5();
456

Carsten  Rose's avatar
Carsten Rose committed
457
        return '<form ' . OnArray::toString($attribute, '=', ' ', "'") . '>' . $honeypot . $md5;
458
459
    }

460
461
462
463
    /**
     * @return string
     */
    public function tail() {
464

465
        $html = '';
Carsten  Rose's avatar
Carsten Rose committed
466
        $deleteUrl = '';
467

468
469
        $formId = $this->getFormId();

470
471
        // Button Save at bottom of form - only if there is a button text given.
        if ($this->formSpec[F_SUBMIT_BUTTON_TEXT] !== '') {
472
473
474
475

            // Default setzen:
            $this->fillWrapLabelInputNote($this->formSpec[F_BS_LABEL_COLUMNS], $this->formSpec[F_BS_INPUT_COLUMNS], $this->formSpec[F_BS_NOTE_COLUMNS]);

476
477
            $buttonText = $this->formSpec[F_SUBMIT_BUTTON_TEXT];

478
            $htmlElement = $this->buildButtonCode('save-button', $buttonText, $buttonText, '', '', $this->formSpec[F_BUTTON_ON_CHANGE_CLASS], 'btn btn-default');
479
480
481
482
483
484
485
486

            $html .= $this->wrapItem(WRAP_SETUP_LABEL, '');
            $html .= $this->wrapItem(WRAP_SETUP_INPUT, $htmlElement);
            $html .= $this->wrapItem(WRAP_SETUP_NOTE, '');

            $html = $this->wrapItem(WRAP_SETUP_ELEMENT, $html);
        }

487
        $html .= '</div> <!--class="tab-content" -->';  //  <div class="tab-content">
488
//        $html .= '<input type="submit" value="Submit">';
489

Carsten  Rose's avatar
Carsten Rose committed
490
491
492
        $formId = $this->getFormId();
        $tabId = $this->getTabId();

Carsten  Rose's avatar
Carsten Rose committed
493
        if (0 < ($recordId = $this->store->getVar(SIP_RECORD_ID, STORE_SIP))) {
494
            $deleteUrl = $this->createDeleteUrl($this->formSpec[F_FINAL_DELETE_FORM], $recordId);
Carsten  Rose's avatar
Carsten Rose committed
495
        }
496

497
498
499
        $actionUpload = FILE_ACTION . '=' . FILE_ACTION_UPLOAD;
        $actionDelete = FILE_ACTION . '=' . FILE_ACTION_DELETE;

500
501
502
        $apiDir = API_DIR;
        $apiDeletePhp = API_DIR . '/' . API_DELETE_PHP;

503
        $dirtyAction = ($this->formSpec[F_DIRTY_MODE] == DIRTY_MODE_NONE) ? '' : "dirtyUrl: '$apiDir/dirty.php',";
Carsten  Rose's avatar
Carsten Rose committed
504

505
        $html .= '</form>';  //  <form class="form-horizontal" ...
506
507
508
509
510
511
        $html .= <<<EOF
        <script type="text/javascript">
            $(function () {
                'use strict';
                QfqNS.Log.level = 0;

Carsten  Rose's avatar
Carsten Rose committed
512
513
514
                var qfqPage = new QfqNS.QfqPage({
                    tabsId: '$tabId',
                    formId: '$formId',
515
                    submitTo: '$apiDir/save.php',
Carsten  Rose's avatar
Carsten Rose committed
516
                    $dirtyAction
Carsten  Rose's avatar
Carsten Rose committed
517
                    deleteUrl: '$deleteUrl',
518
519
520
                    refreshUrl: '$apiDir/load.php',
                    fileUploadTo: '$apiDir/file.php?$actionUpload',
                    fileDeleteUrl: '$apiDir/file.php?$actionDelete'
Carsten  Rose's avatar
Carsten Rose committed
521
522
                });

523
                var qfqRecordList = new QfqNS.QfqRecordList('$apiDeletePhp');
Carsten  Rose's avatar
Carsten Rose committed
524
            })
525
526
         </script>
EOF;
527
        $html .= '</div>';  //  <div class="container-fluid"> === main <div class=...> around everything
528

529
530
531
532
533
        return $html;
    }

    /**
     * @param array $formElement
534
     * @param $htmlFormElementName
535
536
537
     * @param $value
     * @return mixed
     */
538
    public function buildPill(array $formElement, $htmlFormElementName, $value, array &$json) {
539
        $html = '';
540
541
542
543
        // save parent processed FE's
        $tmpStore = $this->feSpecNative;

        // child FE's
544
545
546
        $this->feSpecNative = $this->db->getNativeFormElements(SQL_FORM_ELEMENT_SPECIFIC_CONTAINER,
            ['yes', $this->formSpec["id"], 'native,container', $formElement['id']], $this->formSpec);

Carsten  Rose's avatar
Carsten Rose committed
547
        $html = $this->elements($this->store->getVar(SIP_RECORD_ID, STORE_SIP), FORM_ELEMENTS_NATIVE_SUBRECORD, 0, $json);
548
549
550
551

        // restore parent processed FE's
        $this->feSpecNative = $tmpStore;

552
553
554
        return $html;
    }

555
    /**
556
557
     * @param array $formElement Complete FormElement, especially some FE_WRAP
     * @param string $htmlElement Content to wrap.
558
     * @param $htmlFormElementName
559
     * @return string               Wrapped $htmlElement
560
     * @throws \qfq\UserFormException
561
     */
562
    public function buildRowNative(array $formElement, $htmlElement, $htmlFormElementName) {
563
        $html = '';
564
        $htmlLabel = '';
565
566
        $classHideRow = '';
        $classHideElement = '';
567

568
        if ($formElement[FE_MODE] == FE_MODE_HIDDEN) {
569
            if ($formElement[FE_FLAG_ROW_OPEN_TAG] && $formElement[FE_FLAG_ROW_CLOSE_TAG]) {
570
571
572
573
574
575
                $classHideRow = 'hidden';
            } else {
                $classHideElement = 'hidden';
            }
        }

Carsten  Rose's avatar
Carsten Rose committed
576
        // Label
577
        if ($formElement[FE_BS_LABEL_COLUMNS] > 0) {
578
579
            $addClass = ($formElement[FE_MODE] == FE_MODE_REQUIRED) ? CSS_REQUIRED : '';
            $htmlLabel = $this->buildLabel($htmlFormElementName, $formElement[FE_LABEL], $addClass);
580
        }
581

582
        $html .= $this->customWrap($formElement, $htmlLabel, FE_WRAP_LABEL, $formElement[FE_BS_LABEL_COLUMNS],
583
            [$this->wrap[WRAP_SETUP_LABEL][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_LABEL][WRAP_SETUP_END]], $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_LABEL);
584

Carsten  Rose's avatar
Carsten Rose committed
585
        // Input
586
        $html .= $this->customWrap($formElement, $htmlElement, FE_WRAP_INPUT, $formElement[FE_BS_INPUT_COLUMNS],
587
588
            [$this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_INPUT][WRAP_SETUP_END]],
            $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_INPUT, $classHideElement);
589

Carsten  Rose's avatar
Carsten Rose committed
590
        // Note
Carsten  Rose's avatar
Carsten Rose committed
591
        $note = $formElement[FE_NOTE];
592
        $html .= $this->customWrap($formElement, $note, FE_WRAP_NOTE, $formElement[FE_BS_NOTE_COLUMNS],
593
            [$this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_START], $this->wrap[WRAP_SETUP_NOTE][WRAP_SETUP_END]], $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_NOTE);
594

595
        // Row
596
597
        $openTag = $formElement[FE_FLAG_ROW_OPEN_TAG] ? $this->getRowOpenTag($classHideRow) : '';
        $closeTag = $formElement[FE_FLAG_ROW_CLOSE_TAG] ? $this->wrap[WRAP_SETUP_ELEMENT][WRAP_SETUP_END] : '';
598
599

        $html = $this->customWrap($formElement, $html, FE_WRAP_ROW, -1, [$openTag, $closeTag], $formElement[FE_HTML_ID] . HTML_ID_EXTENSION_ROW);
600
601
602
603

        return $html;
    }

604
    /**
605
606
607
608
609
610
     * Wrap content with $wrapArray or, if specified use $formElement[$wrapName]. Inject $htmlId in wrap.
     *
     * Result:
     * - if $bsColumns==0 and empty $formElement[$wrapName]: no wrap
     * - if $formElement[$wrapName] is given: wrap with that one. Else: wrap with $wrapArray
     * - if $htmlId is give, inject it in $wrap.
611
612
613
614
     *
     * @param array $formElement Complete FormElement, especially some FE_WRAP
     * @param string $htmlElement Content to wrap.
     * @param string $wrapName FE_WRAP_ROW, FE_WRAP_LABEL, FE_WRAP_INPUT, FE_WRAP_NOTE
615
     * @param int $bsColumns
616
     * @param array $wrapArray Systemwide Defaults: [ 'open wrap', 'close wrap' ]
617
618
     * @param string $htmlId
     * @param string $class
619
     * @return string Wrapped $htmlElement
620
     * @throws CodeException
621
622
     * @throws \qfq\UserFormException
     */
623
    private function customWrap(array $formElement, $htmlElement, $wrapName, $bsColumns, array $wrapArray, $htmlId = '', $class = '') {
624

625
        // If $bsColumns==0: do not wrap with default.
626
627
628
629
        if ($bsColumns == 0) {
            $wrapArray[0] = '';
            $wrapArray[1] = '';
        }
630

631
        // If there is a 'per FormElement'-wrap, take it.
632
633
634
635
636
637
638
639
        if (isset($formElement[$wrapName])) {
            $wrapArray = explode('|', $formElement[$wrapName], 2);
        }

        if (count($wrapArray) != 2) {
            throw new UserFormException("Need open & close wrap token for FormElement.parameter" . $wrapName . " - E.g.: <div ...>|</div>", ERROR_MISSING_VALUE);
        }

640
641
        if ($wrapArray[0] != '') {
            $wrapArray[0] = Support::insertAttribute($wrapArray[0], 'id', $htmlId);
642
            $wrapArray[0] = Support::insertAttribute($wrapArray[0], 'class', $class); // might be problematic, if there is already a 'class' defined.
643
644
        }

645
646
647
        return $wrapArray[0] . $htmlElement . $wrapArray[1];
    }

648

649
650
651
652
653
    /**
     * @param $formElement
     * @param $elementHtml
     * @return string
     */
654
    public function buildRowPill(array $formElement, $elementHtml) {
655
656
        $html = '';

657
        $html .= Support::wrapTag('<div class="col-md-12 qfq-form-body ' . $this->formSpec[F_CLASS_BODY] . '">', $elementHtml);
658
659
660

        $active = $this->isFirstPill ? ' active' : '';

661
        $html = Support::wrapTag('<div role="tabpanel" class="tab-pane' . $active . '" id="' . $this->createAnker($formElement['id']) . '">', $html);
662
663
664
665
666
667

        $this->isFirstPill = false;

        return $html;
    }

668
    /**
669
670
     * Builds a fieldset
     *
671
672
673
     * @param $formElement
     * @param $elementHtml
     */
674
    public function buildRowFieldset(array $formElement, $elementHtml) {
675
676
677
        $html = $elementHtml;

        return $html;
678
679
    }

680
681
682
683
684
685
686
687
688
689
690
691
    /**
     * Builds a templateGroup
     *
     * @param $formElement
     * @param $elementHtml
     */
    public function buildRowTemplateGroup(array $formElement, $elementHtml) {
        $html = $elementHtml;

        return $html;
    }

692
693
694
695
696
    /**
     * @param $formElement
     * @param $elementHtml
     * @return string
     */
697
    public function buildRowSubrecord(array $formElement, $elementHtml) {
698
        $html = '';
699
        $html .= $this->wrapItem(WRAP_SETUP_ELEMENT, $this->wrapItem(WRAP_SETUP_SUBRECORD, $formElement[FE_LABEL]));
700
        $html .= $this->wrapItem(WRAP_SETUP_ELEMENT, $this->wrapItem(WRAP_SETUP_SUBRECORD, $elementHtml));
701
        $html .= $this->wrapItem(WRAP_SETUP_ELEMENT, $this->wrapItem(WRAP_SETUP_SUBRECORD, $formElement[FE_NOTE]));
702
703
704

        return $html;
    }
705
}