Contao: Detailseite für eigenen Menüpunkt

von Patrick Froch (Kommentare: 2)

Da wir in den letzten Artikeln eine Produktdatenbank (Contao: Eigenen Backend-Menüpunkt), sowie eine Ausgabe der Daten im Frontend (Contao: Ausgabe für eingenen Menüpunkt) erstellt haben, ist es nun an der Zeit das ganze zu verfeinern. Heute soll aus unserer Ausgabe eine Liste werden. Zusätzlich erstellen wir eine Detailseite für die zusätzlichen Informationen der Produkte. Auch hierfür legen wir wieder ein neues Inhaltselement an. Der aufmerksame Leser wird bemerken, dass sich viele Dinge wiederholen.

Ordnerstruktur

Hier noch einmal die Ordnerstruktur aus dem Artikel "Contao: Ausgabe für eingenen Menüpunkt". Hier hat sich diesmal nichts geändert, da alle Verzeichnisse bereits vorhanden sind.

easy_extension/
├── assets/
│   └── img/
├── classes/
│   └── elements/
├── config/
├── dca/
├── languages/
│   └── de/
└── templates/

Config

In die Datei config/config.php fügen wir nun unser neues Inhaltselement ein.

// config/config.php
$GLOBALS['TL_CTE']['esit']['myproductdetails'] = '\\esit\\easy_extension\\classes\\elements\\ContentProductDetails';

DCA

Hier müssen wir nun zwei Dinge tun. Zum einen müssen wir eine Palette für unser Inhaltselement anlegen. Dies ist zwar nicht nötig aber wir wollen auch in diesem Inhaltselement wieder die Contao-Standard-Felder anzeigen. Zum anderen müssen wir das Inhaltselement für die Liste um eine Weiterleitungsseite erweitern.

// dca/tl_content.php
/**
 * Set Tablename: tl_content
 */
$strName = 'tl_content';

/* Palettes */
$GLOBALS['TL_DCA'][$strName]['palettes']['myproductdetails']    = '{type_legend},type,headline;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID,space;{invisible_legend:hide},invisible,start,stop';
$GLOBALS['TL_DCA'][$strName]['palettes']['myproduct']           = '{type_legend},type,headline;{redirect_legend},jumpTo;{protected_legend:hide},protected;{expert_legend:hide},guests,cssID,space;{invisible_legend:hide},invisible,start,stop';

/* Felder */
$GLOBALS['TL_DCA'][$strName]['fields']['jumpTo'] = array(
    'label'                   => &$GLOBALS['TL_LANG'][$strName]['jumpTo'],
    'exclude'                 => true,
    'inputType'               => 'pageTree',
    'foreignKey'              => 'tl_page.title',
    'eval'                    => array('fieldType'=>'radio'),
    'sql'                     => "int(10) unsigned NOT NULL default '0'",
    'relation'                => array('type'=>'hasOne', 'load'=>'eager')
);

In Zeile 9 wurde nach dem Feld headline folgendes eingefügt: {redirect_legend},jumpTo;. Die Definition des Felds finden wir ab Zeile 12. In Zeile 8 steht nun die neue Palette.

Da wir neue Felder erstellt haben, ist es nun an der Zeit, die Datenbank zu aktualisieren.

Languages

Selbstverständlich benötigen wir auch wieder einen Eintrag in der Sprachdatei für unser Feld. Da es das erste Feld der Tabelle tl_content ist, welches wir anlegen, erstellen wir die Datei languages/de/tl_content.php

// languages/de/tl_content.php
/**
 * Set Tablename
 */
$strName = 'tl_content';

/**
 * Fields
 */
$GLOBALS['TL_LANG'][$strName]['jumpTo'] = array('Weiterleitungsseite', 'Bitte wählen Sie die Weiterleitungsseite aus.');

/**
 * Legends
 */
$GLOBALS['TL_LANG'][$strName]['redirect_legend'] = 'Weiterleitungsseite';

In Zeile 10 befindet sich der Eintrag für das Feld und in Zeile 15 der für die Legende. Da die genauen Zusammenhänge bereits im Artikel "Contao: Ein eigenes Inhaltselement" erläutert wurden, spare ich mir dies hier.

Im Augenblich wird unser neues Inhaltselement im Auswahlfeld "Typ" noch mit dem Array-Key aufegführt. Dies ändern wir, in dem wir die Datei languages/de/default.php um folgenden Eintrag ergänzen:

$GLOBALS['TL_LANG']['CTE']['myproductdetails'] = array('Produktdetails');

Autoload

Da auch für dieses Inhaltselement wieder eine Klasse benötigt wird, wird auch wieder ein Eintrag in der Datei config/autoload.php benötigt. Wir erweitern die Datei also entsprechend und setzen direkt unter die Klasse unseres letzten Inhaltselements die neue Klasse. Weiterhin tragen wir auch ein Templte für die neue Ausgabe ein. Alles zusammen sollte dann so aussehen:

/**
 * Variables
 */
$strFolder      = 'easy_extension';
$strNamespace   = 'esit\\' . $strFolder;

