TYPO3 9 LTS: Bootstrap Klassen für GridElements

Ich versuche derzeit eine neue Idee um die Bootstrap Klassen eines eigenen Themes in TYPO3 zu bekommen. Gerade wenn man ein schlankes System möchte, und man nicht auf zusätzliche Bootstrap Extensions für TYPO3 setzen möchte, sollte das erstellen von GridElements dennoch schnell gehen.

Mit dem Relaunch meiner Seite stand im Vordergrund, dass das Projekt schmal sein soll. Ziel ist es, auf vorgefertigte Theme Extensions zu verzichten. Alle GridElements die benötigt werden, sollen fix schnell gebaut werden können. Allerdings bereits beim zweiten FlexForm eines Elements stellte sich die Frage: Muss das ganze XML sein, benötige ich für jede Column zig Selectboxen, für die ganzen Breakpoints, und vor allem, für die restlichen Bootstrap Klassen wie Textfarbe, Hintergrund, Cols Zentriert etc. Denn genau diese Klassen machen ein Projekt flexibel, aber bläht die FlexForms komplett auf.

Und dann die Frage: wie kann ich schnell was an der Konfiguration ändern, wenn sich am Bootstrap Theme was ändert. Beispiel: Es kommt eine neue Farbe für Text, Hintergrund und Border hinzu. In den Bootstrap Variables ist das per CSS einfach zu machen. Was aber wenn ich für Container und Columns auch Border möchte. Und am besten für 1-, 2-, 3- und 4-Spalter. Die ganzen XML Dateien öffnen und wieder überarbeiten macht hier nicht wirklich Sinn.

Und beim Thema Farben fällt mir Spontan auch noch etwas ein: Header Formatierungen. Diverse Text-Farben, Border, das Alignment, oder ganz verrückt, diverse CSS Animationen. Und natürlich immer in Einklang, oder zumindest leicht zu aktualiseren, mit dem Bootstrap Theme. Letzteres hat zwar nichts mit GridElements zu tun, aber die herangehensweise für GridElements lässt sich auch zu Fluid Styled Content übertragen.

Beispiel Zeilen eines GridElements Dreispalter mit nur den Col Klassen für 3 BreakPoints: 623 Zeilen XML. Gegenüber steht ein Vierspalter mit allen benötigten Bootstrap Klassen für Container, Row und Column, inklusive den ganzen Auswahlen mit Padding / Margin, Hintegrundfarben, Border und Borderfarben und Text Farben (und noch einiges mehr): XML 194 Zeilen.

Wie ist das zu erreichen? Die Antwort ist relativ Simpel: Eine UserFunc, die eine Multiple Sie-By-Side Selectbox zur Verfügung stellt, und alle benötigten Klassen zurückgibt. Anbei ein Screenshot wie das im Backend genau aussieht:

Auf dem Screen ist ganz gut zu erkennen, wie wenig Boxen es für die Konfiguration gibt, eigentlich nur 2. Es können direkt alle Klassen angegeben werden, die für das Element benötigt werden. Neue Klassen lassen sich schnell hinzufügen und stehen in allen GridElements dann zur Verfügung. die einzlenen XML Dateien müssen in der Regel nicht mehr geändert werden.

Bei einem Beispiel für Überschriften wird es nochmal verdeutlicht. Anstelle von vielen Klassenkombinationen über header_layout anlegen zu müssen, lassen sich die gewnüschten Einstellungen einfach aktivieren:

null

Nun aber zum technischen. Was wird benötigt:

  • GridElements
  • Eigene UserFunc
  • Eigene Template Extension
  • VHS (odr einen str_replace ViewHelper)

 

Anlegen einer modularen UserFunc

Anlegen einer neuen Datei: BSClasses.php in eurer Template Extension: typo3conf/ext/template/Classes/Gridelements/FlexFrom/BSClasses.php

 

<?php

namespace RBIZ\Basetemplate\Gridelements\FlexForm;

class BSClasses
{

}

 

Im nächsten Schritt fügen wir eine Funktion hinzu, die in der Flexform aufgerufen wird:

 

    public function getContainerClasses(&$fConfig)
    {

    }

 

