All,
Here is my solution to implementing inline errors in your custom form for server-side validations. In summary, I am going to use a hidden form field to store error messages that were generated within the validateForm method and then use JQuery to parse and output the results. The sample code are snippets of actual code of a module I created. It has been heavily edited for demonstrative purposes.
Below is the sample form code. While I am using text boxes, the solution does work on other form elements. You may need to adjust the jQuery script depending on your theme.
public function buildForm (array $form, FormStateInterface $form_state) {
// Embed my jQuery script
$form['#attached']['library'] = array(
'my_module/my_module_inline_errors',
);
// Field Test 1
$form['field_test_1'] = array(
'#type' => 'textfield',
'#title' => t('Field 1'),
);
// Field Test 2
$form['field_test_2'] = array(
'#type' => 'textfield',
'#title' => t('Field 2'),
);
// Field Test 3
$form['field_test_3'] = array(
'#type' => 'textfield',
'#title' => t('Field 3'),
);
// Submit
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form ;
}
public function validateForm (array &$form, FormStateInterface $form_state) {
// INIT
$values = $form_state->getValues();
// Validate
if ( $values['field_test_1'] == '' ) {
$form_state->setErrorByName('field_test_1', t('Error 1'));
}
if ( $values['field_test_2'] == '' ) {
$form_state->setErrorByName('field_test_2', t('Error 2'));
}
if ( $values['field_test_3'] == '' ) {
$form_state->setErrorByName('field_test_3', t('Error 3'));
}
// If validation errors, save them to the hidden form field in JSON format
if ( $errors = $form_state->getErrors() ) {
$form['my_module_error_msgs']['#value'] = json_encode($errors);
}
return;
}
As you can see, the code is very straightforward. The buildForm is business as usual in that we are defining 4 form elements (3 text boxes and 1 submit button). Note that we are also attaching our jQuery script which will be discussed later on
Within the validateForm method I am checking to see if any errors were previously detected. If so then we retrieve the associative array of errors from $form_state, transform it into a JSON string and save it in the hidden form field. Note that the array keys will be the field names and the values will be the error messages.
In the next section of code, we are going to automatically alter the form using hook_form_alter.
/**
* Implments hook_form_alter
**/
function my_module_form_alter (&$form, FormStateInterface &$form_state) {
// Create hidden field to store error messages
$form['my_module_error_msgs']['#type'] = 'hidden';
// Automatically add ID attribute for each form element
foreach ($form as $key => $frm) {
if ( stristr($key, 'field_' ) ) {
$form[$key]['#id'] = $key;
}
}
}
So here I am taking advantage of hook_form_alter to automate a few things that help simplify the buildForm code. This also allows me to create other custom forms with very minimal code additions.
Note that I like to standardize on a naming convention for my form field names, hence they all begin with the prefix "field_". The purpose of the foreach loop is to overwrite the Drupal default ID attribute with the element name and again minimizes the code within buildForm. As you will see in the next example, using this naming convention has its advantages.
// my_module_inline_errors.js
jQuery(document).ready(function($){
var form_errors = $('input[name="my_module_error_msgs"]').val();
if (form_errors) {
form_errors = JSON.parse(form_errors);
var keys = Object.keys(form_errors);
var count = Object.keys(form_errors).length
for (i = 0; i < count; i++) {
msg = form_errors[keys[i]];
$('#' + keys[i]).after('<div class="my-module-inline-error">' + msg + '</div>' );
}
}
});
And so here is where the magic happens. When this JQuery script is loaded, it will check to see if any error messages have been deposited in the hidden field. If found the JSON string is converted to a JSON object. By parsing through the entire array, I can identify the invalid field and append the error message at the end of the input field. The CSS class can be customized to control proper positioning and highlighting.
My JQuery selector locates each field by ID which as you will recall, I purposely set this value in hook_form_alter. This method of searching in jQuery is much more efficient than searching via the name attribute (i.e $('input[name="field_name"]')
). Regardless, you can choose whatever method works for you.
So the overall advantages for me is simplicity. Previously I tried to store my errors messages in a session variable but this will not work within the life cycle of the FormAPI. The idea was within buildForm to fetch the error messages from the session variable and output them within buildForm. The problem appeared to be with the form cache and my messages would only appear if I refresh the page. I have previously posted this question seeking help on this forum. I am fully aware there is an ongoing project to develop Inline Errors for Drupal 8 as a module and I have tried to contact the project members but to no avail.
Anyway I am very happy with this and would love to hear your comments and feedback,
Cheers
Comments
Bette, simpler and beautiful solution
Hello,
I just wrote a full article about my implementation of inline validations for Drupal 8 !
https://antistatique.net/en/we/blog/2017/02/16/drupal-8-inline-messages-...
It's fare simpler because I use the Form API whitout using Javascript or externals scripts !
Check it out and leave a comment if you found a better solution.
Awesome. I'll give it a look
Awesome. I'll give it a look later and let you know.
Inline Form Validation seems not to work with 8.3.1
Hi,
Thanks for your great Tutorial.
But I am not able to get the inline validations against the example form in tutorial.
It seems something changed in latest release. Can you please help.
Thank you very much again.
Regards
Amit
Help
I can help you but I need some code, could you post your code on a gist ?
Custom validations appearing 2 times.
Hi Wengerk,
Thanks for your response.
I am just using the code provided by you in above mentioned link. (After success will start with requirement (form))
The problem I am facing here is on submit I am getting the Flash/Growl messages with Inline messages.
Although I have used
$form['#attributes']['novalidate'] = 'novalidate';
, but it doesn't show any effect.I can hide the Flash/Growl messages with CSS but looking for a better approach.
Also if you can guide me how to achieve Live validation Approach, it will be great.
Thanks again.
Regards
Amit Dwivedi
Flash/Growl messages
Yeah, the growl messages are generated because I use the standard way to set error using
setErrorByName
.The
$form['#attributes']['novalidate'] = 'novalidate';
is only need to avoid HTML5 validations.To remove all the grow messages you must add some code after the loop
// Add error to fields using Symfony Accessor
.Eg.
$form_state->clearErrors();
.So in my example you will get something like:
But be carefull @amit-dwivedi, for accessiblities it's a very good approach to have both inline and growl messages :D.
The trouble with FormState:
The trouble with FormState::clearErrors() is that it stops the validation and the form goes to the submit step. The whole point of using setErrorByName() is that it returns the form with an error on it.
I've been looking for a way around this for a while now, but not having much luck.
It's a real shame that the static method FormState::setAnyErrors() is protected. This seems to be the only way to stop the form from submitting. It gets called in setErrorByname().
I can't intercept the messages at the drupal_set_message() level due to the way Drupal stores them in the session. I tried to intercept them in my own validation callback, but of course it's firing before the messages are set. I don't see anywhere that I can intercept them. preprocess_status_messages() is not useful for me. I don't want to remove all messages, or remove them by matching to a long string with variables in it.
It's also annoying that the preprocess for status_messages calls drupal_get_messages(), instead of being passed the messages in $variables. This seems like an unDrupal way of doing things. If I want to render some status messages I have to do it myself without using the existing status_messages template, because that template has a destructive preprocess and cannot be passed the messages you want to render.
Yeah I know, I already search
Yeah I know, I already search a long time.... for me, my trait is one of the cleanest & drupal way to do the job.
I hope the core team will add some method to improve form validations/messages.
You could use "Inline form
You could use "Inline form errors" core module for this.
It's not helping
I enabled the inline form errors module. But still the error message is not getting displayed when the form is submitted. The form just stays as it. Some help here please.
Inline error message in ajax modal
Hello @wengerk,
I implement it with " class="use-ajax" data-dialog-type="modal" in hook form alter on submit but i am not better luck. Please review my code .
if ($form_id == 'node_abcd_form') {
$form['#prefix'] ='<span class="file-valid-message"></span>';
$form['#cache']['max-age'] = 0;
$term_id = \Drupal::request()->query->get('termid');
$form['field_reference']['widget'][0]['target_id']['#default_value'] = \Drupal\taxonomy\Entity\Term::load($term_id);
$form['actions']['submit']['#ajax']=[
'callback' => 'validateDocAjax',
'event' => 'click',
];
}
*
* Ajax callback to validate the Document field in toolkit content type.
*/
function validateDocAjax(array &$form, FormStateInterface $form_state) {
/*$response = new \Drupal\Core\Ajax\AjaxResponse();
$message = 'Please fill the form with title and document';
if (!$form_state->hasAnyErrors()) {
$response->addCommand(new \Drupal\Core\Ajax\CloseDialogCommand());
$destination = \Drupal::destination()->get();
$response->addCommand(new RedirectCommand($destination));
return $response;
} else
{
$response->addCommand(new HtmlCommand('.file-valid-message', $message));
return $response;
}*/
// Assert the firstname is valid
if (!$form_state->getValue('title') || empty($form_state->getValue('title'))) {
$form_state->setErrorByName('title', 'Votre prénom est obligatoire.');
}
}
// Its show an ajax error message and also not show inline message
AjaxError:
An AJAX HTTP error occurred.
HTTP Result Code: 200
Debugging information follows.
Path: /node/add/abcd?destination=/{{ same page }}&_wrapper_format=drupal_modal&ajax_form=1
StatusText: OK
ResponseText:
Hello,
Hello,
I think you should ask your question on Stackoverflow, you would have a lot better chance from people to answer your questions there ^^.
Stackoverflow Drupal 8 Tagged -> https://stackoverflow.com/questions/tagged/drupal-8
Drupal Stack -> https://drupal.stackexchange.com
Also, just to give you a quick answer - I would suggest you use the Inline Form Error Core module which helps you way more than this old-fashioned way.
Little details please.
I am new to drupal. I face the same issue.
I made changes but it's not working. It shows some internal error occurred in the website. Can anyone help me out?
Did you try
Sorry didn't see your previous message .....
Could you be more precise where the error occurs ? It's a custom form or in the Drupal admin UI ?
Hello !Since Drupal 8.4.x Drupal has a stable Core module Inline Form Errors which perform exactly what you need. Did you try it ?https://www.drupal.org/docs/8/core/modules/inline-form-errors/inline-for...In the drupal admin UI.
Actually i am creating a page which has a form and it contains details of a specific product. If the user tries to input the product name twice, it should print the error stating that the product name already exists. But for me it is not showing error and also it doesn't move to save items page as it has errors.
Without common name for fields!
Can you explain how to catch the errors without using a common name like field_
I am having fields like name, experience, ...
How can you validate using these fields.