Drupal 8 Custom Forms Inline Errors Made Easy

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 using JQuery to 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


    public function buildForm(array $form, FormStateInterface $form_state) { 

        // Add my JQuery function
        $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();

        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 any errors have occurred, then 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 thus far is very straightforward. The buildForm is business as usual and the only thing worth nothing is within validateForm where we check 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. The purpose of the foreach loop is to overwrite the Drupal default ID attribute with the element name and again minimizes the code within buidForm. As you will see in the next example, using this naming convention has its advantages.

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 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 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 but to no avail.

Anyway I am very happy with this and would love to hear your comments and feedback,

Cheers

Drupal version: 


Source: https://www.drupal.org/taxonomy/term/4/feed