Die wichtigsten Klassen beim Container sind später die Margin und Padding Klassen. Ich beschränke mich selbst aktuell auf Margin/Padding Bottom und Top. Damit ich diese Werte auch für andere Sachen abrufen möchte, lagere ich das entsprechend in eine eigene Funktion aus

 

    // Anzahl der Margins/Paddings aus der Sass Konfiguration
    protected $margins = 10;
    
    // Generieren der Klassen mit Value und Label
    protected function createMarginsAndPaddings(&$target)
    {

        for ($i = 1; $i <= $this->margins; $i++) {
            array_push($target, ['Margin Top ' . $i, 'mt-' . $i]);
        }
        for ($i = 1; $i <= $this->margins; $i++) {
            array_push($target, ['Margin Bottom ' . $i, 'mb-' . $i]);
        }

        for ($i = 1; $i <= $this->margins; $i++) {
            array_push($target, ['Padding Top ' . $i, 'pt-' . $i]);
        }

        for ($i = 1; $i <= $this->margins; $i++) {
            array_push($target, ['Padding Bottom ' . $i, 'pb-' . $i]);
        }

        $extraClasses = [
            ['Margin Left Auto', 'ml-auto'],
            ['Margin Right Auto', 'mr-auto']
        ];
        foreach ($extraClasses as $extraClass){
            array_push($target, $extraClass);
        }

    }

 

 

In der zuvor erstellten Funktion, welche aus den Flexforms aufgerufen wird, werden nun die Auswahlen geholt. Zusätzlich dazu werden noch die beiden Container Klassen hinzugefügt.

 

    public function getContainerClasses(&$fConfig)
    {

        $extraClasses = [
            ['Container', 'container'],
            ['Container Fluid', 'container-fluid']
        ];
        foreach ($extraClasses as $extraClass){
            array_push($fConfig['items'], $extraClass);
        }
        $this->createMarginsAndPaddings($this->containerClasses);
        foreach ($this->containerClasses as $class){
            array_push($fConfig['items'], $class);
        }

    }

 

 

Das FlexForm für dieses Element schaut so aus:

 

<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<T3DataStructure>
	<meta type="array">
		<langDisable>1</langDisable>
	</meta>
	<sheets>
		<containerClasses>
			<ROOT type="array">
				<TCEforms>
					<sheetTitle>Container Classes</sheetTitle>
				</TCEforms>
				<type>array</type>
				<el type="array">
					<containerClasses>
						<TCEforms>
							<label>Classes</label>
							<config>
								<type>select</type>
								<itemsProcFunc>RBIZ\Basetemplate\Gridelements\FlexForm\BSClasses->getContainerClasses</itemsProcFunc>
								<maxitems>99</maxitems>
								<renderType>selectMultipleSideBySide</renderType>
								<size>10</size>
								<enableMultiSelectFilterTextfield>1</enableMultiSelectFilterTextfield>
								<default>container</default>
							</config>
						</TCEforms>
					</containerClasses>
				</el>
			</ROOT>
		</containerClasses>
     </sheets>
</T3DataStructure>

 

Im Template können wir nun darauf zugreifen:

 

<html xmlns="http://www.w3.org/1999/xhtml"
	  xmlns:f="http://typo3.org/ns/TYPO3/Fluid/ViewHelpers" lang="en"
	  xmlns:v="http://typo3.org/ns/FluidTYPO3/Vhs/ViewHelpers">

<div class="
	{data.flexform_containerClasses -> v:format.replace(substring:',', replacement:' ')}
	{data.flexform_containerTheme -> v:format.replace(substring:',', replacement:' ')}
	">
	<div class="container">
		<div class="row
			{data.flexform_rowClasses -> v:format.replace(substring:',', replacement:' ')}
			{data.flexform_rowTheme -> v:format.replace(substring:',', replacement:' ')}
		">
			<div class="
				{data.flexform_colClasses -> v:format.replace(substring:',', replacement:' ')}
				{data.flexform_colTheme -> v:format.replace(substring:',', replacement:' ')}
			">
				<f:for each="{data.tx_gridelements_view_children}" as="child" iteration="iteration">
					<f:format.raw><v:variable.get name="data.tx_gridelements_view_child_{child.uid}" /></f:format.raw>
				</f:for>
			</div>
		</div>
	</div>
</div>
</html>

 

