Ajax mit TYPO3 – so funktionierts!

Es gibt diverse Ansätze, um Ajax im Typo3 Frontend zu nutzen. Wir haben hier einen für uns doch sehr einfachen und praktikablen Weg, den wir euch nicht vorenthalten wollen. Der gezeigte Weg verwendet Typo3 Extbase und jQuery, da wir jQuery häufig in diversen Projekten verwenden. Eingesetzt kann die Technik z.B. für das Nachladen von Bildern, Beiträgen, Kommentaren, usw. werden oder um einfach einen Status abzufragen oder ein Formular zu senden.

Erster Schritt besteht darin, die Typoscript Konfiguration in der jeweiligen Erweiterung vorzunehmen. Dies kann entweder über die Datei Configuration/Typoscript/setup.txt, welche im Backend bei einem neuen Template-Datensatz zugewiesen wird, oder über die Datei ext_typoscript_setup.txt, welche standardmäßig eingebunden wird, vorgenommen werden. Um die Ajax-Anfragen für diverse Plugins regeln zu können, erstellen wir unterschiedliche PAGE-Types. Beim Aufruf einer URL mit angegebenem PAGE-Type wird nur das angegebene Plugin ausgeführt. Header-Code usw. wird entfernt:

# plugin configurationplugin.txvaajax {   ...   settings {      # default items per page for asynchronous requests      defaultItemsPerPage = 12             # ajax page types      ajax{         pluginOne = 765         pluginTwo = 876      }   }} # ajax action plugin oneajax_vaajaxtest_pluginone = PAGEajax_vaajaxtest_pluginone {   typeNum < plugin.tx_vaajaxtest.settings.ajax.pluginOne   10 < tt_content.list.20.vaajaxtest_pluginone     config {      disableAllHeaderCode = 1      additionalHeaders = Content-type:text/html      xhtml_cleaning = 0      admPanel = 0      debug = 0      no_cache = 1   }} # ajax action plugin twoajax_vaajaxtest_plugintwo < ajax_vaajaxtest_pluginoneajax_vaajaxtest_plugintwo {   typeNum < plugin.tx_vaajaxtest.settings.ajax.pluginTwo   10 < tt_content.list.20.vaajaxtest_plugintwo   config {      additionalHeaders = Content-type:application/json   }}

Jetzt können wir unsere Anfragen an den entsprechenden PAGE-Type schicken. Dies kann das Aufrufen eines Links oder auch das Absenden eines Formulars sein. Plugin One liefert das Ergebnis als HTML zurück, Plugin Two als Json, deshalb haben wir hier beim 2. PAGE-Type additionalHeaders auf den Content-Type application/json gesetzt. Wir deaktivieren auch das Caching für die jeweiligen Plugins. Ein Button der z.B. weitere Inhalte asynchron nachlädt kann wie folgt aussehen:

<a class="button load-more-items" data-page="{page}" data-url="{f:uri.action(action: 'more', controller: 'Item', pluginName: 'PluginOne', pageType:'{settings.ajax.pluginOne}')}">   mehr laden</a>

Als data-Attribut übergeben wir hier die aktuelle Seite (data-page) und die URL die aufgerufen werden soll (data-url). Damit der Button auch eine Funktion hat, muss per Javascript/jQuery noch ein entsprechender Click-Handler angelegt werden. Vereinfacht kann das ganze so aussehen:

jQuery('.load-more-items').on('click', function(){   var url = jQuery(this).data('url');   var page = jQuery(this).data('page');   var format = 'html';    // if set use the format from the data attribute   if(jQuery(this).data('format')){      format = jQuery(this).data('format');   }    // send request   jQuery.ajax({      type: "POST",      url: url,      headers: {         'Cache-Control': 'no-cache, no-store, must-revalidate',         'Pragma': 'no-cache',         'Expires': '0'      },      dataType: format,      data: data,      success: function (content) {         // do something with your loaded content         // remove old more-button         // init new more-buttons      }   });});

In unserem Controller können wir jetzt verschiedene Actions anlegen. Eine für unsere Standard-Listenansicht und eine zweite, die das Ausliefern des nachgeladenen Contents regelt:

class ItemController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {   public function listAction() {      ...      // get next items, set itemsPerPage and next page      $next = $itemRepository->findItems(intval($this->settings['defaultItemsPerPage']), 1);       $this->view->assign('settings', $this->settings);      $this->view->assign('next', $next->count());   }    public function moreAction() {      ...      $itemsPerPage = $this->settings['defaultItemsPerPage']      $page = 0;       if ($this->request->hasArgument('page')) {         $page = intval($this->request->getArgument('page'));      }       // get items, set items per page and next page      $next = $itemRepository->findItems(intval($this->settings['defaultItemsPerPage']), $page);       // get next items, set items per page and next page      $next = $itemRepository->findItems(intval($this->settings['defaultItemsPerPage']), $page + 1);       $this->view->assign('settings', $this->settings);      $this->view->assign('items', $items);      $this->view->assign('next', $next->count());      $this->view->assign('page', $page + 1);   }}

Die nachgeladenen Items werden mittels eines Fluid-Templates ausgegeben. Dort setzen wir auch wieder unseren neuen „Mehr laden“-Button, falls weitere Items vorhanden sind:

<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"      xmlns:ce="http://typo3.org/ns/TYPO3/CMS/FluidStyledContent/ViewHelpers"      xmlns:v="http://typo3.org/ns/FluidTYPO3/Vhs/ViewHelpers"      data-namespace-typo3-fluid="true">    <f:if condition="{items}">      <f:then>         <f:for each="{items}" as="item" iteration="itemIterator">            <f:render partial="Item/Item" arguments="{_all}" />         </f:for>      </f:then>   </f:if>   <f:if condition="{next}">      <a class="button load-more-items" data-page="{page}" data-url="{f:uri.action(action: 'more', controller: 'Item', pluginName: 'PluginOne', pageType:'{settings.ajax.pluginOne}')}">         mehr laden      </a>   </f:if>

Im Javascript-Teil erhalten wir genau diesen Inhalt als content zurück und können die nachgeladenen Items in einen Container laden. Und das wars auch schon wieder!

Viel Spaß und Erfolg beim Ausprobieren.

PS: Um eine API/RESTful-Schnittstelle mit TYPO3 zu entwerfen, empfehlen wir dir diesen Blogbeitrag: REST API mit TYPO3 (REST Server).