Templates im Template

In den meisten Shops sehen alle Produkt-Auflistungen weitestgehend gleich aus. Die “Neuen Artikel” unterscheiden sich selten groß von den “Angeboten”, den normalen Listen in einer Kategorie.

Ja. Auch das, was in der “also_purchased.html”, “cross_selling.html” und in der “reverse_cross_selling.html” innerhalb der ForEach-Schleife passiert, ist vom HTML-Quellcode her oftmals meistens so gut wie identisch.

Und “noch einer” vorweg: Smarty rocks … Ist einfach so …!


Das Problem:

Wenn eh alles gleich (oder fast gleich) aussehen soll - Wie ärgerlich, wenn man mal etwas ändern muss. Kommt regelmäßig vor. Auch wenn man sein HTML-Grundlayout wirklich gut durchgedacht hat:

Katastrophen sind allgegenwärtig: Der Opera zickt rum (Sorry, IE-Hasser, bei mir machen eigentlich nur Opera und NS Schwierigkeiten) und man braucht ein zusätzliches DIV, das FSK-Icon soll woanders hin, tralala-hier, tralala-da.

Oder irgendwer will irgendwas (nach Möglichkeit überall gleichzeitig) bis gestern vor zwei Wochen gaaaanz anders haben als es noch vorgestern vor drei Wochen abgesprochen war. Kommt das irgendwem bekannt vor? Ja?


Jibbie!

Dann darf man einen guten A***h voll Dateien nachkorrigieren, was nicht nur wirklich nervt, sondern vor allem auch fehlerträchtig ist.

Vertipper sind da keine “Rechtschreibfehler” mehr, sondern jeder Vertipper ist im Regelfall irgendetwas zwischen “wird nicht mehr angezeigt” bis hin zu “komplett kaputt”.

Want 2 go to RIU?


Don’t panic!

Wie schön wäre es doch, wenn man in solchen Fällen nur eine einzige Datei zu ändern bräuchte … Genau das kriegt man hin, in dem man aus den “gleichbleibenden” Teilen quasi “Templates im Template” macht.

Ab dafür. Gehen wir mal davon aus, dass all unsere Produktlisten wirklich allesamt ein-und-dasselbe Layout haben sollen.

Die ForEach-Schleifen in allen “Listing-Dateien” könnten dann (hier: Zwei-Spalten-Layout, nach dem jeweils zweiten DIV in einer Zeile wird umgebrochen) in etwa so aussehen:


{php} $col=0; {/php}
{foreach name=aussen item=module_data from=$module_content}
{php} $col++; {/php}

<div>
<h4><a href="{$module_data.PRODUCTS_LINK}">{$module_data.PRODUCTS_NAME}</a></h4>
{if $module_data.PRODUCTS_FSK18=='true'}<p><img src="{$tpl_path}img/fsk18.gif" alt="FSK" /></p>{/if}
{if $module_data.PRODUCTS_IMAGE!=''}<p><a href="{$module_data.PRODUCTS_LINK}"><img src="{$module_data.PRODUCTS_IMAGE}" alt="{$module_data.PRODUCTS_NAME}" /></a></p>{/if}
{$module_data.PRODUCTS_SHORT_DESCRIPTION}
<p><strong>{$module_data.PRODUCTS_PRICE} </strong><br />
{$module_data.PRODUCTS_TAX_INFO}{$module_data.PRODUCTS_SHIPPING_LINK}
{if $module_data.PRODUCTS_VPE} <br />{$module_data.PRODUCTS_VPE}{/if}
{if $module_data.PRODUCTS_SHIPPING_NAME} <br />
{#text_shippingtime#}{if $module_data.PRODUCTS_SHIPPING_IMAGE}
<img src="{$module_data.PRODUCTS_SHIPPING_IMAGE}" alt="{$module_data.PRODUCTS_SHIPPING_NAME}" />{/if}
{$module_data.PRODUCTS_SHIPPING_NAME}{/if}</p>
{$module_data.PRODUCTS_BUTTON_BUY_NOW}
</div>

{php} if ($col>=2) { $col=0; {/php}
<br style="clear:both" />
{php} } {/php}

{/foreach}
	

Meistens sieht’s sogar noch viel komplizierter aus, ich hab die “Gestaltung” hier auf das Nötigste reduziert.

Aber auch wenn’s zwanzig Mal so viel Code wäre - den “Fummelkram” innerhalb der Schleife brauchen wir bald nicht mehr - bzw. brauchen wir den nur noch einmal.

In Zahlen: 1 x

Denn wir basteln uns einen “Schnippsel”!
Einen.

 

“Snippets”

Ziel soll sein, das was sich ohnehin an x Stellen wiederholt, in eine eigene Datei auszulagern, auf die dann alle Produktlisten zurückgreifen können. Und sollte man dort mal etwas ändern müssen, ändern sich alle Produktlisten automatisch mit.


Schritt 1)