Da wir in diesem Einspalter nun bis auf row keine fixen Klassen mehr haben, wird deutlich, wie Flexibel das ganze werden kann. Einsalter Fluid oder Begrenzt, Hintergrund, Textrabe, Border und vieles mehr lässt sich durch ein oder zwei Selektboxen zusammenstellen. Am Ende kommt nochmal das komplette Listing der Klasse und ebenfalls das komplette XML für den Einspalter. Zuvor jedoch noch eine Zusatzlösung:

 

Hochflexible Headers in TYPO3

Damit wir auch bei den Überschriften flexibel sind, lege ich in dieser Klasse eine neue Funktion an, die mir aus den verschiedenen Klassenkollektionen die passenden Klassen für Header zur Verfügung stellt:

 

    public function getHeaderClasses(&$config)
    {
        $extraClasses = [
            ['H1 Like', 'h1-like'],
            ['H2 Like', 'h2-like'],
            ['H3 Like', 'h3-like'],
            ['H4 Like', 'h4-like'],
            ['H5 Like', 'h5-like'],
            ['H6 Like', 'h6-like'],
        ];
        foreach ($extraClasses as $extraClass){
            array_push($config['items'], $extraClass);
        }
        $this->createTextAligns($this->headerClasses);
        $this->createTextColors($this->headerClasses);
        $this->createAnimationClasses($this->headerClasses);
        $this->createBorderColors($this->headerClasses);
        $this->createBackgroundColors($this->headerClasses);
        $this->createMarginsAndPaddings($this->headerClasses);
        foreach ($this->headerClasses as $class){
            array_push($config['items'], $class);
        }

    }

 

Im nächsten Schritt erweitern wir das TYPO3 Backend. Zuerst wird ein neues Feld für tt_content in der Datenbank erzeugt, und zwar im File ext_tables.sql:

 

# Table structure for table 'tt_content'
#
CREATE TABLE tt_content (
        header_classes text
);

 

Anschließend fügen wir das neue Feld im Backend hinzu, im File typo3conf/ext/template/Configuration/TCA/Overrides/tt_content.php

 

<?php
if (!defined('TYPO3_MODE')) {
    die();
}
$fields = [
    'header_classes' => [
        'exclude' => 1,
        'label' => 'Theme Header Classes',
        'config' => [
            'type' => 'select',
            'itemsProcFunc' => 'RBIZ\Basetemplate\Gridelements\FlexForm\BSClasses->getHeaderClasses',
            'maxItems' => 99,
            'renderType' => 'selectMultipleSideBySide',
            'size' => 10,
            'enableMultiSelectFilterTextfield' => 1
        ]
    ],
];
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns('tt_content', $fields);
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addToAllTCAtypes('tt_content', 'header_classes', '', 'after:header_layout');

 

Ausserdem entfernen wir die Header Positions im Backend und formatieren die Überschriften einmal durch:

 

TCEFORM {
	tt_content {
		header_layout {
			altLabels {
				1 = H1
				2 = H2
				3 = H3
				4 = H4
				5 = H5
				100 = Versteckt
			}
			removeItems = 0
			addItems {
				6 = H6
			}
		}
		header_position.disabled = 1
	}
}

 

Danach werden die beiden Dateien von FSC in die eigene Extension übernommen und angepasst:

/typo3/sysext/fluid_styled_content/Resources/Private/Partials/Header/All.html

 

<html
		xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
		xmlns:v="http://typo3.org/ns/FluidTYPO3/Vhs/ViewHelpers"
		data-namespace-typo3-fluid="true">
<f:if condition="{data.header_layout} != 100">
	<f:if condition="{data.header} || {data.subheader} || {data.date}">
		<header>
			<f:render partial="Header/Header" arguments="{
				header: data.header,
				layout: data.header_layout,
				positionClass: data.header_classes,
				link: data.header_link,
				default: settings.defaultHeaderType}" />
			<f:render partial="Header/SubHeader" arguments="{
				subheader: data.subheader,
				layout: data.header_layout,
				positionClass: data.header_classes,
				default: settings.defaultHeaderType}" />
			<f:render partial="Header/Date" arguments="{
				date: data.date,
				positionClass: data.header_classes}" />
		</header>
	</f:if>
</f:if>
</html>

 

Wichtig hier, ist das für positionClass nicht mehr header_position übergeben wird, sondern unser neues Feld header_classes!

Und in der Datei 