/**
 * Register the namespaces
 */
ClassLoader::addNamespaces(array
(
   $strNamespace
));

/**
 * Register the classes
 */
ClassLoader::addClasses(array
(
    // Elements
    $strNamespace . '\\classes\\elements\\ContentProduct'           => "system/modules/$strFolder/classes/elements/ContentProduct.php",
    $strNamespace . '\\classes\\elements\\ContentProductDetails'    => "system/modules/$strFolder/classes/elements/ContentProductDetails.php"
));

/**
 * Register the templates
 */
TemplateLoader::addFiles(array
(
    'ce_product'           => "system/modules/$strFolder/templates",
    'ce_product_details'   => "system/modules/$strFolder/templates",
));

Die Ausgabeklasse

Wie die Klasse heißt und wo sie sich befinden soll, haben wir ja gerade festgelegt. Wir erstellen nun die entsprechende Datei: classes/elements/ContentProductDetails.php.

namespace esit\easy_extension\classes\elements;

/**
 * Class ContentProductDetails
 * @package easy_extension\classes\elements
 */
class ContentProductDetails extends \ContentElement
{


    /**
     * Template
     * @var string
     */
    protected $strTemplate = 'ce_product_details';


    /**
     * Generate the content element
     */
    protected function compile()
    {
        if (TL_MODE == 'BE') {
            $this->genBeOutput();
        } else {
            $this->genFeOutput();
        }
    }


    /**
     * Erzeugt die Ausgebe für das Backend.
     * @return string
     */
    private function genBeOutput()
    {
        $this->strTemplate          = 'be_wildcard';
        $this->Template             = new \BackendTemplate($this->strTemplate);
        $this->Template->title      = $this->headline;
        $this->Template->wildcard   = "### ContentProductDetails ###";
    }


    /**
     * Erzeugt die Ausgebe für das Frontend.
     * @return string
     */
    private function genFeOutput()
    {
        $intId = \Contao\Input::get('id');

        if ($intId) {
            $this->Template->objProduct = $this->loadProduct($intId);
        }
    }


    /**
     * Lädt alle Einträge aus der Produktdatenbank
     * @return \Database\Result|object
     */
    private function loadProduct($intId)
    {
        $objDb      = \Contao\Database::getInstance();
        $strQuery   = "SELECT * FROM tl_products WHERE id = $intId";
        $objResult  = $objDb->execute($strQuery);
        return $objResult;
    }
}

In Zeile 15 wird das Templat für die Detailseite gesetzt. In Zeile 50 wird mittels \Contao\Input::get('id') die übergebene ID ausgelesen. Ist diese vorhanden, wird in Zeile 54 die Methode für das Laden der Daten aufgerufen und das Ergbenis an das Template übergeben. In Zeile 65 wird mit der ID der entsprechende Datensatz aus der Tabelle tl_products geladen.

Templates

Zunächst erstellen wir das Template für die Detailseite templates/ce_product_details.html5. Es ähnelt dem Template aus dem Artikel "Contao: Ausgabe für eingenen Menüpunkt", nur dass wir hier keine while-Schleife haben, da nur ein Produkt ausgegeben wird.

<div class="<?php echo $this->class; ?> block"<?php echo $this->cssID; ?><?php if ($this->style): ?> style="<?php echo $this->style; ?>"<?php endif; ?>>
    <!-- Überschrift aus dem Inhaltselement-->
    <?php if ($this->headline): ?>
        <><?php echo $this->headline; ?></<?php echo $this->hl; ?>>
    <?php endif; ?>

    <!-- Produktdaten -->
    <div class="product">
        <h2>
            <?php echo $this->objProduct->title; ?>
        </h2>

        <div class="price">
            Preis: <?php echo $this->objProduct->price; ?> €
        </div>

        <div class="description">
            <?php echo $this->objProduct->description; ?>
        </div>
    </div>
</div>
Update 07.10.2016: Es muss natürlich $this->objProduct heißen und nicht $this->objProducts! Ich habe das Template entsprechend geändert. An dieser Stellen noch einmal vielen Dank an Joachim für's aufmerksame Lesen.

Nun müssen wir noch das Template für die Liste überarbeiten: templates/ce_product.html5

<div class="<?php echo $this->class; ?> block"<?php echo $this->cssID; ?><?php if ($this->style): ?> style="<?php echo $this->style; ?>"<?php endif; ?>>
    <!-- Überschrift aus dem Inhaltselement-->
    <?php if ($this->headline): ?>
        <><?php echo $this->headline; ?></<?php echo $this->hl; ?>>
    <?php endif; ?>

    <!-- Produktdaten -->
    <?php while ($this->objProducts->next()): ?>
        <ul class="product">
            <li>
                <a href="<?php echo $this->jumpTo . '?id=' . $this->objProducts->id; ?>">
                    <?php echo $this->objProducts->title; ?>
                </a>
            </li>
        </ul>
    <?php endwhile; ?>
</div>

Link erstellen

