tricks icon indicating copy to clipboard operation
tricks copied to clipboard

YForm Trick: Formular Tabs und Spalten-Layouts

Open AWqxKAWERbXo opened this issue 4 years ago • 6 comments

Basierend auf https://github.com/yakamara/redaxo_yform/issues/178 - es gibt verschiedene Ansätze dazu, wie man mit Bootstrap und/oder JS im Backend Tabs ermöglicht. Die Kombination mit Fieldsets wäre dabei besonders interessant.

Auch das einfügen von html-Feldern mit Boostrap-Spalten ist etwas, das noch nirgends erklärt zu sein scheint.

AWqxKAWERbXo avatar May 03 '21 16:05 AWqxKAWERbXo

Ein eigenes (Wrapper-)Feld, dass sich wie das Fieldset verhält wäre ebenfalls eine Lösung

skerbis avatar May 04 '21 12:05 skerbis

Ich schmeiß dass mal raus, weil nun weiter unten die aktuelle YF4-Version steht.

christophboecker avatar May 04 '21 13:05 christophboecker

Na also, geht doch 🥳

skerbis avatar May 04 '21 13:05 skerbis

Wäre auch was für https://github.com/alxndr-w/yform_fields gewesen

AWqxKAWERbXo avatar May 04 '21 13:05 AWqxKAWERbXo

Ach, das lebt noch?

christophboecker avatar May 04 '21 13:05 christophboecker

Aktueller Zustand: 🧟‍♂️ Nicht tot, aber auch nicht lebendig.

AWqxKAWERbXo avatar May 04 '21 14:05 AWqxKAWERbXo

Update zum Post vom 04. Mai 2021 mit meinem aktuellen Code für YF4:

Das Yform-Value rex_yform_value_tabs

/**
 * Tabs in YForm-Formularen.
 *
 * Es müssen mindestens drei Tab-Values derselben Gruppe (group_by) im Formular sein:
 *  erster Tab      -> beginnt einen Tab und baut das Tab-Menü über alle auf
 *  innerer Tab     -> jeder innere Tab schließt den vorhergehenden ab und öffnet den eigenen Container 
 *  letzter Tab     -> ohne eigenen Eintrag im Tab-Menü, schließt den vorhergehenden Container und die Gruppe
 *  
 * nur ein Tab-Value im Formular:   es fehlt der schließende Tab. Technisch nicht machbar.
 * nur zwei Tab-Values im Formular: es gibt eh nur einen Tab im Menü. Das ist sinnlos.
 * 
 * Nutzt in rex_yform_field die Felder ...
 *  - group-by: Clusterung als Tab-Gruppe
 *  - default:  Beim Anzeigen ausgewählter Default-Tab (1= Der erste / 2 = der ausgewählte)
 */

class rex_yform_value_tabs extends rex_yform_value_abstract
{

    /**
     * Variablen zur Ablage der Tab-Menü-Struktur
     * @var self[] $tabset
     */
    public array $tabset = [];
    public int $sequence;
    public bool $selected = false;
    public string $hasErrorField = '';

    public string $fragment = 'value.tabs.tpl.php';

