Photo of a computer screen showing some HTML Code.
Formular per Ajax

EXT:form (TYPO3 8.7) Formular mit typoscript_rendering per Ajax versenden

Formular mit typoscript_rendering per Ajax verschicken

Formulare mit der neuen form-Extension per Ajax zu verschicken ist mit der Extension typoscript_rendering erfreulich leicht.

Update 18.02.2018: Ein Leser schrieb mir, dass beim zweiten Mal absenden das Formular zwar die Bestätigungsseite zeigt, jedoch keine Mail versendet wird. Da dies am TYPO3 Cache lag war die Lösung dafür, dem uri.ajaxAction-Vielhelper beim Aufruf zusätzlich noch den Parameter no_cache=1 mitzugeben. Ich habe das unten entsprechend angepasst.

Update 01.11.2022: Für TYPO3 v11 wurde die ajaxuri im Form.html angepasst. Vielen Dank an Ralf (siehe Kommentare). Der no-cache Parameter wird wohl nicht mehr benötigt und entfernt. Die Ermittlung von objData mit v:content.info() und pageData mit v:page.info() funktioniert hier mit vhs 6.1.2.

Mit TYPO3 8.5 wurde eine komplett neue EXT:form eingeführt, die große Ziele hat. Sie soll einerseits für den Editor leicht benutzbar, durch Integratoren und Entwickler aber ebenso gut anpassbar sein. Mit dem Upgrade dieser Seite auf 8.7 und dem Ende von formhandler, den ich zuvor eingesetzt hatte, habe ich dann auch auf die neue form Extension gewechselt. Es gab "damals" kaum Doku, nur ein paar veraltete Beispiele in einem GIT-Repo. Das hat sich heute glücklicherweise gebessert, es ist mittlerweile eine recht umfangreiche Doku zu EXT:form vorhanden.

Worauf die Doku noch nicht eingeht ist, wie man Formulare per Ajax verschicken kann. Evtl. ist das aber gar nicht nötig, denn wenn man die Extension typoscript_rendering einsetzt, dann ist das erstaunlich einfach und schnell umzusetzen.

typoscript_rendering

Als erstes muss die typoscript_rendering Extension installiert werden. Einen Blick ins Handbuch kann man sich hier sparen, da ist im Moment nur Dummy-Text, aber das ist auch straight-forward: Einfach über den Extension-Manager (oder composer) installieren. Es gibt auch bereits eine offizielle Version für 8.7.

Da man sich die Infos aus einigen Blog-Posts zusamensuchen muss erkläre ich hier noch verkürzt, was die Extension macht:

Die Extension erlaubt es, ein einzelnes Plugin samt Controller, Action, Parametern usw. auf einer bestimmten Seite aufzurufen und gibt direkt dessen Ausgabe zurück. Zwei andere Ansätze dafür wären das ganze über einen eigenen Seitentyp (?type=12345) zu lösen und dort die Ajax-Antwort zu rendern oder mit dem moderneren eID Ansatz zu arbeiten. Für den ersten Weg muss das komplette Frontend gerendert werden, während bei eID sehr wenig geladen wird, damit aber auch nicht zur Verfügung steht. Die typoscript_rendering Extension steht hier irgendwo dazwischen, da sie den Content dann ausgibt, wenn alles Notwendige geladen ist, dann aber das weitere Frontend-Rendering abbricht. Die Extension bringt ausserdem ein paar Viewhelper mit um einfach URLs für Aufrufe von typoscript_rendering in Fluid-Templates zu generieren.

Form.html Template erweitern um ein ajaxuri data Attribut

Nun muss das Form.html Template erweitert werden. Dieses befindet sich in typo3/sysext/form/Resources/Private/Frontend/Templates/Form.html, wird aber natürlich nicht dort angepasst, sondern kopiert und per templateRootPaths verwendet.

Da sowohl die ID der Seite mit dem Formular als auch die tt_content ID benötigt wird und EXT:form diese Werte (derzeit) nicht im Frontend verfügbar macht müssen wir ausserdem die vhs-Viewhelper benutzen, um an diese Daten zu kommen.

Das komplette Template sieht bei mir nach der Anpassung so aus:

<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
      xmlns:formvh="http://typo3.org/ns/TYPO3/CMS/Form/ViewHelpers"
      xmlns:v="http://typo3.org/ns/FluidTYPO3/Vhs/ViewHelpers"
      xmlns:t="http://typo3.org/ns/Helhum/TyposcriptRendering/ViewHelpers"
      data-namespace-typo3-fluid="true">
<div class="ext_form">
    <v:variable.set name="objData" value="{v:content.info()}"/>
    <v:variable.set name="pageData" value="{v:page.info()}"/>
    <formvh:renderRenderable renderable="{form}">
        <formvh:form
                object="{form}"
                action="{form.renderingOptions.controllerAction}"
                method="{form.renderingOptions.httpMethod}"
                id="{form.identifier}"
                section="{form.identifier}"
                enctype="{form.renderingOptions.httpEnctype}"
                addQueryString="{form.renderingOptions.addQueryString}"
                argumentsToBeExcludedFromQueryString="{form.renderingOptions.argumentsToBeExcludedFromQueryString}"
                additionalParams="{form.renderingOptions.additionalParams}"
                additionalAttributes="{data-ajaxuri: '{t:uri.ajaxAction(action:\'perform\',contextRecord: \'tt_content:{objData.uid}\',pageUid: \'{pageData.uid}\')}'}"
        >
            <f:render partial="{form.currentPage.templateName}" arguments="{page: form.currentPage}"/>
            <div class="actions">
                <f:render partial="Form/Navigation" arguments="{form: form}"/>
            </div>
        </formvh:form>
    </formvh:renderRenderable>
</div>
</html>

JavaScript um das Formular per Ajax zu versenden

Um das Versenden des Formulars per JavaScript zu vereinfachen benutze ich das jQuery Form Plugin.

Mit folgender Funktion (die man nach dem Document Ready aufrufen sollte) wird das Formular dann ajaxifiziert:

function initAjaxForms() {
    $('form[data-ajaxuri]').each(function() {
        var form = $(this);
        var form_id = '#' + form.attr('id');
        var ajaxuri = form.attr("data-ajaxuri");
        var options = {
            target: form_id,
            url: ajaxuri,
            success: function() {
                // re-init ajax forms
                initAjaxForms();
                form.fadeIn('slow');
            }
        };
        form.ajaxForm(options);
    })
};

Das war auch schon alles, was nötig ist, um EXT:form Formulare per Ajax zu versenden. Schön an dieser Lösung ist übrigens auch, dass das ganze auch ohne JavaScript funktioniert, da das eigentliche Form-Target ja nicht verändert wurde. Ohne JavaScript gibt es dann eben nur kein Ajax-Submit.

Bonus: Formular in Overlay öffnen

Man kann das Formular mit dieser Technik nicht nur per Ajax versenden, sondern auch direkt in einem Overlay (Modal, Lightbox) öffnen. Das funktioniert ähnlich und ebenfalls mit dem t:uri.ajaxAction Viewhelper, allerdings muss man diesem hier ein paar mehr Optionen mitgeben:

{namespace t=Helhum\TyposcriptRendering\ViewHelpers}
<f:comment>
  Two variables need to be configured (or hard coded)
  * settings.site.pid.contact - PID of a page containing the (contact) form
  * settings.content.uid.contact - tt_content UID of the form plugin to render
</f:comment>
<f:link.page class="nav-link icon-mail modal-ajax" pageUid="{settings.site.pid.contact}" title="Kontakt"
             data="{toggle: 'modal', target: '#ajax-modal', modal-title: 'Nachricht senden', modal-footer:'<p>Alternativ können Sie mir auch eine Nachricht per <a href=\'{f:uri.page(pageUid: settings.site.pid.contact, section: \'c87\')}\'>Post oder Email</a> senden.</p>'}"
             additionalAttributes="{data-ajaxuri: '{t:uri.ajaxAction(contextRecord: \'tt_content:{settings.content.uid.contact}\',pageUid: \'{settings.site.pid.contact}\',extensionName: \'form\', pluginName: \'formframework\', controller: \'FormFrontend\', action: \'render\')}'}"
    >
    <a class="sr-only">Kontakt</a>
</f:link.page>

Hier verwende ich ein Bootstrap 4 Modal.

Zum Öffnen des Modals und laden des Formulars kommt wieder JavaScript zum Einsatz:

function initModalAjax() {
    $('#ajax-modal').on('show.bs.modal', function (event) {
        // reset previously set content
        var modal = $(this);
        modal.find('.modal-title').text('');
        modal.find('.modal-footer').html('');
        var button = $(event.relatedTarget); // Button that triggered the modal
        var title = button.data('modal-title'); // Extract info from data-* attributes
        var text = button.data('modal-text');
        var footer = button.data('modal-footer');
        modal.find('.modal-title').text(title);
        modal.find('.modal-footer').html(footer);
    });
    function loadContent(trigger) {
        $(trigger.data("target") + ' .modal-body').load(
            trigger.attr("data-ajaxuri"),
            function () {
                // other functions to call after the content has been loaded
                // ajaxify the just loaded form
                initAjaxForms();
            }
        );
    }
    $('body').on('click', '.modal-ajax[data-toggle="modal"]', function (e) {
        loadContent($(this));
        e.preventDefault();
    });
}

Der Vollständigkeit halber noch das zugehörige HTML für das Modal:

<div class="modal fade" id="ajax-modal" tabindex="-1" role="dialog" aria-labelledby="ajaxModalLabel" aria-hidden="true">
    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
        <span aria-hidden="true">&times;</span>
    </button>
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title" id="ajaxModalLabel"></h5>
            </div>
            <div class="modal-text"></div>
            <div class="modal-body">
            </div>
            <div class="modal-footer"></div>
        </div>
    </div>
</div>

