tricks
tricks copied to clipboard
YForm Trick: Formular Tabs und Spalten-Layouts
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.
Ein eigenes (Wrapper-)Feld, dass sich wie das Fieldset verhält wäre ebenfalls eine Lösung
Ich schmeiß dass mal raus, weil nun weiter unten die aktuelle YF4-Version steht.
Na also, geht doch 🥳
Wäre auch was für https://github.com/alxndr-w/yform_fields gewesen
Ach, das lebt noch?
Aktueller Zustand: 🧟♂️ Nicht tot, aber auch nicht lebendig.
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
Wäre auch was für https://github.com/alxndr-w/yform_fields gewesen
Erledigt. Im Addon mit etwas mehr Funktionalität (CSS, Validierung, ...)
Ich würde gerne noch was für Bootstrap-Spalten entwickeln, aber das heißt nicht, dass das hier offen bleiben muss.