Beiträge getaggt mit fluid

Foundation Block-Grid mit Gridelements

Ich mag Foundation, unter anderem weil ich das Block Grid so toll finde. Und so kann man mit Gridelements ein Block-Grid im Frontend (mit einer hübschen Backend-Ausgabe) realisieren.

Zunächst man braucht man ein Gridelement mit nur einer Content-Spalte. Da schmeißt man dann alle Inhaltselemente rein und sie werden automatisch in Spalten ausgegeben. In diesem Fall hab ich das Gridelement in einer Datei konfiguriert.

tx_gridelements.setup.blockgrid {
	title = Blocks
	description = Inhalte automatisch auf mehrere Spalten verteilen
	icon = EXT:my_extension/Resources/Public/Icons/columns-3.png
	flexformDS = FILE:EXT:my_extension/Configuration/FlexForms/Gridelements/Blockgrid.xml
	config {
		colCount = 1
		rowCount = 1
		rows {
			1 {
				columns {
					1 {
						name = Inhalt
						colPos = 55
					}
				}
			}
		}
	}
}

Im Flexform habe ich zwei Felder konfiguriert – large, medium und small. Darin gibt man die Anzahl der Elemente für die jeweilige Auflösung an. Default sind es 3 Large, 2 Medium und 1 Small.

Die Ausgabe erfolgt durch ein Fluid-Template. Wie die Konfiguration dazu aussieht, findet man schnell im Internet – einfach nach „gridelements fluid“ suchen. Damit ich keinen eigenen ViewHelper schreiben muss, um auf variable Array-Indizes zuzugreifen, habe ich Vhs verwendet. Jedes Content-Element aus der Spalte wird in <div class="column"></div> gewrappt.

{namespace v=FluidTYPO3\Vhs\ViewHelpers}
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
      xmlns:ce="http://typo3.org/ns/TYPO3/CMS/FluidStyledContent/ViewHelpers" data-namespace-typo3-fluid="true">
<div class="row small-up-{data.flexform_large} medium-up-{data.flexform_medium} large-up-{data.flexform_large}">
    <f:for each="{data.tx_gridelements_view_children}" as="content">
        <div class="column">
            <f:format.raw>
                <v:variable.get name="data.tx_gridelements_view_child_{content.uid}" />
            </f:format.raw>
        </div>
    </f:for>
</div>
</html>

So weit passt schonmal das Frontend. Im Backend kann man ein wenig mit CSS tricksen. Ich habe bewusst als colPos 55 gewählt. Diese Zahl wird ins Template reingeschrieben, und macht es so möglich, ein spezielles CSS darauf anzuwenden. Man definiert also ein Backend CSS und schreibt da folgendes rein (Scss):

.t3-page-ce-wrapper[data-colpos='55'] {
  .t3-page-ce[data-uid] {
    width: 46%;
    float: left;
 
    &:nth-child(2n) {
      clear: both;
    }
  }
}

Damit werden die Content Element im Backend in zwei Spalten dargestellt. Theoretisch kann man da auch Breakpoints definieren, der Phantasie sind keine Grenzen gesetzt.

So was wie MenuItemProcFunc in Content Element „Menü“ mit Fluid Styled Content

Früher, in den Zeiten von css_styled_content konnte man in das Inhaltselement „Menü“ mittels itemArrayProcFunc eigene Menü-Items einhängen. Das funktioniert nach wie vor mit HMENU und sieht so aus:

1 = TMENU
1 [...]
2 < .1
2 {
	wrap = <ul>|</ul>
	itemArrayProcFunc = My\Extension\Hooks\MenuItemArrayProcessor->process
}

In der Funktion kann man dann z.B. Items aus der eigenen Extension ins Menü hängen, damit es so aussieht als wären es Seiten.

Das hat mit css_styled_content gut funktioniert, da dort das Rendering des Inhaltselemente (unter anderem Menü) ebenfalls mit TypoScript gemacht wurde. Also wenn man den TypoScriptObjectBrowser richtig bedienen kann, dann findet man auch schnell raus, wo man den Code einsetzen muss.

Nun stand ich vor einem Problem (nein, falsch – Herausforderung). In fluid_styled_content ist das Rendering der Content Element komplett über Fluid. So gibt es z.B. einen ViewHelper, der für „Menü ausgewählter Seiten“ die Seiten holt. Die Ausgabe geschieht dann mit Fluid. Nix TypoScript!

