[5529 Aufrufe]

2.5 Eine Togglefunktion für eine eigene Liste

Im letzten Abschnitt haben wir einen Button für die Liste im Backend erstellt. Wenn man sich nun überlegt, was eine Toogle-Funktion ist, kommt man zu dem Schluss, dass es ein Button ist, der nicht einfach einen Wert einträgt, sondern abhängig vom Zustand hin und her schaltet. Der Status wird dann durch das Aussehen des Buttons angezeigt. Wir müssen also nur das Aussehen des Buttons anhand des Status anpassen.

(Bitte auch in diesem Artikel wieder den Vendor-Namespace (oder entsprechenden Ordner) durch Euren eignen ersetzen und nicht Ctocb verwenden! Danke!)

Mathis Völkert hat mich darauf hingewiesen, dass man diese Funktion auch einfacher umsetzen kann. Ich denke, es ist trotzdem ein gutes Beispiel und lasse es so stehen. Seine Lösung findet Ihr aber am Ende des Beitrags.

Erweiterung des DCA

Hier das entsprechende DCA (/src/Ctocb/Example/Resources/contao/dca/tl_testtable.php):

<?php declare(strict_types=1);

$table = 'tl_testtable';

$GLOBALS['TL_DCA'][$table] = [
    // Config
    'config'      => [
        // ...
    ],
    // List
    'list'        => [
        'sorting'           => [
           // ...
        ],
        'label'             => [
            // ...
        ],
        'global_operations' => [
            // ...
        ],
        'operations'        => [
            'edit' => [
                'label'      => &$GLOBALS['TL_LANG'][$table]['edit'],
                'href'       => 'act=edit',
                'icon'       => 'edit.svg'
            ],
            'copy' => [
                'label'      => &$GLOBALS['TL_LANG'][$table]['copy'],
                'href'       => 'act=copy',
                'icon'       => 'copy.svg'
            ],
            'delete' => [
                'label'      => &$GLOBALS['TL_LANG'][$table]['delete'],
                'href'       => 'act=delete',
                'icon'       => 'delete.svg',
                'attributes' => 'onclick="if(!confirm(\'' . $GLOBALS['TL_LANG']['MSC']['deleteConfirm'] . '\'))return false;Backend.getScrollOffset()"'
            ],
            'show' => [
                'label'      => &$GLOBALS['TL_LANG'][$table]['show'],
                'href'       => 'act=show',
                'icon'       => 'show.svg'
            ],
            'toggle' => [
                'label'             => &$GLOBALS['TL_LANG'][$table]['toggle'],
                'icon'              => 'visible.svg',
                'attributes'        => 'onclick="Backend.getScrollOffset();return AjaxRequest.toggleVisibility(this,%s)"',
                'button_callback'   => [\Ctocb\Example\Classes\Contao\Operations\TlTesttable::class, 'toggleIcon'],
                'showInHeader'      => true
            ]
        ]
    ],
    // Palettes
    'palettes'    => [
        'default'      => '{title_legend},title;{publish_legend},published;'
    ],
    // Fields
    'fields'      => [
        'id'     => [
            'sql' => 'int(10) unsigned NOT NULL auto_increment'
        ],
        'tstamp' => [
            'sql' => "int(10) unsigned NOT NULL default '0'"
        ],
        'title'  => [
            'label'     => &$GLOBALS['TL_LANG'][$table]['title'],
            'exclude'   => true,
            'inputType' => 'text',
            'eval'      => ['mandatory' => true, 'maxlength' => 255, 'tl_class' => 'w50'],
            'sql'       => "varchar(255) NOT NULL default ''"
        ],
        'content' => [
            'label'                 => &$GLOBALS['TL_LANG'][$table]['content'],
            'exclude'               => true,
            'inputType'             => 'textarea',
            'eval'                  => ['mandatory'=>true, 'tl_class' => 'clr long', 'rte'=>'tinyMCE'],
            'sql'                   => "text NULL"
        ],
        'published'  => [
            'label'     => &$GLOBALS['TL_LANG'][$table]['published'],
            'exclude'   => true,
            'inputType' => 'checkbox',
            'eval'      => ['doNotCopy'=>true],
            'sql'       => "char(1) NOT NULL default ''"
        ]
    ]
];

