REX_LIST-Zeilen datenabhängig formatieren
Einzelne Zellen können per Callback bei der Ausgabe der rex_list angepasst und bedingt formatiert werden. Aber die ganze Zeile anders einfärben wenn bestimmte Werte im Datensatz stehen? Geht? Geht! Auch für YForm.
Grundannahmen und Beispiel
Die rex_list wird wie üblich als Tabelle (<table>) erstellt; die Spalten werden als <td> formatiert.
In der Beispieltabelle gibt es ein Feld privacy
mit den Werten public
, internal
und
classified
. Die Zeilen sollen entsprechend der Klassifizierung eingefärbt werden (standard, beige
und rot).
Für die Beschriebenen Verfahren gilt, dass sie entweder in der Listendefinition (sofern man sie selbst vornimmt) eingebaut werden oder in an anderer Stelle generierten Listen mittels Extension-Point (EP) REX_LIST_GET. Für YForm-Listen, die auf rex_list basieren, solte der EP YFORM_DATA_LIST genutzt werden.
Ab REDAXO 5.12.0
Seit Version REDAXO 5.12.0 verfügt rex_list über eine Methode setRowAttributes, mit der Attribute
auf Zeilenebene (<tr>-Tag) gesetzt werden können. In den Attributen sind Feld-Platzhalter möglich.
So könnte z.B. die Satznummer in den tr-Tag übernommen werden ($list->setRowAttributes(['data-id','###id###']);
).
Alternativ kann eine Callback-Funktion angegeben werden, die Attribute auf Basis der Zeilenwerte erstellt.
Eine Klasse mit Feldwerten zuweisen
Da die Attribute auch Platzhalter für Zeilenwerte enthalten dürfen, kann ein mit $list->setRowAttributes erstelltes class-Attribut zusammengesetzt werden aus einem festen Teil und einem variablen Teil aus dem Datensatz:
$list->setRowAttributes(['class'=>'xyz-privacy-###privacy###']);
tr.xyz-privacy-internal:not(:hover) {background-color: ivory;}
tr.xyz-privacy-classified:not(:hover) {background-color: coral;}
Eine Klasse aus komplexen Abfragen zuweisen
Das obige Beispiel ist nicht immer anwendbar. Was bei einer sehr geringen Anzahl Werten, die zudem statisch sind, noch funktioniert, gerät bei dynamischen Werten schnell an Grenzen.
Alternativ werden die Daten per Callback-Funktion ausgewertet und dann gezielt Attribute gesetzt. Im Beispiel werden veraltete Dokumente (älter als 1 Jahr) orange markiert:
$list->setRowAttributes(function($list){
if( $list->getValue('lastupdate') < time() ) {
return 'class="xyz-oudated"';
}
return '';
});
tr.xyz-oudated:not(:hover) {background-color: orange;}
YForm-Beispiel: den aktuellen Datensatz markieren
Ansatzpunkt ist der EP REX_YFORM_SAVED, der sowohl beim Hinzufügen als auch beim Ändern eines Datensatzes durchlaufen wird. Er stellt über die Parameter die Datensatznummer zur Verfügung.
Damit wird der Folge-EP YFORM_DATA_LIST aktiviert, der der Liste per setRowAttributes eine Callback-Funktion zuweist. Die Funktion prüft, ob die ID des geänderten Datensatzes mit der ID des angezeigten übereinstimmt und entsprechend eine Klasse zuweist oder nicht.
\rex_extension::register('REX_YFORM_SAVED',
function( \rex_extension_point $ep )
{
\rex_extension::register('YFORM_DATA_LIST',
function( \rex_extension_point $ep)
{
if( $ep->getParam('table')->getTablename() == $ep->getParam('__TABLE') ) {
$list = $ep->getSubject();
$id = $ep->getParam('__ID');
$list->setRowAttributes( function($list) use ($id) {
if ($id === $list->getValue('id')) {
return 'class="highlight-last-updated"';
}
});
}
},
rex_extension::NORMAL,
['__TABLE' => $ep->getParam('table'), '__ID' => $ep->getParam('id')]
);
}
);
tr.highlight-last-updated:not(:hover) {background-color: ivory;}
Bis REDAXO 5.11.2
Der ideale Weg, über eine Methode wie setRowAttributes auf der Zeile (<tr class="xyz">
) die
gewünschte Formatierung zu erhalten, fehlt. NAchfolgend wird direkt der komplexe Weg, Listen via EP
zu ändern, beschrieben. Ob man die Callback-Funktion des EP als anonyme Funktion, als benannte
Funktion oder als (statische) Methode eine Klasse anlegt, ist dabei irrelevant. Das Beispiel geht
von einer benannten Funktion aus.
Die Lösung für selbsterstellte Liste läßt sich daraus ableiten.
Für gewünschte Optik muss die Tabellenzelle (<td>) formatiert werden, nicht der Inhalt.
Die dafür zuständigen Angaben sind im columnLayout
der rex_list abrufbar und änderbar. Für
die Änderung ist kein Callback auf eine Custom-Funktion möglich wie beim Zellinhalt (columnFormat
).
Durch Änderung des columnLayout
können gezielt Feldwerte in den <td>-Tag eingeschleust und
darüber CSS-Formatierungen gesteuert werden. Der Klassenname im CSS sollte - sofern die Feldwerte
nicht bereits als Klassenname geeignet sind - aus einem Prefix mit angehängtem Feldwert gebildet
werden: css-prefix-###feldname###
. Als Attribut: attributname="###feldname###"
.
Es reicht aus, der ersten Spalte eine zusätzliche Markierung (Attribut oder Klasse) mitzugeben, anhand derer per CSS die Zeile formatiert wird. Die Formatierung lässt sich im CSS einfach per Siblings-Selektor auf die nachfolgenden Spalten erweitern.
Die Markierung selbst kann nur ein Feldwert der der aktuellen Zeile zugrunde liegenden Abfrage sein.
Die Callback-Funktion ist allgemein gehalten. Das Feld, auf dem die Formatierung beruht (im Beispiel:
privacy
) wird als Parameter im EP übergeben, ebenso der ein CSS-Name, an den der Feldwert als
unterscheidendes Merkmal angefügt wird. (Beispiel: xyz-privacy-
wird zu xyz-privacy-public
,
xyz-privacy-internal
bzw. xyz-privacy-classified
).
Selbstverständlich spricht nichts dagegen, die Funktion spezialisiert zu schreiben.
Um den Parameter von Feldwerten unterscheiden zu können, muss er eindeutig sein und nicht mit Fednamen des Datensatzes in Konflikt geraten. Im Beispiel sind die Werte in einem Array rex_list_row_marker gebündelt.
<td>-Tag um eine Klasse erweitern
Aufruf des EP
Der EP wird mit der Callback-Funktion rex_list_set_row_class aufgerufen. Feldname (privacy
) und
das Prefix des CSS-Klassennamens (xyz-privacy-
) werden als Parameter übergeben. Der EP wird "spät"
aufgerufen - nur für den Fall dass ein anderer EP-Aufruf die erste Spalte löscht.
\rex_extension::register(
'REX_LIST_GET',
'rex_list_set_row_class',
\rex_extension::LATE,
['rex_list_row_marker'=>['field'=>'privacy','name'=>'xyz-privacy-']]
);
Die Callback-Funktion rex_list_set_row_class
function rex_list_set_row_class (\rex_extension_point $ep)
{
// Abruf der Parameter
$row_marker = $ep->getParam('rex_list_row_marker');
if( !is_array($row_marker) ) return;
// Layout der ersten rex_list-Spalte abrufen
$list = $ep->getSubject();
$name = $list->getColumnNames()[0];
$layout = $list->getColumnLayout($name);
// Falls der TD-Tag eine class= hat die Klasse erweitern, sonst class= einfügen
$ok = preg_match( '/(?<td>\<td\s+).*?(class="(?<class>.*?)")*.*?\>/',$layout[1],$match,PREG_OFFSET_CAPTURE );
if( $ok && $match ){
$class = $row_marker['name'] . '###' . $row_marker['field'] . '###';
if( isset($match['class']) ) {
$pos = $match['class'][1]+strlen($match['class'][0]);
$class = ' ' . $class;
} else {
$pos = $match['td'][1]+strlen($match['td'][0]);
$class = 'class="' . $class . '" ';
}
$layout[1] = substr_replace($layout[1],$class,$pos,0);
$list->setColumnLayout($name,$layout);
}
}
Das CSS
Das CSS muss zunächst eine Klasse bestehend aus dem CSS-Prefix (xyz-privacy-
) und den zu
formatierenden Feldwerten aufweisen. Die nachfolgenden Sibling-Klassen erhalten dieselbe
Formatierung.
Problematisch ist der Hover-Effekt. Er ist im allgemeinen CSS auf der Zeile (<tr>-Tag) zugewiesen und wird nun durch die nachgelagerte <td>-Formatierung übrschrieben. Daher muss nach den Formatierungsklassen den <td> eine Hover-Farbe zugewiesen werden.
.xyz-privacy-internal, .xyz-privacy-internal ~ td {background-color: ivory;}
.xyz-privacy-classified, .xyz-privacy-classified ~ td {background-color: coral;}
.table-hover tbody tr:hover td {background-color: #e0f5ee;}
<td>-Tag um ein Attribut erweitern
Aufruf des EP
Der EP wird mit der Callback-Funktion rex_list_set_row_attr aufgerufen. Feldname (privacy
) und
der Attributname (data-privacy
) werden als Parameter übergeben. Der EP wird "spät" aufgerufen -
nur für den Fall dass ein anderer EP-Aufruf die erste Spalte löscht.
\rex_extension::register(
'REX_LIST_GET',
'rex_list_set_row_attr',
\rex_extension::LATE,
['rex_list_row_marker'=>['field'=>'privacy','name'=>'data-privacy']]
);
Die Callback-Funktion rex_list_set_row_attr
function rex_list_set_row_attr (\rex_extension_point $ep)
{
// Abruf der Parameter
$row_marker = $ep->getParam('rex_list_row_marker');
if( !is_array($row_marker) ) return;
// Layout der ersten rex_list-Spalte abrufen
$list = $ep->getSubject();
$name = $list->getColumnNames()[0];
$layout = $list->getColumnLayout($name);
// Attribut als erstes nach <td in den Tag eintragen
$layout[1] = str_replace(
'<td ',
'<td ' . $row_marker['name'] . '="###' . $row_marker['field'] . '###" ',
$layout[1]
);
$list->setColumnLayout($name,$layout);
}
Das CSS
Das CSS muss zunächst eine <td>-Formatierung mittels Attribut-Selektor auf dem zu formatierenden Feldwerten aufweisen. Die nachfolgenden Sibling-Klassen erhalten dieselbe Formatierung.
Problematisch ist der Hover-Effekt. Er ist im allgemeinen CSS auf der Zeile (<tr>-Tag) zugewiesen und wird nun durch die nachgelagerte <td>-Formatierung übrschrieben. Daher muss abschließend den <td> eine Hover-Farbe zugewiesen werden.
td[data-privacy="internal"], td[data-privacy="internal"] ~ td {background-color: ivory;}
td[data-privacy="classified"], td[data-privacy="classified"] ~ td {background-color: coral;}
.table-hover tbody tr:hover td {background-color: #e0f5ee;}
Optionen
YForm
Die Datentabellen in YForm basieren auf rex_list, damit greift auch der EP REX_LIST_GET. Im Kontext von YForm sollte aber der EP YFORM_DATA_LIST genutzt werden, der zusätzliche Informationen zur Tabelle enthält. Über den Tabellennamen kann im EP geprüft werden, ob die beabsichtigte rex_list geändert wird.
Komplexe Basisdaten
Wie beschrieben sind nur Formatierungen auf Feldwerte möglich. Komplexe Kriterien (größer, kleiner, Wertebereiche etc.) sind mit dem beschriebenen Mechanismus nicht abdeckbar, da immer nur ein konkreter Feldwert herangezogen werden kann.
Dennoch sind auch komplexe Auswertungen möglich, wenn es gelingt der Tabelle ein entsprechendes berechnetes Feld einzumischen. Dazu muss die SQL-Abfrage um ein berechnetes Feld erweitert werden.
$query = "SELECT *, IF(entfernung<50,'0',IF(entfernung>100,'3','1')) as tdcolor FROM demo";
\rex_extension::register(
'REX_LIST_GET',
'rex_list_set_row_class',
\rex_extension::LATE,
['rex_list_row_marker'=>['field'=>'tdcolor','name'=>'xyz-distanz-']]
);
$list = rex_list::factory( $query );
Für YForm-Tabellen gibt es den EP YFORM_DATA_LIST_SQL, über den die SQL-Abfrage verändert werden. Hier ein komplexes Beispiel kann.
rex_extension::register('YFORM_DATA_LIST_SQL', function( $ep ){
switch( substr($ep->getParams()['table']['table_name'],strlen(rex::getTablePrefix())) ) {
case 'tabelle_1':
# Liste umbauen
rex_extension::register('YFORM_DATA_LIST', function( $ep ) {
$list = $ep->getSubject();
$list->removeColumn('id');
$list->addColumn('Anschrift','###strasse###<br>###land###-###plz### ###ort###',3);
$list->removeColumn(rex_i18n::msg('yform_delete'));
$list->setColumnFormat('bild','custom', function( $params ) {
if (!$params["value"]) return '';
if( !is_file( rex_path::media($params["value"])) ) return '';
return '<img src="'.rex_url::backendController( ['rex_media_type'=>'rex_mediapool_preview','rex_media_file'=>$params["value"]] ).'" />';
});
return $list;
});
# Abfrage umbauen
return str_replace (',`name`',',concat (`name`,", ",`vorname`) as `name`,',$ep->getSubject());
break;
case 'tabelle_2':
break;
}
});