Im Template der Liste (templates/ce_product.html5) soll in Zeile 11 der Link ausgegeben werden. Bis jetzt steht dort aber nur die ID der Seite. Um dies zu ändern müssen wir die Ausgabeklasse classes/elements/ContentProduct.php anpassen.

namespace esit\easy_extension\classes\elements;

/**
 * Class ContentProduct
 * @package easy_extension\classes\elements
 */
class ContentProduct extends \ContentElement
{


    /**
     * Template
     * @var string
     */
    protected $strTemplate = 'ce_product';


    /**
     * Compile the content element
     */
    protected function compile()
    {
        if (TL_MODE == 'BE') {
            $this->genBeOutput();
        } else {
            $this->genFeOutput();
        }
    }


    /**
     * Erzeugt die Ausgebe für das Backend.
     * @return string
     */
    private function genBeOutput()
    {
        $this->strTemplate          = 'be_wildcard';
        $this->Template             = new \BackendTemplate($this->strTemplate);
        $this->Template->title      = $this->headline;
        $this->Template->wildcard   = "### ContentProduct ###";
    }


    /**
     * Erzeugt die Ausgebe für das Frontend.
     * @return string
     */
    private function genFeOutput()
    {
        $objProducts = $this->loadProducts();

        if ($objProducts) {
            $this->Template->objProducts    = $objProducts;
            $this->Template->jumpTo         = $this->makeLink($this->jumpTo);
        }
    }


    /**
     * Lädt alle Einträge aus der Produktdatenbank
     * @return \Database\Result|object
     */
    private function loadProducts()
    {
        $objDb      = \Contao\Database::getInstance();
        $strQuery   = "SELECT * FROM tl_products";
        $objResult  = $objDb->execute($strQuery);
        return $objResult;
    }


    /**
     * Erstellt den Link zur Detailseite.
     * @param $intId
     * @return string
     */
    private function makeLink($intId)
    {
        $objPage = \Contao\PageModel::findByPk($intId);

        if ($objPage) {
            return $objPage->alias . '.html';
        }

        return '';
    }

}

In Zeile 54 wird die Methode für die Erstellung des Links aufgerufen und das Ergebnis an das Template übergeben. Der Link wird ab Zeile 79 erstellt. Diese Erstellung ist sehr rudimentär, es wird z.B. nicht berücksichtigt, ob Contao in einem Unterordner installiert ist. Es soll aber zur Demonstration der Funktionsweise erst einmal reichen.

Wenn wir nun die Liste aufrufen, werden uns die Links zur Detailseite präsentiert. Wird einer davon angeklickt, gelangen wir zur Detailseite. Dort stehen uns dann alle Infos des Produkts zur Verfügung.

Update 07.10.2016: Auf Anregung von Joachim habe ich die Linkerzeugung etwas optimiert. Die folgende Methode `makeLink()` ersetzt die vorhandene komplett.
/**
 * Erstellt den Link zur Detailseite.
 * @param $intId
 * @return string
 */
private function makeLink($intId)
{
    $objPage    = \Contao\PageModel::findByPk($intId);
    $url        = '';

    if (!$GLOBALS['TL_CONFIG']['rewriteURL']) {
        $url .= '/index.php'
    }

    if ($objPage) {
        $url .= '/' . $objPage->alias . $GLOBALS['TL_CONFIG']['urlSuffix'];
    }

    return $url;
}

Zurück

Einen Kommentar schreiben

Kommentar von Joachim Scholtysik |

Hallo Patrick,

vielen Dank für diesen Blogeintrag!

Ich habe mal zwei Produkte angelegt, die dann zum Schluss auch als Links zur Produktdetail-Seite angezeigt werden. Leider erhalte ich beim Klick auf beide Links einen 404.

Der Alias der Weiterleitungsseite (produktdetails) und die ID 1 setzen sich beim Link z.B. folgendermaßen zusammen:

http://localhost/contao/produktdetails.html?id=1

Stimmt da etwas in Deinen Templates oder Klassen nicht?

Viele Grüße.

Joachim

Antwort von Patrick Froch

Hallo Joachim,

eigentlich hat es funktioniert, als ich es getestet habe. Es wird ja nur über die Id der jumpTo-Seite der Alias ausgelesen und die Id des Produkts angehängt. Wenn Contao bei Dir nicht in einem Unterordner installiert ist, gehört das /contao vermutlich nicht in den Link. Wo es aber herkommt, kann ich Dir leider nicht sagen.

Viele Grüße,
Patrick

Kommentar von Joachim Scholtysik |

Hi Patrick,

doch, Contao 3.5.17 ist unter Xampp in einem Unterordner Contao installiert. Funktioniert Dein Beispiel nicht mit Unterordnern?

Antwort von Patrick Froch

Hallo Joachim,

doch es funktioniert auch mit Unterordnern. Der Linkaufbau hat mich nur irritiert. Hast Du das Umschreiben der URLs aktiviert. Falls nein, fehlt vermutlich das index.php in Deinem Link. Dies ist heutzutage so selten, dass ich es ausgelassen habe. Ich habe den Artikel etwas erweitert.