Neu ist hier das Feld published, die Aktion wurde von resettime in toggle umbenannt und etwas angepasst. Sie ruft nun ein Ajax-Request auf und hat kein key-Attribut mehr. Es gibt jetzt aber einen button_callback, der das Aussehen des Buttons anpasst.

Sprachdatei

Wir ergänzen die Datei /src/Ctocb/Example/Resources/contao/languages/de/tl_testtable.php, bis sie so aussieht:

<?php declare(strict_types=1);

$table = 'tl_testtable';

// Fields
$GLOBALS['TL_LANG'][$table]['title']            = ['Titel', 'Bitte geben Sie den Titel ein.'];
$GLOBALS['TL_LANG'][$table]['content']          = ['Inhalt', 'Bitte geben Sie den Inhalt ein.'];
$GLOBALS['TL_LANG'][$table]['published']        = ['Veröffentlichen', 'Daten veröffentlichen']; // neu

// Legends
$GLOBALS['TL_LANG'][$table]['title_legend']     = 'Titel';
$GLOBALS['TL_LANG'][$table]['publish_legend']   = 'Veröffentlichen'; // neu

// Operations
$GLOBALS['TL_LANG'][$table]['toggle']           = 'Toggle';

// Buttons
// ...

Operation anlegen

Die Klasse der Operation ruf hier nur eine Hilfsklasse (/src/Ctocb/Example/Classes/Contao/Operations/TlTesttable) auf:

<?php declare(strict_types=1);

namespace Ctocb\Example\Classes\Contao\Operations;

use Ctocb\Example\Classes\Helper\ToggleHelper;

class TlTesttable
{

    private $table = 'tl_testtable';

    public function toggleIcon($row, $href, $label, $title, $icon, $attributes): string {
        $helper = new ToggleHelper($this->table);
        return $helper->toggleIcon($row, $href, $label, $title, $icon, $attributes);
    }
}

Bei jedem Ajax-Request wird die Funktion toggleIcon aufgerufen. Diese ruft ihrerseits in Zeile 14 eine Hilfklasse auf. Ich habe die eigentliche Logik ausgelagert, da man sie häufig an mehreren Stellen in einem Projekt verwerden will. Außerdem lässt sich die Hilfsklasse leichter adaptieren und für eigene Zwecke erweitern.

(Normalerweise würde ich die Hilfklasse nicht mit new erstellen, sondern per Dependency Injection oder über eine Factory beziehen. Auf diese Konzepte gehe ich später noch ein. Der Einfachheithalber soll es so aber erst einmal genügen.)

Hilfsklasse

Wir erstellen unsere Hilfsklasse ToggleHelper unter /src/Ctocb/Example/Classes/Helper/ToggleHelper.php.

<?php declare(strict_types=1);

namespace Ctocb\Example\Classes\Helper;

use Contao\BackendUser;
use Contao\Controller;
use Contao\CoreBundle\Exception\AccessDeniedException;
use Contao\Database;
use Contao\DataContainer;
use Contao\Image;
use Contao\Input;
use Contao\StringUtil;
use Contao\Versions;

class ToggleHelper
{

    private $table = '';


    public function __construct(string $table)
    {
        $this->table = $table;
    }


    public function toggleIcon($row, $href, $label, $title, $icon, $attributes): string {
        $tid        = Input::get('tid');
        $state      = Input::get('state') === (string)1;
        $dc         = func_num_args() <= 12 ? null : func_get_arg(12);
        $referer    = Controller::getReferer();
        $title      = StringUtil::specialchars($title);
        $href      .= '&amp;tid=' . $row['id'] . '&amp;state=' . ($row['published'] ? '' : 1);
        $url        = Controller::addToUrl($href);

        if (null !== $tid) {
            $this->toggleVisibility((int)$tid, $state, $dc); // Ändern der Sichtbarkeit
            Controller::redirect($referer);
        }

        if (!$row['published']) {
            $icon = 'invisible.svg';
        }

        $img = Image::getHtml($icon, $label, 'data-state="' . ($row['published'] ? 1 : 0) . '"');

        return '<a href="' . $url . '" title="' . $title . '"' . $attributes . '>' . $img . '</a> ';
    }


