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">×</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)