Kommentare (18)

  1. Andreas Kokott
    Andreas Kokott am 17.02.2020
    Schöne Anleitung hat mir sehr geholfen, hatte allerdings Probleme wenn nach dem Absenden auf eine andere Seite weitergeleitet werden sollte.
    Lösung war im success Callback die Ersetzung des Formularinhalts händisch vorzunehmen und die Antwort selbst zu parsen. Das "target" in den ajaxForm options darf dann allerdings nicht gesetzt sein.
    Ungefähr sowas:
    success: function (responseText, statusText, xhr, $form) {
    var thisform = $(responseText).filter('form'+form_id);
    if(thisform.length) {
    form.html(thisform.html())
    } else {
    var nnodes = $.parseHTML(responseText)
    var newLocation = $(nnodes).filter('link[rel="canonical"]')[0]['href'];
    window.location = newLocation;
    }
    // re-init ajax forms
    initForms(form);
    }
  2. Vasyl Mosiychuk
    Vasyl Mosiychuk am 17.02.2020
    Hello! I am hard in your code because I have only some knowledge of JS. Do you have an example without a modal function? I tried using your example but it does not work in my case and I do not understand why.
    I have the page together with form without a modal window.
  3. Alla Barabash
    Alla Barabash am 17.02.2020
    Hello! Thank you for your solution. Did you try this with felogin and Femanager?
  4. sciloqi
    sciloqi am 30.06.2020
    Ich sag mal einfach "Danke!"
  5. Chris
    Chris am 11.05.2021
    So wie es aussieht funktioniert das seit 9.5.25/26 nicht mehr. Mit vorherigen Versionen klappt es noch einwandfrei. Hast Du da eine Idee oder Alternative?

    Hat evtl. hiermit zu tun: https://github.com/TYPO3/TYPO3.CMS/commit/66b75cecaba51df1526821dda113dc071b482d94#diff-31d13456cf6ab5e4ff6c78e4957376b6f409aa16bc2aa104c18ee4cff238772e
  6. sciloqi
    sciloqi am 11.05.2021
    @chris: Kann ich bestätigen mit 10.4.15/16, noch keine Lösung gefunden...
  7. Manuel Munz
    Manuel Munz am 11.05.2021
    @chris @sciloqi Ich bin noch nicht in das Problem gelaufen. Ich sollte wohl mal updaten. in dem verlinkten commit ist aber ja von einem neuen _session Parameter die Rede. Den noch ins eigene Form zu packen habt ihr wahrscheinlich versucht schon? Ansonsten kann ich gerne nochmal kommentieren, falls ich ne Lösung dafür finde, bin aber auch überhaupt nicht böse, falls da jemand schnelelr was findet.
  8. Chris
    Chris am 25.05.2021
    @Manuel Munz: Ich habe es ehrlich gesagt nicht zum Laufen gebracht. Wir hebeln Teile der Änderungen aktuell per Composer Core Patch aus. Sicher nicht Sinn und Zweck aber wenn Du noch eine tolle Lösung hast gerne mal updaten oder kommentieren. Ist mir eh rein Rätsel weshalb Ajaxsubmit nicht von Haus aus eingebaut ist.
  9. sciloqi
    sciloqi am 03.06.2021
    @manuel @chris: Ich hatte leider auch keinen Erfolg bisher, auch nicht mit dem _session Parameter (wobei ich mir unsicher bin, ob ich das richtig gemacht habe...). Ich hab mich auch schon gefragt, ob nicht auch die TyposcriptRendering Extension dafür angepasst werden müsste, denn diese verweigert ja die Annahme und Weitergabe des _session Parameters...
  10. Ralf
    Ralf am 04.06.2021
    Habe heute über dem gleichen Problem gebrütet. Die Lösung ist: Die 'ajaxuri' hat sich geändert.
    Statt
    additionalAttributes="{data-ajaxuri: '{t:uri.ajaxAction(additionalParams:\'{no_cache:1}\',contextRecord: \'tt_content:{objData.uid}\',pageUid: \'{pageData.uid}\')}'}"
    muss es
    additionalAttributes="{data-ajaxuri: '{t:uri.ajaxAction(action:\'perform\',contextRecord: \'tt_content:{objData.uid}\',pageUid: \'{pageData.uid}\')}'}"
    heißen.
    Also "no_cache=1" raus und dafür "action='perform'" rein.
  11. Ralf
    Ralf am 04.06.2021
    Ach ja: Aloha zusammen! :)
  12. sciloqi
    sciloqi am 11.06.2021
    @ralf – erfolgreich getestet, besten Dank!
  13. Chris
    Chris am 19.08.2021
    Danke Ralf :-)
    Läuft wieder einwandfrei!
  14. Robert
    Robert am 18.10.2021
    @Ralf: Danke, Danke, Danke!!! Das hat mir gerade sehr geholfen nachdem ich verzweifelt auf Fehlersuche war. Tolle Arbeit =)
  15. Andreas
    Andreas am 13.04.2022
    TYPO3 10.4
    Ich bringe es einfach nicht zum laufen..
    Es verhält sich einfach genauso wie vorher. Alles funktioniert.. Nur nicht per Ajax!
    Der Absendebutton führt den Request aus. Fehlt da nicht sowas wie ein preventDefault()?
    Gibt es mittlerweile nicht eine Lösung mit der man die Forms-Extension einfach auf AJAX umschalten kann um einen Request innerhalb eines Contentelemets abzuwickeln?
  16. Kay
    Kay am 10.08.2022
    Unter TYPO3 11.5 mit VHS 6.1.2 funktioniert die Ermittlung der benötigten Inhaltselement-UID (objData.uid) mit "v:content.info" nicht mehr. Wir haben uns dafür einen Bugfix gebastelt, welchen wir auch im Issue-Tracker von VHS beschrieben haben. Falls jemand in v11 ebenfalls darüber stolpert und verzweifelt. Bis Version 10.4 läuft noch alles.

    https://github.com/FluidTYPO3/vhs/issues/1799
  17. Manuel Munz
    Manuel Munz am 01.11.2022
    Ich hab die Seite hier eben auf TYPO3 v11 und vhs 6.1.2 upgegraded. Bis auf die Anpassung der ajaxuri (s.o.) musste ich sonst nichts machen, damit das Form weitergeht.
  18. Markus
    Markus am 28.09.2023
    Hallo zusammen,

    nur kurz zur Info, weil ich das eben mal wieder hatte:
    das ajax-submit mit dem normalen Form geht nicht (mehr) weil im standard Partials/Form/Navigation.html
    inzwischen ein <button> als Form-Element drin ist...

    Wenn man das ganze per javascript submitten will, fehlt der extbase validierung dann der Wert vom input - daher muss man das Partial ergänzen, ich hab das mit einem

    <f:form.hidden property="__currentPage" value="{form.pages -> f:count()}" />

    ergänzt, dann geht das (wieder).

Neuen Kommentar schreiben

Bild eines gelben Schilds, auf dem 'Blogger fütternewCommentn verboten' steht.