/typo3/sysext/fluid_styled_content/Resources/Private/Partials/Header/Header.html:

 

<html
		xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
		xmlns:v="http://typo3.org/ns/FluidTYPO3/Vhs/ViewHelpers"
		data-namespace-typo3-fluid="true">
<f:if condition="{header}">
	<f:switch expression="{layout}">
		<f:case value="1">
			<h1 class="{positionClass -> v:format.replace(substring:',', replacement:' ')}">
				<f:link.typolink parameter="{link}">{header}</f:link.typolink>
			</h1>
		</f:case>
		<f:case value="2">
			<h2 class="{positionClass -> v:format.replace(substring:',', replacement:' ')}">
				<f:link.typolink parameter="{link}">{header}</f:link.typolink>
			</h2>
		</f:case>
		<f:case value="3">
			<h3 class="{positionClass -> v:format.replace(substring:',', replacement:' ')}">
				<f:link.typolink parameter="{link}">{header}</f:link.typolink>
			</h3>
		</f:case>
		<f:case value="4">
			<h4 class="{positionClass -> v:format.replace(substring:',', replacement:' ')}">
				<f:link.typolink parameter="{link}">{header}</f:link.typolink>
			</h4>
		</f:case>
		<f:case value="5">
			<h5 class="{positionClass -> v:format.replace(substring:',', replacement:' ')}">
				<f:link.typolink parameter="{link}">{header}</f:link.typolink>
			</h5>
		</f:case>
		<f:case value="6">
			<h6 class="{positionClass -> v:format.replace(substring:',', replacement:' ')}">
				<f:link.typolink parameter="{link}">{header}</f:link.typolink>
			</h6>
		</f:case>
		<f:case value="100">
			<f:comment> -- do not show header -- </f:comment>
		</f:case>
		<f:defaultCase>
			<f:if condition="{default}">
				<f:render partial="Header/Header" arguments="{
					header: header,
					layout: default,
					positionClass: positionClass,
					link: link}" />
			</f:if>
		</f:defaultCase>
	</f:switch>
</f:if>
</html>

 

 

 

Das war es im Grundlegenden. Anbei das komplette Listing der BSClasses wie ich es aktuell einsetze. Die folgenden Methoden sind die Public, um von verschiedenen Punkten (Container, Row oder Column, oder auch in tt_content den Header) die richtige Sammlung der benötigten Klassen zu bekommen:

  •         public function getHeaderClasses(&$config)
  •         public function getThemeClasses(&$fConfig)
  •         public function getContainerClasses(&$fConfig)
  •         public function getRowClasses(&$fConfig)
  •         public function getColClasses(&$fConfig)

Die folgenden Methoden sind protected, und dienen dazu, die eigentlichen Werte zusammenzustellen. Damit wird es sehr flexibel, denn pro Pubilc Methode kann ich genau bestimmen, welche Klassen geholt werden sollen:

  •         protected function createTextAligns(&$target)
  •         protected function createTextColors(&$target)
  •         protected function createBackgroundColors(&$target)
  •         protected function createBorderColors(&$target)
  •         protected function createGridColClasses(&$target)
  •         protected function createMarginsAndPaddings(&$target)
  •         protected function createJustifyContent(&$target)
  •         protected function createAnimationClasses(&$target)

 

<?php

namespace RBIZ\Basetemplate\Gridelements\FlexForm;


class BSClasses
{
    protected $containerClasses = [];

    protected $justifyClasses = [];

    protected $rowClasses = [];

    protected $colClasses = [];

    protected $themeClasses = [];

    protected $headerClasses = [];

    protected $margins = 10;

    protected $gridCols = 12;

    protected $breakPoints = ['xl', 'lg', 'md', 'sm'];

    protected $themeColors = [
        'primary',
        'secondary',
        'success',
        'info',
        'warning',
        'danger',
        'light',
        'dark',
        'blue',
        'indigo',
        'purple',
        'pink',
        'red',
        'orange',
        'yellow',
        'green',
        'teal',
        'cyan',
        'white',
        'gray-100',
        'gray-200',
        'gray-300',
        'gray-400',
        'gray-500',
        'gray-600',
        'gray-700',
        'gray-800',
        'gray-900'
    ];
    
    protected $animationClasses = [
        'fadeInUp',
        'fadeInDown',
        'fadeInLeft',
        'fadeInRight',
        'fadeIn',
        'turn',
        'turnOut'

    ];