    /**
     * sucht die Elemente der eigenen Tabgruppe (group_by) zusammen und markiert 
     * alle Elemnte konsolidiert.
     * Wird nur beim ersten Element der Tabgruppe ausgeführt für alle.
     */
    protected function collectTabElements(): void
    {
        // nur ausführen, wenn noch nicht durch ein anderes Tab derselben Gruppe erledigt
        if( 0 < count($this->tabset) ) {
            return;
        }

        // Alle Tab-Elemente derselben Gruppe ermitteln
        /** @var \rex_yform_value_abstract[] $tabElements  */
        $tabElements = $this->params['values'];
        /** @var self[] $tabElements  */
        $tabElements = array_filter( $tabElements,function($v){
            return is_a($v,self::class) && $v->getElement('group_by') === $this->getElement('group_by');
        });

        // Zu wenig Elemente: dann wird das nix, ignorieren
        if( 3 > count($tabElements) ) {
            return;
        }

        // Der letzte Tab (nur Platzhalter für den Abschluss der Tabgruppe) ist nie aktiv.
        $tabElements[array_key_last($tabElements)]->setElement('default','1');

        // In den Tabs die steuernden Informationen eintragen
        $i = 0;
        $active = -1;
        foreach($tabElements as $id => $tab ) {
            $tab->tabset = $tabElements;
            $tab->sequence = $i++;
            $tab->selected = false;
            if( -1 === $active && '2' === $tab->getElement('default') ) {
                $active = $id;
            };
        }
        $active = -1 === $active ? array_key_first($tabElements) : $active;
        $tabElements[$active]->selected = true;
        // Das letzte Element erhält die Nummer PHP_INT_MAX; 
        $tabElements[array_key_last($tabElements)]->sequence = PHP_INT_MAX;

        // Gibt es Fehlermeldungen in einem Tab-Bereich? Den Tab markieren
        $tabKeys = array_keys($tabElements);
        foreach($this->params['warning'] as $needle=>$errorClass ) {
            $fields = array_filter($tabKeys,function($v) use ($needle) { return $v < $needle;} );
            $errorTab = end($fields);
            if( false !== $errorTab ) {
                $tabElements[$errorTab]->hasErrorField = $errorClass;
            }
        }
    }

    /**
     * Zu diesem Zeitpunkt sind alle Felder existent.
     *
     * @return void
     */
    public function enterObject()
    {
        if (!$this->needsOutput()) {
            return;
        }
        $this->collectTabElements();
        $output = '';

        // Wenn erstes Tab der Gruppe: Menü aufbauen, Tab-Container öffnen
        if (0 === $this->sequence) {
            $output .= $this->parse($this->fragment, ['option' => 'open_tabset', 'tabset' => $this->tabset]);
            $startFeld = true;
        }

        // Wenn nicht Startfeld: vorhergehende Tab-Gruppe schließen
        if (0 < $this->sequence) {
            $output .= $this->parse($this->fragment, ['option' => 'close_tab']);
        }

        // Wenn nicht letzter Eintrag: tab-Gruppe öffnen; der letzte dient ja nur dem Abschluß
        if (PHP_INT_MAX > $this->sequence) {
            $output .= $this->parse($this->fragment, ['option' => 'open_tab']);
        }

        // Wenn letzter Eintrag: Tab-Container schließen
        if (PHP_INT_MAX === $this->sequence) {
            $output .= $this->parse($this->fragment, ['option' => 'close_tabset']);
        }

        $this->params['form_output'][$this->getId()] = $output;
    }

    public function getDescription(): string
    {
        return 'tabs|name|label|aktiv[1,2]|[tabgroup]';
    }

    /**
     * @return array<string,mixed>
     */
    public function getDefinitions(): array
    {
        return [
            'type' => 'value',
            'name' => 'tabs',
            'values' => [
                'name' => [
                    'type' => 'name',
                    'label' => rex_i18n::msg('yform_values_defaults_name'),
                ],
                'label' => [
                    'type' => 'text',
                    'label' => rex_i18n::msg('yform_values_defaults_label'),
                ],
                'default' => [
                    'type' => 'choice',
                    'label' => rex_i18n::msg('yform_values_tabs_active_label'),
                    'choices' => rex_i18n::rawMsg('yform_values_tabs_active_options'),
                    'expanded' => '1',
                    'default' => '1',
                    'notice' => rex_i18n::msg('yform_values_tabs_active_notice'),
                ],
                'group_by' => [
                    'type' => 'text',
                    'label' => rex_i18n::msg('yform_values_tabs_cluster_label'),
                    'notice' => rex_i18n::msg('yform_values_tabs_cluster'),
                ],
            ],
            'description' => rex_i18n::msg('yform_values_tabs_description'),
            'dbtype' => 'none',
            'is_searchable' => false,
            'is_hiddeninlist' => true,
        ];
    }
}

