Front end form validation


#1

Silverstripe Version: 4.1

I’m trying to raise some custom errors in the form submit handler. I can get it done but then it won’t reload the form data.

The docs (https://docs.silverstripe.org/en/4/developer_guides/forms/validation) are saying to use a function that doesn’t exist ($form->addErrorMessage).

Doing this in the submit handler

public function doEventForm($data, Form $form)
{
	if ($data['Title'] != 'potato')
	{
		$form->sessionError('There\'s an error in the form');
	}

	return $this->redirectBack();
}

Does set the form to invalid but seems to wipe the data. If I take the $form->sessionError() out, the form reloads with data but of course no error.

It was my assumption that after a failed submit the data that was sent should persist.

When building the form I do:


$res = $form->getSessionValidationResult();

if (is_null($res))
{
	# First load
	if ($this->ExistingID)
	{
		# Populate the form with existing event (edit or duplicate)
		$event = Event::get_by_id('Event', $this->ExistingID);
		$form->loadDataFrom($event);
	}
}
else if ( ! $res->isValid())
{
	# No valid, load from session via Form->restoreFormState()
}

What have I done wrong?

Similar battle with setting a message for a field. In the end I did:

public function doEventForm($data, Form $form)
{
	//$validator = $form->getValidator();

	if ($data['Title'] != 'potato')
	{
		$result = ValidationResult::create();
		$result->addFieldError('Title', 'that is not potato.');
		$form->setSessionValidationResult($result, true);

		$form->sessionError('There\'s an error in the form');
	}

	return $this->redirectBack();
}

That’s not the right way to do it is it?


#5

Form::addErrorMessage was removed in SS4, unfortunately without deprecation. Related Issue: addErrorMessage dont exist anymore · Issue #8640 · silverstripe/silverstripe-framework · GitHub

I opened one at the lessons repository: SS4: Form::addErrorMessage doesn't exist any more · Issue #77 · silverstripe/silverstripe-lessons-v4 · GitHub

@robbieaverill quoted in the issue above from the SS4 changelog:

Removed addErrorMessage(). Use sessionMessage() or sessionError() to add a form level message, throw a ValidationException during submission, or add a custom validator.


#2

Have a look at https://www.silverstripe.org/learn/lessons/v4/introduction-to-frontend-forms-1. Preserving state is what you are looking for. Uncle Cheese explains it very well there.


#3

@Greg_808 - He is using the same non existent function $Form->addErrorMessage

[Emergency] Uncaught BadMethodCallException: Object->__call(): the method ‘addErrorMessage’ does not exist on 'SilverStripe\Forms\Form

POST /manage/EventForm/

Line 54 in /var/www/html/vendor/silverstripe/framework/src/Core/CustomMethods.php

With the form repopulating I got that to work by manually adding $form->setSessionData($data);.

That uses a slightly different session though than the lesson. FormInfo. versus FormData. from the lesson. Seems odd to have to do that manually though?

I’m still stuck on how to invalidate the form and add a message or CSS class to the error’ed field.

# doEventForm()
if ($data['Title'] != 'spud')
{
    $form->sessionMessage('Title is not spud','bad'); # Not sessionError any more
    $validator = $form->getValidator();
    $validator->validationError('Title', 'Still not spud', 'bad');
    return $this->redirectBack();
}

I thought this might work but no joy.

# Template

$Fields.dataFieldByName('Title')

<% if $Fields.dataFieldByName('Title').Message %>
    <div class="message $Fields.dataFieldByName('Title').MessageType">
        $Fields.dataFieldByName('Title').Message
    </div>
<% end_if %>

Can you just not do this on submission like I’m trying? Have to sub class every field and wrote a custom validate() method on it? If that’s the case, how would you send data to that fields validate method that is from a different field? For example, the $ID value to tell if it’s an edit or create.


#4

Sorry i didn’t know that the lessons are not up to date. When i started of they where great.


#6

No worries mate. Lessons are a great start for sure.


#7

I came across this and below is the approach I took which was to re-create the addErrorMessage that has been removed. Create your own session array of form validation errors and then use setFieldMessage to add error messages to your form when the page is reloaded after validation errors. This is cut down code from a live project:

//Product Enquiry Form
public function ProductEnquiryForm(){ 
	
  $fields = FieldList::create();
 
  $fields->push(TextField::create('Name', '')
	->setAttribute("tabindex", "1")
	->setAttribute("placeholder", "Name...")
	->setAttribute("class", "form-control")
	->setAttribute("autocomplete", "name")
  );

  $actions = FieldList::create(FormAction::create('doProductFormSubmit', 'Send'));
	
  $form = new Form($this, 'ProductEnquiryForm', $fields, $actions);
  	
  //validation messages
  $errors = $this->getRequest()->getSession()->get("FormData.{$form->getName()}.errors");
	
  if(!empty($errors)){
	foreach($errors as $key => $value ){
		$form->setFieldMessage($key, $value, 'error');
	}	
  }

  $data = $this->getRequest()->getSession()->get("FormData.{$form->getName()}.data");
  return $data ? $form->loadDataFrom($data) : $form;
}

public function doProductFormSubmit($data, $form){
	
	$session = $this->getRequest()->getSession();

	//Error Validation
	$errors = array();
	
	if(strlen($data['Name']) < 2){
		$errors['Name'] = 'Please enter your name.';
	}
   	
	//add more validation rules....
	
    if(!empty($errors)){
	 	  //set errors...
		  $session->set("FormData.{$form->getName()}.errors", $errors);
		  $session->set("FormData.{$form->getName()}.data", $data);
		  return $this->redirectBack();
	}
	
	//if all is good then clear session data
	$session->clear("FormData.{$form->getName()}.errors");
	$session->clear("FormData.{$form->getName()}.data");
	
	//Do rest of your form processing i.e. sending emails etc...
}

Others may have a better way of doing this but it is working for me. Hope it helps…


#8

Hey James thanks for that.

I can work with that if there’s no other more uhm, “SilverStripe-y” way.

Doesn’t this seem like a bug?

I guess what we’re meant to be doing is creating a custom validator for each field and running it’s validate() method. Less overhead than subclassing the whole Field but still, bit laborious for simple things.

CompositeField doesn’t take a validator though so it wouldn’t get it done anyway.


#9

Ugh. A CompositeField can’t even have a message set on it. Form::setFieldMessage only sets them on fields returned with dataFieldByName. The functions comment " Set message on a given field name. This message will not persist via redirect." doesn’t tell you that.

Any way to set a message on a CompositeField?

Edit: Can set a message on it if you get the field from the FieldList with fieldByName() then setMessage().