    public function __construct()
    {
    }

    public function getHeaderClasses(&$config)
    {
        $extraClasses = [
            ['H1 Like', 'h1-like'],
            ['H2 Like', 'h2-like'],
            ['H3 Like', 'h3-like'],
            ['H4 Like', 'h4-like'],
            ['H5 Like', 'h5-like'],
            ['H6 Like', 'h6-like'],
        ];
        foreach ($extraClasses as $extraClass){
            array_push($config['items'], $extraClass);
        }
        $this->createTextAligns($this->headerClasses);
        $this->createTextColors($this->headerClasses);
        $this->createAnimationClasses($this->headerClasses);
        $this->createBorderColors($this->headerClasses);
        $this->createBackgroundColors($this->headerClasses);
        $this->createMarginsAndPaddings($this->headerClasses);
        foreach ($this->headerClasses as $class){
            array_push($config['items'], $class);
        }

    }

    public function getThemeClasses(&$fConfig)
    {
        $this->createTextAligns($this->themeClasses);
        $this->createTextColors($this->themeClasses);
        $this->createBackgroundColors($this->themeClasses);
        $this->createBorderColors($this->themeClasses);
        foreach ($this->themeClasses as $class){
            array_push($fConfig['items'], $class);
        }
    }

    public function getContainerClasses(&$fConfig)
    {

        $extraClasses = [
            ['Container', 'container'],
            ['Container Fluid', 'container-fluid']
        ];
        foreach ($extraClasses as $extraClass){
            array_push($fConfig['items'], $extraClass);
        }
        $this->createMarginsAndPaddings($this->containerClasses);
        $this->createJustifyContent($this->containerClasses);
        $this->createAnimationClasses($this->containerClasses);
        foreach ($this->containerClasses as $class){
            array_push($fConfig['items'], $class);
        }

    }

    public function getRowClasses(&$fConfig)
    {
        $this->createMarginsAndPaddings($this->containerClasses);
        foreach ($this->containerClasses as $class){
            array_push($fConfig['items'], $class);
        }
        $this->createJustifyContent($this->containerClasses);
        foreach ($this->containerClasses as $class){
            array_push($fConfig['items'], $class);
        }

    }

    public function getColClasses(&$fConfig)
    {
        $this->createGridColClasses($this->colClasses);
        $this->createMarginsAndPaddings($this->colClasses);
        $this->createAnimationClasses($this->colClasses);
        foreach ($this->colClasses as $class){
            array_push($fConfig['items'], $class);
        }
    }

    protected function createTextAligns(&$target)
    {
        $extraClasses = [
            ['Text Left', 'text-left'],
            ['Text Right', 'text-right'],
            ['Text Center', 'text-center'],
            ['Text Justify', 'text-justify']
        ];
        foreach ($extraClasses as $extraClass){
            array_push($target, $extraClass);
        }
    }


    protected function createTextColors(&$target)
    {
        foreach ($this->themeColors as $color){
            array_push($target, ['Text ' . ucfirst($color), 'text-' . $color]);
        }
    }

    protected function createBackgroundColors(&$target)
    {
        foreach ($this->themeColors as $color){
            array_push($target, ['Background ' . ucfirst($color), 'bg-' . $color]);
        }
    }

    protected function createBorderColors(&$target)
    {
        foreach ($this->themeColors as $color){
            array_push($target, ['Border ' . ucfirst($color), 'border-' . $color]);
        }

        $extraClasses = [
            ['Border Top', 'border-top'],
            ['Border Right', 'border-right'],
            ['Border Bottom', 'border-bottom'],
            ['Border Left', 'border-left']
        ];
        foreach ($extraClasses as $extraClass){
            array_push($target, $extraClass);
        }
    }


    protected function createGridColClasses(&$target)
    {
        foreach ($this->breakPoints as $breakPoint){
            for ($i = 1; $i <= $this->gridCols; $i++) {
                array_push($target, ['Col '.strtoupper($breakPoint).' ' . $i, 'col-'.$breakPoint.'-' . $i]);
            }
        }
        for ($i = 1; $i <= $this->gridCols; $i++) {
            array_push($target, ['Col ' . $i, 'col-' . $i]);
        }
    }

