· Nikolaos Gkionis  Â· 4 min read

Validation

Should prototypes have validation? Yes, they should. At a minimum empty field validation needs to be implemented to ensure branching is accurately rendered.

Should prototypes have validation? Yes, they should. At a minimum empty field validation needs to be implemented to ensure branching is accurately rendered.

Should prototypes have validation?

This has long been a debate among interaction designers and other DDaT staff in government. My opinion is that the prototype should be as close to the real service as possible.

Yes, there are cases where such high fidelity is not required; but, if your plan is to add usability testing to your user research, you have to include different levels of fidelity.

I recommend the following approach:

• Level 1 – Branching • Level 2 – Passing data • Level 3 – Conditional logic • Level 4 – Error validation

Working on the NCTS phase 5 prototype we have used the HTML form validation for empty fields.

<form class="form" method="post">
  <span class="govuk-caption-l">{{ data['guaranteeSummary'] }}</span>
  <fieldset
    class="govuk-fieldset"
    data-required
    data-error=" Enter the Guarantee Reference Number"
    aria-required="true"
  >
    <div class="govuk-form-group">
      <h1 class="govuk-label-wrapper">
        <label class="govuk-label govuk-label--l" for="guaranteeReference">
          What is the Guarantee Reference Number (GRN)?
        </label>
      </h1>
      <div id="guarantee-hint" class="govuk-hint">
        This can be up to 24 characters long and include both letters and numbers. For example,
        01GB01234567890123A1B2C3.
      </div>
      <p>You must make sure this reference is up to date.</p>
      <input
        class="govuk-input govuk-input--width-20"
        id="guaranteeReference"
        name="guaranteeReference"
        type="text"
        aria-describedby="guaranteeReference-hint"
      />
    </div>
  </fieldset>

  <div class="govuk-button-group govuk-!-margin-top-3">
    <button class="govuk-button" data-module="govuk-button">Save and continue</button>
  </div>
</form>

Simply put, if the user does not enter information in the input field or select a radio they get an error message.

A screenshot

However, this approach requires javaScript injected in the /javascripts folder of the project, named validation.js. You can copy the code below or download it via the link on the related content.

// Config
const defaultErrorHeading = 'There is a problem';
const defaultErrorDescription = 'Check the following';
const defaultErrorMessage = 'There is an error';

function clearValidation() {
  $('.govuk-error-summary').remove();

  $('.govuk-form-group--error').each(function () {
    $(this).removeClass('govuk-form-group--error');
  });

  $('.govuk-error-message').each(function () {
    $(this).remove();
  });

  $('.form-group-error').each(function () {
    $(this).removeClass('form-group-error');
  });
}

function checkTextFields(errors) {
  $(document)
    .find('input[type="text"],input[type="password"], textarea')
    .each(function () {
      var $fieldset = $(this).parents('fieldset');
      var label = $(this).parent().find('label').clone().children().remove().end().text();

      if (
        $fieldset.attr('data-required') !== undefined &&
        $(this).val() === '' &&
        !$(this).parent().hasClass('js-hidden')
      ) {
        if ($(this).attr('id') === undefined) {
          $(this).attr('id', $(this).attr('name'));
        }

        errors.push({
          id: $(this).attr('id'),
          name: $(this).attr('name'),
          errorMessage: $fieldset.attr('data-error') || defaultErrorMessage,
          label: label,
          type: 'text, password',
        });
      }
    });
  return;
}

function checkSelectors(errors) {
  var checked = [];

  $(document)
    .find('input[type="radio"], input[type="checkbox"]')
    .each(function () {
      var $fieldset = $(this).parents('fieldset');
      var label = $fieldset.find('legend').clone().children().remove().end().text();

      if ($fieldset.attr('data-required') !== undefined && $fieldset.find(':checked').length === 0) {
        if ($(this).attr('id') === undefined) {
          $(this).attr('id', $(this).attr('name'));
        }

        if (checked.indexOf($(this).attr('name')) < 0) {
          checked.push($(this).attr('name'));
          errors.push({
            id: $(this).attr('id'),
            name: $(this).attr('name'),
            errorMessage: $fieldset.attr('data-error'),
            label: label,
            type: 'text, password',
          });
        }
      }
    });
}

function appendErrorSummary(errors) {
  console.log(errors);
  var summaryNotPresent = $(document).find('.govuk-error-summary').length === 0;
  var errorHTML = errors.reduce((res, error) => res + `<li><a href="#${error.id}">${error.errorMessage}</a></li>`, '');
  var summary =
    '<div class="govuk-error-summary" role="alert" aria-labelledby="error-summary-title" tabindex="-1" data-module="govuk-error-summary">' +
    '<h2 class="govuk-error-summary__title" id="error-summary-title">' +
    defaultErrorHeading +
    '</h2>' +
    '<div class="govuk-error-summary__body">' +
    '<ul class="govuk-list govuk-error-summary__list">' +
    errorHTML +
    '</ul>' +
    '</div>' +
    '</div>';
  if (summaryNotPresent) {
    $('form').before(summary);
  }
}

function appendErrorMessages(errors) {
  for (var i = 0; i < errors.length; i++) {
    const error = errors[i];
    const $input = $(document).find(`#${error.id}`);
    var $formgroup = $input.parents('.govuk-form-group');
    console.log($input);
    console.log($formgroup);
    if ($formgroup.hasClass('govuk-form-group--error')) {
      break;
    }

    $formgroup.addClass('govuk-form-group--error');

    if (
      $formgroup.find('input[type="text"], input[type="password"]').length > 0 ||
      $formgroup.find('textarea').length > 0
    ) {
      if ($formgroup.find('.form-date').length > 0) {
        $formgroup.find('.form-date').before('<span class="gov-uk-error-message">' + error.errorMessage + '</span>');
      } else {
        $input.before('<span class="govuk-error-message">' + error.errorMessage + '</span>');
      }
    } else if ($formgroup.find('input[type="radio"]').length > 0 || $formgroup.find('input[type="checkbox"]')) {
      $formgroup.find('legend').append('<span class="govuk-error-message">' + error.errorMessage + '</span>');
    }
  }
}

$(document).on('submit', 'form', function (e) {
  const requiredFieldsPresent = $(document).find('[data-required]').length > 0;

  clearValidation();

  if (requiredFieldsPresent) {
    const errors = [];

    checkTextFields(errors);
    checkSelectors(errors);
    console.log(errors);

    if (errors.length > 0) {
      e.preventDefault();
      appendErrorSummary(errors);
      appendErrorMessages(errors);
      $(document).scrollTop(0);
    }
  }
});

That’s all well and good if you want to use HTML but what if you are using nunjucks? Well, here comes the ExpressJS validator.

A new approach we are going to implement going forward is using the Express.js validator and the nunjucks syntax. We have already put this into practice in the Routes section, this is still a work in progress at the time of this post.

This new approach requires that you add the following requirement into your dependencies package.json file "express-validator": "^6.14.2", it rearranges alphabetically after you save the file.

    Share:
    Back to Blog

    Related Posts

    View All Posts »
    Macro interactions

    Macro interactions

    A series of different approaches to a single user problem. Iterations based on User Research. Achieving a 100% success task completion rate.

    Postcode search

    Postcode search

    Improving the user experience for the majority of the users. Adding a postcode search that is returns AAA accessible results.

    Disclosure

    Disclosure

    When one thing per page does not work, and progressive disclosure does not fit the context. A brief Design History.