Zunächst einmal legen wir einen Ordner namens “snippets” im Template an. Ist nur ein Benennungs-Vorschlag, ich persönlich habe meine “Mini-Templates” jedenfalls alle so organisiert …

In diesem Ordner erstellen wir eine HTML-Datei namens “schnippsel1.html” mit folgendem Inhalt:


<div>
<h4><a href="{$product.PRODUCTS_LINK}">{$product.PRODUCTS_NAME}</a></h4>
{if $product.PRODUCTS_FSK18=='true'}<p><img src="{$tpl_path}img/fsk18.gif" alt="FSK" /></p>{/if}
{if $product.PRODUCTS_IMAGE!=''}<p><a href="{$product.PRODUCTS_LINK}"><img src="{$product.PRODUCTS_IMAGE}" alt="{$product.PRODUCTS_NAME}" /></a></p>{/if}
{$product.PRODUCTS_SHORT_DESCRIPTION}
<p><strong>{$product.PRODUCTS_PRICE} </strong><br />
{$product.PRODUCTS_TAX_INFO}{$product.PRODUCTS_SHIPPING_LINK}
{if $product.PRODUCTS_VPE} <br />{$product.PRODUCTS_VPE}{/if}
{if $product.PRODUCTS_SHIPPING_NAME} <br />
{#text_shippingtime#}{if $product.PRODUCTS_SHIPPING_IMAGE}
<img src="{$product.PRODUCTS_SHIPPING_IMAGE}" alt="{$product.PRODUCTS_SHIPPING_NAME}" />{/if}
{$product.PRODUCTS_SHIPPING_NAME}{/if}</p>
{$product.PRODUCTS_BUTTON_BUY_NOW}
</div>
	

Nur: Wo ist jetzt $module_data hin? Tja: Das haben wir kurzerhand ausgetauscht.
Smarty nutzen, selbst Namen ausdenken. Geht alles. Dazu ist Smarty da …

:-)

Den Bezeichner haben wir geändert, damit wir dieselbe Datei auch für die Auflistungen benutzen können, die nicht mit $module_data sondern mit $products_data arbeiten (Beispiel: “cross_selling.html”) … Und außerdem wollen wir ja weniger tippen.

Wenn schon, denn schon!


Schritt 2)

Nun müssen wir unserem Template noch “beibringen”, wo unser “schnippsel1.html” zu finden ist. Sonst gibt’s nachher Fehlermeldungen …

Hierfür gibt es zwar durchaus andere Möglichkeiten, der Einfachheit halber nehmen wir jedoch erst einmal den Weg, in der “boxes.php” des Templates eine Konstante mit dem Serverpfad zu “schnippsel1.html” zu definieren.

Irgendwo dort (beinahe egal wo) ist also Folgendes reinzuschreiben:


define('schnippsel1',DIR_FS_CATALOG.'templates/'.CURRENT_TEMPLATE. '/snippets/schnippsel1.html');
	

Und damit ist via {$smarty.const.schnippsel1} unsere neue “Schnippsel”-Konstante im Temlate zugänglich.


Schritt 3)

Fertig: Jetzt beginnt der spaßige Teil, denn alle Produkt-Listen, die vorher ausgesehen haben, wie im ersten Smarty-Beispiel, werden plötzlich kürzer. Deutlich kürzer.

Wir müssen die Schleife allerdings nunmehr mit item=product und nicht wie üblich mit item=module_data bilden, denn in der neuen “schnippsel1.html” haben wir die Benennung ja etwas geändert.

Ausgehend von einer zweispaltigen Auflistung sieht das dann in etwa so aus:

{php} $col=0; {/php}
{foreach item=product from=$module_content}
{php} $col++; {/php}
{include file=$smarty.const.schnippsel1}
{php} if ($col>=2) { $col=0; {/php}
<br style="clear:both" />
{php} } {/php}

{/foreach}

Das ist doch schon mal schön schlank, oder?

Tipp: Wer’s noch ein bisschen kürzer mag, der installiert sich die Block-Funktion » ListingVars - und kann sich auf die Weise zudem auch noch den mitlaufenden “Zähler” sparen.

{ListingVars count=2 from=$module_content item=outer}
{foreach item=product from=$outer}
{include file=$smarty.const.schnippsel1}
{/foreach}
<br style="clear:both" />
{/ListingVars}


Das Tolle daran:
Dieser Code kann (theoretisch) in wirklich jeder Produkt-Auflistung genau so benutzt werden. Egal, ob’s nun die “specials.html”, die “new_products_overview.html” oder die “product_listing_v1.html” ist.

» ListingVars dürfte insbesondere für uns CSS-Designer interessant sein: Geschichten wie “Das erste Produkt einer Zeile bekommt ‘Eigenschaft Links’, alle anderen ‘Eigenschaft Mitte’, das letzte ‘Eigenschaft Rechts’” sind kein Problem mehr.

Besondere Styles für die erste, die letzte oder irgendeine-dazwischen-Zeile sind damit ebenfalls machbar. Ausdrückliche Einladung zum Herumprobieren!

Aber nun zurück zum Thema: Die Schnippsel - Die müssen ihre Daten ja irgendwo her kriegen. Tun sie auch. Nur Who-is-Who?

 

“Schönheitsfehler”

Wie immer: Natürlich gibt es einen Haken. Die Template-Tags sind nicht überall dieselben. Diese Art von Ärger kennt jeder, der irgendwann schon einmal mehr oder weniger “blindlings” irgendwelche Bereiche von Datei A nach Datei B gecopy-pasted hat … Es funkioniert manchmal einfach nicht, auch wenn’s genau gleich aussehen soll.


Namen sind Schall und Rauch

Ärgerlicherweise kann man sich nicht hundertprozentig darauf verlassen, dass ein normaler PRODUCTS_BUTTON_BUY_NOW nicht hier und dort mal vielleicht nur BUTTON_BUY_NOW heißt. Hoppla?!

Und manchmal wird der Artikel-Kurztext in PRODUCTS_DESCRIPTION statt (wie sonst überall) in PRODUCTS_SHORT_DESCRIPTION abgelegt. Nanü?!

Zwar hat sich mal jemand Gedanken gemacht - Es gibt es ein $product-Objekt und auch ein $product->buildDataArray - Die Grundidee haut hin.

Nur nutzt sie herzlich wenig für uns Template-Bastler, wenn sie nicht in jeder Produkt-Auflistung eingesetzt wird. Und das wird sie nicht: Vermutlich noch Relikte aus älteren Zeiten. Aus Zeiten, in denn es mal eine SP2.2 geben sollte?

Wie dem auch sei: Einige oft gebrauchte Dateien sind offensichtlich und nicht nur scheinbar schlicht vergessen worden.

Doof. Das bedeutet unnötige Arbeit.


Entweder System-Gebastel …

Da hilft alles nichts - man muss etliche Systemdateien nachbearbeiten. Schadet xt:C sicherlich nicht, ist nur viel Arbeit … Dabei guckt man am besten ein bisschen in den Dateien ab, die buildDataArray benutzen.

Einfach mal die “specials.php” mit der “products_new.php” vergleichen - Und siehe da, in der “products_new.php” könnte man gut 50 Zeilen Code sparen - Und hat dann auch keinen Ärger mit “komischen Template-Tags” mehr.

Ich persönlich versuche immer, System-Änderungen so weit es geht zu vermeiden. Selbst wenn es manchmal regelrecht danach schreit.

Bloß: Wer schon einmal irgendein Modul installiert hat, in dessen Installations-Anleitung Anweisungen stehen à la “In blabla.php nach ’sowieso’ suchen und durch ‘dings’ ersetzen” - oder (noch besser) “Die Zeilen 123 bis 456 durch lalala austauschen” - Der kann sich das Gesuche vorstellen, wenn man schon lauter System-Dateien verbessert hat.