    protected function createMarginsAndPaddings(&$target)
    {

        for ($i = 1; $i <= $this->margins; $i++) {
            array_push($target, ['Margin Top ' . $i, 'mt-' . $i]);
        }
        for ($i = 1; $i <= $this->margins; $i++) {
            array_push($target, ['Margin Bottom ' . $i, 'mb-' . $i]);
        }

        for ($i = 1; $i <= $this->margins; $i++) {
            array_push($target, ['Padding Top ' . $i, 'pt-' . $i]);
        }

        for ($i = 1; $i <= $this->margins; $i++) {
            array_push($target, ['Padding Bottom ' . $i, 'pb-' . $i]);
        }

        $extraClasses = [
            ['Margin Left Auto', 'ml-auto'],
            ['Margin Right Auto', 'mr-auto']
        ];
        foreach ($extraClasses as $extraClass){
            array_push($target, $extraClass);
        }

    }

    protected function createJustifyContent(&$target)
    {
        $types = ['start', 'center', 'end'];

        foreach ($this->breakPoints as $breakPoint){
            foreach ($types as $type){
                array_push($target, ['Justifiy Content '.strtoupper($breakPoint).' ' . ucfirst($type), 'justify-content-'.$breakPoint.'-' . $type]);
            }
        }
    }

    protected function createAnimationClasses(&$target)
    {
        foreach ($this->animationClasses as $class){
            array_push($target, ['Animation ' . ucfirst($class), $class]);
        }
    }
}

 

 

Als letztes Listing nochmal einen GridElements 4 Spalter:

 