Das YTemplate bootstrap/value.tabs.tpl

/**
 * Template für den privaten YForm-Datentyp "rex_yform_value_tabs".
 */

namespace Project;

use rex_yform_value_tabs;

/**
 * @var rex_yform_value_tabs $this
 * @var string $option
 * @var rex_yform_value_tabs[] $tabset
 */

/**
 * Tabset insgesamt öffnen, also das Menü aufbauen und den Container öffnen
 * Der letzte Tab ($sequence === PHP_INT_MAX) ignorieren, das ist der
 * Platzhalter zum Schließen ohne eigenen Menüeintrag.
 */
if ('open_tabset' === $option) {
    echo '<ul class="nav nav-tabs">',PHP_EOL;
    foreach ($tabset as $tab) {
        if (PHP_INT_MAX !== $tab->sequence) {
            $tabLabel = $tab->getLabel();
            $tabHTMLid = $tab->getHTMLId();
            $class = [];
            if($tab->selected) {
                $class[] = 'active';
            }
            if($tab->hasErrorField) {
                $class[] = $tab->hasErrorField;
                $tabLabel = '<span class="text-danger"><i class="fa fa-warning"></i> ' . $tabLabel . '</span>';
            }
            $class = $class ? ' class="'.implode(' ',$class).'"' : ''; 
            echo '  <li role="presentation"',$class,'><a data-toggle="tab" href="#',$tabHTMLid,'">',$tabLabel,'</a></li>',PHP_EOL;
        }
    }
    echo '</ul>',PHP_EOL;
    echo '<div class="panel panel-default tab-content">',PHP_EOL;
}

/**
 * Schließt den gesamten Tabset.
 */
if ('close_tabset' === $option) {
    echo '</div> <!-- close tab-content -->',PHP_EOL;
}

/**
 * öffnet den Tab.
 */
if ('open_tab' === $option) {
    $isActive = $this->selected ? ' in active' : '';
    $tabHTMLid = $this->getHTMLId();
    echo '<div role="tabpanel" id="',$tabHTMLid,'" class="tab-pane xfade',$isActive,'">',PHP_EOL;
}

/**
 * schließt den Tab.
 */
if ('close_tab' === $option) {
    $tabHTMLid = $this->getHTMLId();
    echo '</div> <!-- close tab (',$tabHTMLid,')-->',PHP_EOL;
}

.lang-Einträge

#
# rex_yform_value_tabs (Tab-Navigation)
#
yform_values_tabs_cluster = Dient der Gruppierung zusammenhängender Tabs in einer Menü-Gruppe (Achtung: keine überlappenden oder verschachtelten Gruppen!)
yform_values_tabs_description = Tab-Navigation einfügen
yform_values_tabs_cluster_label = Tab-Gruppe
yform_values_tabs_active_label = Aktiver Tab
yform_values_tabs_active_notice = 'Aktiv' hat Vorrang vor 'Dynamisch'. Bei mehreren aktiven Tabs der erste genommen.
yform_values_tabs_active_options = Der erste Tab der Gruppe wid aktiviert=1,Dieser Tab wird aktiviert=2

christophboecker avatar Jan 11 '23 14:01 christophboecker

Wäre auch was für https://github.com/alxndr-w/yform_fields gewesen

Erledigt. Im Addon mit etwas mehr Funktionalität (CSS, Validierung, ...)

christophboecker avatar Jan 14 '23 15:01 christophboecker

Ich würde gerne noch was für Bootstrap-Spalten entwickeln, aber das heißt nicht, dass das hier offen bleiben muss.

AWqxKAWERbXo avatar Jan 14 '23 19:01 AWqxKAWERbXo