Das macht sehr wenig Spaß.


Oder eine andere Variante …

… habe ich in meinem » Testshop im Einsatz. Diese ist aber wirklich nur dann zu empfehlen, wenn man Smarty-Caching aktiviert hat, da es sonst viel zu viele Datenbank-Abfragen gibt.

Das Prinzip beruht auf einer Smarty-Funktion, die einfach ein neues $product-Objekt erzeugt, buildDataArray damit ausführt und mit den Ergebnissen die alten Smartys überschreibt.

In meinem Fall wird auch noch so einiges mehr angestellt, Herstellername und Logo werden abgefragt, die Tags für Mehrfachbestellformulare erzeugt, Warenbestand abgefragt … Ohne Caching wäre das ne ziemliche Belastung.

WARNUNG: Was man sich mit Smarty-Caching (dank schlampiger Integration in xt:Commerce) so alles kaputt machen kann und wie man das repariert - Das ist Stoff für einen weiteren Beitrag. Bei Interesse: Mal bei » Ecombase vorbeigucken. Ein Teil des Caching-Dilemmas wird da deutlich.

Es gibt jedoch noch weitere Ungereimtheiten. Zum Beispiel kann man bei aktiviertem Smarty-Cache bei den Bewertungen nicht mehr blättern, die angezeigten Preise “stimmen” nur noch in Ausnahmefällen mit dem überein, was schlussendlich im Warenkorb landet - Das Thema ist mit Vorsicht zu genießen, obwohl Smarty-Caching eine echt feine Sache sein könnte.

ALSO: Kommen Sie bitte nicht auf die Idee, Smarty-Caching anzuschalten, wenn Sie Staffelpreise, verschiedene Währungen oder bloß nur verschiedene Sprachen in Ihrem Shop zur Auswahl haben. Auch unterschiedliche Besteuerungen nach Rechnungs-Adresse können ein ernstes Problem werden. Da ist seitens xt:C wirklich böse gepfuscht worden.

Aber wie gesagt - Das ist Stoff für einen eigenen Beitrag …

 

Fazit:

Ich muss ja zugeben: Ich hab Smarty zuerst überhaupt nicht gemocht. Ich habe tatsächlich gedacht, es sei einfacher, irgendwie PHP in die Templates mit reinzuzwängen. Aber inzwischen durfte ich etliche Male feststellen, was für eine tolle Template-Engine Smarty eigentlich ist.


Umwege:

Sicher ist es nicht jedermanns Sache, für jeden “Killefitz” einen Modifier, eine Function, einen Insert oder was-auch-immer zu schreiben.

Zudem muss man sich vom echo $lalala verabschieden, und sich eher darüber Gedanken machen, ob man seine Daten in einzelnen Variablen oder in einem Array sammelt. Kommt immer drauf an, was man schließlich damit anstellen möchte.

Aber wenn man erst einmal ein bisschen “den Bogen raus” und einen Satz von Erweiterungen für “sein” Content-Management-System zusammengeschrieben hat, wird man nicht mehr darauf verzichten wollen.

Die “Snippets-Idee” wäre in “nacktem PHP” jedenfalls schon mal deutlich komplizierter geworden.


Und mit Smarty geht noch mehr …

… zum Beispiel “Preise als Grafiken”, wie auf » ZAPATERIA oder die Headline-Schriften (vermutlich auch nicht auf jedem Rechner zu finden) bei » Rb-HairCompany - Es wäre ziemlich sicher ein mittlerer Aufstand. Da lohnt es sich schon, sich mal ein bisschen hinzusetzen und eigene Modifier zu schreiben.

Konkret diese Modifier war allerdings ein etwas-mehr-als-mittlerer Aufstand, da will ich nicht lügen. Für die Templates hat sich die Aktion jedoch gelohnt …

Im Template sieht’s nachher nämlich nur noch so aus: {$product.PRODUCTS_PRICE|imgReplace} - Man stelle sich das in PHP vor … Klar geht das. Aber wäre es dann noch genauso übersichtlich? Auch für Leute, die um halb zwei nicht mehr bis drei zählen können?

Richtig. Der imgReplace-Modifier ist ebenfalls wieder Stoff für einen eigenen Beitrag, nur noch nicht wirklich “serienreif”. Mol kieken … Kommt Zeit, kommt Rat.