<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<T3DataStructure>
	<meta type="array">
		<langDisable>1</langDisable>
	</meta>
	<sheets>
		<rowClasses>
			<ROOT type="array">
				<TCEforms>
					<sheetTitle>Row Classes</sheetTitle>
				</TCEforms>
				<type>array</type>
				<el type="array">
					<rowClasses>
						<TCEforms>
							<label>Classes</label>
							<config>
								<type>select</type>
								<itemsProcFunc>RBIZ\Basetemplate\Gridelements\FlexForm\BSClasses->getRowClasses</itemsProcFunc>
								<maxitems>99</maxitems>
								<renderType>selectMultipleSideBySide</renderType>
								<size>10</size>
								<enableMultiSelectFilterTextfield>1</enableMultiSelectFilterTextfield>
							</config>
						</TCEforms>
					</rowClasses>
					<rowTheme>
						<TCEforms>
							<label>Theme Colors and Text Setup</label>
							<config>
								<type>select</type>
								<itemsProcFunc>RBIZ\Basetemplate\Gridelements\FlexForm\BSClasses->getThemeClasses</itemsProcFunc>
								<maxitems>99</maxitems>
								<renderType>selectMultipleSideBySide</renderType>
								<size>10</size>
								<enableMultiSelectFilterTextfield>1</enableMultiSelectFilterTextfield>
							</config>
						</TCEforms>
					</rowTheme>
				</el>
			</ROOT>
		</rowClasses>

		<col1Classes>
			<ROOT type="array">
				<TCEforms>
					<sheetTitle>Column 1 Classes</sheetTitle>
				</TCEforms>
				<type>array</type>
				<el type="array">
					<col1Classes>
						<TCEforms>
							<label>Classes</label>
							<config>
								<type>select</type>
								<itemsProcFunc>RBIZ\Basetemplate\Gridelements\FlexForm\BSClasses->getColClasses</itemsProcFunc>
								<maxitems>99</maxitems>
								<renderType>selectMultipleSideBySide</renderType>
								<size>10</size>
								<enableMultiSelectFilterTextfield>1</enableMultiSelectFilterTextfield>
								<default>col-sm-12, col-md-6, col-lg-3</default>
							</config>
						</TCEforms>
					</col1Classes>
					<col1Theme>
						<TCEforms>
							<label>Theme Colors and Text Setup</label>
							<config>
								<type>select</type>
								<itemsProcFunc>RBIZ\Basetemplate\Gridelements\FlexForm\BSClasses->getThemeClasses</itemsProcFunc>
								<maxitems>99</maxitems>
								<renderType>selectMultipleSideBySide</renderType>
								<size>10</size>
								<enableMultiSelectFilterTextfield>1</enableMultiSelectFilterTextfield>
							</config>
						</TCEforms>
					</col1Theme>
				</el>
			</ROOT>
		</col1Classes>
		<col2Classes>
			<ROOT type="array">
				<TCEforms>
					<sheetTitle>Column 2 Classes</sheetTitle>
				</TCEforms>
				<type>array</type>
				<el type="array">
					<col2Classes>
						<TCEforms>
							<label>Classes</label>
							<config>
								<type>select</type>
								<itemsProcFunc>RBIZ\Basetemplate\Gridelements\FlexForm\BSClasses->getColClasses</itemsProcFunc>
								<maxitems>99</maxitems>
								<renderType>selectMultipleSideBySide</renderType>
								<size>10</size>
								<enableMultiSelectFilterTextfield>1</enableMultiSelectFilterTextfield>
								<default>col-sm-12, col-md-6, col-lg-3</default>
							</config>
						</TCEforms>
					</col2Classes>
					<col2Theme>
						<TCEforms>
							<label>Theme Colors and Text Setup</label>
							<config>
								<type>select</type>
								<itemsProcFunc>RBIZ\Basetemplate\Gridelements\FlexForm\BSClasses->getThemeClasses</itemsProcFunc>
								<maxitems>99</maxitems>
								<renderType>selectMultipleSideBySide</renderType>
								<size>10</size>
								<enableMultiSelectFilterTextfield>1</enableMultiSelectFilterTextfield>
							</config>
						</TCEforms>
					</col2Theme>
				</el>
			</ROOT>
		</col2Classes>
		<col3Classes>
			<ROOT type="array">
				<TCEforms>
					<sheetTitle>Column 3 Classes</sheetTitle>
				</TCEforms>
				<type>array</type>
				<el type="array">
					<col3Classes>
						<TCEforms>
							<label>Classes</label>
							<config>
								<type>select</type>
								<itemsProcFunc>RBIZ\Basetemplate\Gridelements\FlexForm\BSClasses->getColClasses</itemsProcFunc>
								<maxitems>99</maxitems>
								<renderType>selectMultipleSideBySide</renderType>
								<size>10</size>
								<enableMultiSelectFilterTextfield>1</enableMultiSelectFilterTextfield>
								<default>col-sm-12, col-md-6, col-lg-3</default>

							</config>
						</TCEforms>
					</col3Classes>
					<col3Theme>
						<TCEforms>
							<label>Theme Colors and Text Setup</label>
							<config>
								<type>select</type>
								<itemsProcFunc>RBIZ\Basetemplate\Gridelements\FlexForm\BSClasses->getThemeClasses</itemsProcFunc>
								<maxitems>99</maxitems>
								<renderType>selectMultipleSideBySide</renderType>
								<size>10</size>
								<enableMultiSelectFilterTextfield>1</enableMultiSelectFilterTextfield>
							</config>
						</TCEforms>
					</col3Theme>
				</el>
			</ROOT>
		</col3Classes>
		<col4Classes>
			<ROOT type="array">
				<TCEforms>
					<sheetTitle>Column 4 Classes</sheetTitle>
				</TCEforms>
				<type>array</type>
				<el type="array">
					<col4Classes>
						<TCEforms>
							<label>Classes</label>
							<config>
								<type>select</type>
								<itemsProcFunc>RBIZ\Basetemplate\Gridelements\FlexForm\BSClasses->getColClasses</itemsProcFunc>
								<maxitems>99</maxitems>
								<renderType>selectMultipleSideBySide</renderType>
								<size>10</size>
								<enableMultiSelectFilterTextfield>1</enableMultiSelectFilterTextfield>
								<default>col-sm-12, col-md-6, col-lg-3</default>
							</config>
						</TCEforms>
					</col4Classes>
					<col4Theme>
						<TCEforms>
							<label>Theme Colors and Text Setup</label>
							<config>
								<type>select</type>
								<itemsProcFunc>RBIZ\Basetemplate\Gridelements\FlexForm\BSClasses->getThemeClasses</itemsProcFunc>
								<maxitems>99</maxitems>
								<renderType>selectMultipleSideBySide</renderType>
								<size>10</size>
								<enableMultiSelectFilterTextfield>1</enableMultiSelectFilterTextfield>
							</config>
						</TCEforms>
					</col4Theme>
				</el>
			</ROOT>
		</col4Classes>
	</sheets>
</T3DataStructure>