    private function toggleVisibility(int $intId, bool $blnVisible, DataContainer $dc=null): void
    {
        Input::setGet('id', $intId);
        Input::setGet('act', 'toggle');

        if (null !== $dc) {
            $dc->id = $intId;
        }

        // Check the field access
        $user = BackendUser::getInstance();

        if (false === $user->hasAccess($this->table . '::published', 'alexf')) {
            throw new AccessDeniedException('Not enough permissions to publish/unpublish data ID "' . $intId . '".');
        }

        $db = Database::getInstance();
        $objRow = $db->prepare('SELECT * FROM ' . $this->table . ' WHERE id=?')->limit(1)->execute($intId);

        if ($objRow->numRows < 1) {
            throw new AccessDeniedException('Invalid Id "' . $intId . '".');
        }

        if (null !== $dc) {
            $dc->activeRecord = $objRow;
        }

        $objVersions = new Versions($this->table, $intId);
        $objVersions->initialize();

        $time       = time();
        $published  = ($blnVisible ? '1' : '');
        $qury       = "UPDATE " . $this->table . " SET tstamp=?, published='" . $published . "' WHERE id=?";
        $db->prepare($qury)->execute($time, $intId);

        if ($dc) {
            $dc->activeRecord->tstamp       = $time;
            $dc->activeRecord->published    = $published;
        }

        $objVersions->create();

        if (null !== $dc) {
            $dc->invalidateCacheTags();
        }
    }
}

Bei jedem Ajax-Request wird von der Klasse mit unserer Operation die Funktion toggleIcon aufgerufen. Diese ruft ihrerseits in Zeile 37 die Funktion toggleVisibility auf. toggleIcon ist für die Anzeige des Icons da und entscheidet, welches Icon angezeigt wird. toggleVisibility kümmert sich um das Ändern des Werts in der Datenbank.

Es werden hier noch einige Abläufe von Contao erledigt, wie das Erstellen einer neuen Version. Diese Dinge werde ich hier erst einmal vernachlässigen. Ich wollte sie aber wenigstens zeigen und eine gute Klasse für die Verwendung in eingenen Projekten zur Verfügung stellen.

Wichtig ist die Erstellung des Icons in den Zeilen 41 - 47 und das Ändern des Werts in der Datenbank in den Zeilen 81 - 84. Die Hilfsklasse kann so in eingenen Projekten mit beliebigen Tabellen verwendet werden.

Fertig

Zum Abschluss müssen wir noch den Cache leeren und die Datenbank aktualisieren.

Unser Toogle-Icon sieht dann so aus:

Toogle-Icon

Ergänzung

Mittlerweile ist es möglich, die Toogle-Funktion einfach im DCA zu konfigurieren. Dies funktioniert, wenn man die Standard-Icons verwendet und keine zusätzliche Funktionalität benötigt.

Man kann einfach den oben gezeigten Eintrag unter operations durch folgende Zeilen ersetzen. Der Rest wird dann nicht benötigt, da Contao alles übernimmt.

<?php declare(strict_types=1);

$table = 'tl_testtable';

$GLOBALS['TL_DCA'][$table] = [
    // Config
    'config'      => [
        // ...
    ],
    // List
    'list'        => [
        'sorting'           => [
           // ...
        ],
        'label'             => [
            // ...
        ],
        'global_operations' => [
            // ...
        ],
        'operations'        => [
            // ...
            'toggle' => [
                'href'                => 'act=toggle&field=published',
                'icon'                => 'published.svg',
            ],
        ]
    ],
    // ...

Danke an Mathis Völkert für den Hinweis.