Ich habe folgendes implementiert: Falls eine bestimmte Seiten-ID in der Liste vorkommt (es ist meine Ziel-Detailseite), dann schmeiss ich einen weiteren ViewHelper an, der mit die Items mit Titel und Parameter ermittelt. Das Rendering dann wieder mit Fluid.

(mehr …)

Einträge in Liste in Zweiergruppen ausgeben

Folgende Aufgabenstellung: Mit Fluid sollen für eine Slideshow jeweils zwei Elemente einer Liste gruppiert (also in 1 Div eingeschlossen) ausgegeben werden. Als erstes muss am Anfang ein Slide geöffnet werden, wenn die Liste nicht leer ist. Nach dem letzten Element muss man den Slide schließen, egal wieviele Elemente in der Liste sind. Dazwischen muss man nach zwei Elementen den Slide schließen und wieder öffnen. Und sieht dann der Code aus:

<div class="video-slideshow">
	<div class="slick-slideshow" data-slick='{
			"adaptiveHeight": true
		}'>
		<div class="slide"><!-- Slide BEGIN -->
			<f:for each="{videos}" as="video" iteration="i">
				<f:render section="Video" arguments="{video: video, settings: settings}" />
				<f:if condition="{i.isLast}">
					<f:then>
						</div><!-- Slide END -->
					</f:then>
					<f:else>
						<f:if condition="{i.index} % 2">
							</div><div><!-- Slide -->
						</f:if>
					</f:else>
				</f:if>
			</f:for>
	</div><!-- slick-slideshow -->
</div><!-- video-slideshow -->

Ok, übersichtlich ist was anderes…

Fluid Select mit Objekten in Hierarchie darstellen

Folgendes Datenmodell: Objekte vom Typ ‚Items‘ und Kategorien. Eine Kategorie hat eigentlich nur einen Titel, kann aber über das Feld ‚parent‘ einer anderen Kategorie als Kind zugewiesen werden, so dass eine Art Baum entsteht. So ist z.B. in Kategorie A.1 ist Kategorie A als Parent gesetzt.

- A
-- A.1
-- A.2
--- A.2.I
--- A.2.II
-- A.3
- B

Nun will ich in einem Select-Feld die Kategorien ausgeben, dabei würde ich gerne den vollen Pfad zur Kategorie im Select ausgeben und nicht nur den Titel der Kategorie. So würde dann die Auswahl im Select-Feld so aussehen:

A
A > A.1
A > A.2
A > A.2.I
[...]

Und das ist meine (wie ich finde elegante) Lösung: das Select-Feld in Fluid wird dann so definitert:

<f:form.select property="category" options="{categories}" prependOptionLabel="Bitte wählen" prependOptionValue="" optionLabelField="titlePath"/>

Die Eigenschaft titlePath exisitert in der Kategorie natürlich nicht, aber Fluid bedient sich da des Getters, also muss nur der entsprechende Getter definiert werden. In dem Getter ruft man dann die gleiche Funktion vom Parent auf. That’s it!

/**
 * @return string
 */
public function getTitlePath() {
    if($this->parent == null) {
        return $this->title;
    }
    return $this->parent->getTitlePath().' - '.$this->getTitle();
}

Fluid Formular für neues Objekt mit Select und leerer Option

Ich implementiere gerade eine Extension mit (vereinfacht) folgendem Aufbau: Objekte vom Typ ‚Items‘ können jeweils einer Kategorie zugeordnet werden. Die Kategorien sind wiederum in der Datenbank als Datensätze hinterlegt. So etwas kommt häufig vor, das beliebteste Beispiel ist wahrscheinlich ein Blog-Eintrag und die dazugehörige Kategorie.

Erstellt man diese Extension z.B. über den Extension Builder und lässt sich eine new/create-Action anlegen, kann man im Frontend nun auch Objekte vom Typ ‚Item‘ anlegen. Nun hatte ich folgende Hürde zu meistern: das Feld Kategorie ist ein Pflichtfeld, trotzdem soll in der Kategorieauswahl im Frontend eine zusätzliche leere Option angezeigt werden. Da höre ich schon den Schrei der Empörung: „Warum brauchst du eine leere Option? Das Feld ist doch ein Pflichtfeld!“. Weil dann der Benutzer gezwungen ist, sich für eine Option bewusst zu entscheiden und nicht einfach die erste Option angenommen wird.

Eine leere Option im Formular dranzuhängen ist einfach:

<f:form.select property="category" options="{categories}" prependOptionLabel="Bitte wählen" prependOptionValue="" optionLabelField="title" />

Im Model das Feld als Pflichtfeld deklarieren:

/**
 * @var \MY\MyExtension\Domain\Model\Category
 * @validate NotEmpty
 */
protected $category = null;

Wenn man nun das Formular im Frontend testet, passiert folgendes: beim Absenden des Formulars ohne etwas auszuwählen gibt es eine Exception: Exception while property mapping at property path "": PHP Catchable Fatal Error: Argument 1 passed to MY\MyExtension\Domain\Model\Item::setCategory() must be an instance of MY\MyExtension\Domain\Model\Category, null given. Das erwartete Verhalten wäre aber, dass der Validator greift und eine Fehlermeldung ausgibt.

Die Lösung ist das Setzen eines Default-Wertes im Setter:

/**
 * Sets the category
 *
 * @param \MY\MyExtension\Domain\Model\Category $category
 * @return void
 */
public function setCategory(\MY\MyExtension\Domain\Model\Category $category = null)
{
    $this->category = $category;
}

Fehler im allgemeinen Validator an ein Feld hängen

(attach object errors to a specific field)

Es gibt die Möglichkeit in Extbase Validatoren für Objekte zu definieren. Wenn man also ein Model mit dem Namen „Domain_Model_ObjectModel“ hat, wird falls vorhanden der Validator mit dem Namen „Domain_Validator_ObjectValidator“. Dabei wird nachdem die einzelnen Felder geprüft wurden mit dem angegebenen Validator das komplette Objekt überprüft. So kann man wie in meinem Beispiel feststellen, ob der Titel bereits vergeben worden ist (Unique). Normalweise wird die Fehlermeldung bei einer nicht erfolgreichen Validierung an das Objekt drangehängt. Da ich die Fehlermeldungen neben dem Feld ausgebe (siehe dazu Fehlermeldung mit Fluid direkt neben dem Feld ausgeben), wollte ich den Fehler an das Attribut title dranhängen.

class Tx_MyExtension_Domain_Validator_ObjectValidator 
	extends Tx_Extbase_Validation_Validator_AbstractValidator
{
 
    /**
     * Check if there is a object with the same name
     * @param  Tx_MyExtension_Domain_Model_Object $object
     * @return bool
     */
	public function isValid($object) {
		$objectRepository = t3lib_div::makeInstance('Tx_MyExtension_Domain_Repository_ObjectRepository');
		$sameNameCount = $objectRepository->countByTitleExcludeUid($object->getTitle(), $object->getUid());
		if($sameNameCount > 0) {
			if (!isset($this->errors['title'])) {
				$this->errors['title'] = new Tx_Extbase_Validation_PropertyError('title');
			}
			$error = new Tx_Extbase_Validation_Error(Tx_Extbase_Utility_Localization::translate('error.object_title_exists', 'MyExtension'), '1312448731');
			$this->errors['title']->addErrors(array($error));
			return FALSE;
		}
		return TRUE;
    }
 
}

Tags: , ,

Geschrieben in TYPO3 | Kommentare deaktiviert für Fehler im allgemeinen Validator an ein Feld hängen

Fluid If-Condition und String-Vergleich

Leider kann der Fluid-ViewHelper keine Strings vergleichen. Ich habe ein wenig recherchiert und Lösungen gefunden.

Die erste ist ein eigener View Helper, der in der Lage ist, zwei Objekte (Strings z.B.) zu vergleichen.

<?php
 
class Tx_MyExt_ViewHelpers_CompareViewHelper extends Tx_Fluid_Core_ViewHelper_AbstractViewHelper {
 
    /**
     * Returns true if $a and $b are type-equal, false otherwise.
     *
     * @param mixed $a
     * @param mixed $b
     * @return boolean
     */
    public function render($a, $b) {
        return $a === $b;
    }
}
 
?>

Dann soll es laut dem Eintrag so funktionieren:

<f:if condition="{myext:compare(a: 'foo', b: 'bar')}">
  <f:then>...</f:then>
  <f:else>...</f:else>
</f:if>

Leider hat es bei mir nicht funktioniert.

Aber der Vorschlag zwei hat hingegen funktioniert. Es wird mit Fluid ein Alias für den zu vergleichenden String defininiert und dann verglichen:

<f:alias map="{foobar: 'myString'}">
<f:if condition="{object.property} ==  {foobar}"><!-- do something --></f:if>
</f:alias>