Eine oft anzutreffende Variante bei der Übermittlung von Formularen via Ajax im TYPO3-Kontext ist, dass die gesamte Validierung clientseitig via Javascript / jQuery erfolgt. Das ist unschön, denn die clientseitige Validierungslogik muss hier (im besten Falle) redundant zur bestehenden Extbase Validierungslogik (Annotations im Model, eigene Validatoren usw.) implementiert werden. In der Regel widerspricht das gutem Softwaredesign.

Eleganter ist es, die in den meisten Fällen bereits auf Ebene der Domänenobjekte definierten Validierungsregeln zu nutzen. Eine Variante ist also, das Formular via Ajax-Request zu übermitteln und den entsprechenden DOM-Abschnitt mit dem Rückgabewert der aufgerufenen Controller-Actions (also dem HTML Markup der createAction() bzw. newAction() bei Validierungsfehlern) zu ersetzen (ähnlich einer statischen Umsetzung ohne Ajax). Clientseitige (redundante) Validierungslogik ist hierbei nicht erforderlich, allerdings wird immer das gesamte Markup übertragen.

Optimaler wäre also, wenn das Response-Objekt des Ajax-Calls lediglich die Validierungsfehler zurückliefern würde und die gesamte Validierungslogik weiterhin nur auf Objektebene bzw. im Controller definiert wird. Da aber sowohl initialen Anzeigen des Formulares als auch bei Validierungsfehlern die gleiche Action ausgefürt wird (Extbase ruft bei fehlerhafter Validierung die zuvor ausgeführte Action erneut auf), müsste in dieser Action theoretisch eine entsprechende Fallunterscheidung stattfinden ("Formular Markup zurückgeben oder Validierungsfehler zurückgeben"), was sich nicht sauber lösen lässt.

Der Trick ist nun, die errorAction() zu nutzen und für die eigenen Zwecke anzupassen. Die errorAction wird standardmäßig bei Validierungsfehlern aufgerufen (in dieser erfolgt eine Weiterleitung an die eigentliche Action) und lässt sich im Controller überschreiben. Ebenso kann eine eigene errorAction in der Variable $errorMethodName definiert werden. Im nachfolgenden Beispiel werden innerhalb der errorAction() die einzelnen Validation-Errors ausgewertet, Rückgabewert ist ein Array mit den fehlgeschlagenen Properties des Models. Dieses Array kann nun clientseitig weiter verarbeitet werden (Visuelle Darstellung der Validierungsfehler durch Hinzufügen der CSS-Klasse "f3-form-error" zu den betroffenen Formularfeldern o.ä.).

Extbase Controller

class TestController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {

	/**
	* action new
	*
	* @param \Vendor\Ext\Domain\Model\Test $newTest
	* @ignorevalidation $newTest
	* @return void
	*/
	public function newAction(\Vendor\Ext\Domain\Model\Test $newTest = NULL) {

		if ($newTest == NULL) {
			$newTest = $this->objectManager->get('Vendor\\Ext\\Domain\\Model\\Test'); 
		}
		$this->view->assign('newTest', $newTest);  

	}

	/**
	* action create
	*
	* @param \Vendor\Ext\Domain\Model\Test $newTest
	* @return void
	*/
	public function createAction(\Vendor\Ext\Domain\Model\Test $newTest) {
		  
		// Do anything...
		$response['status'] = 'success';
		return json_encode($response);

	}

	/**
	* action error
	* 
	* @return string
	* @api
	*/
	protected function errorAction() {

		$errors=array();
		if($this->arguments->getValidationResults()->hasErrors()){
			foreach($this->arguments->getValidationResults()->getFlattenedErrors() as $key => $error){
				$errors[] = str_replace('newTest.','',$key);
			}
		}

		$response['status'] = 'validation';
		$response['errors'] = $errors;
		return json_encode($response);        

	}       

}

Ajax Form Submit

jQuery(document).on("click", "#myform button.submit", function(e){
	e.preventDefault();
	$("#myform").find(".error").removeClass('error');
	$.ajax({
		url: "/?type=12345&tx_myext_test[action]=create&tx_myext_test[controller]=Test",
		type: "POST",
		data : $("#myform").serialize(),
		dataType: 'json',        
		success: function(response) {
			if (response['status'] == 'success') {
				// Do anything...
			} else {
				$.each(response.errors, function( index, value ){
					$("#myform").find("#"+value).addClass('error');
				});
			}            
		},
		error: function(error) {
			// Do anything...
		}
	});
});