Webform accessibility improvements, several field types are not optimized

Created on 14 December 2022, almost 2 years ago
Updated 24 February 2023, over 1 year ago

Problem/Motivation

Pre-conditions "Disable clientside-validation option is checked and "Use ajax" is unchecked in form settings.

Webforms with wet-boew (wb-frmvld) validation enabled results in certain types of fields losing their webform conditionals functionality. Initial attempts to fix wet-boew have been abandoned due to the complexity of the wet-boew solution and the fact that the issue is not obvious other than it's caused by enabling the wb-frmvld wet-boew form validation (the wrapper div around a form containing in the wrapper div a "wb-frmvld" class is what is referred to as wet-boew form validation that is instantiated by the wet-boew js library should that pattern be found.

Review assumptions.

Steps to reproduce

Proposed css/js solution does not break webform conditionals like wet-boew does. Custom css/js solution expanded to do part of what wb-frmvld does to make it look similar. Review assumptions.

Proposed resolution

see css and js in this issue summary.
Initial assumptions may no longer be completely valid based on upgrade to webform 6.1.4 in wxt 4.4.2

Transitory solution / Temporary solution (PURE CSS/JS solution works well with/without SKIP THIS PART FOR NOW

'#prefix': '<div class="wb-frmvld">'
'#suffix': '</div>'

CSS for webform

add this css into your webform css (in the drupal GUI for webform settings css :

body.anonymous.path-webform ul.tabs--primary.nav.nav-tabs {
  display none;
}
div.highlighted {
  position: relative;
  z-index: 1;
}
sector.alert-danger,
form.webform-submission-form div.form-item.form-group {
  position: relative;
}
form.webform-submission-form div.form-type-checkbox strong.error {
  margin-left: -20px;
}
.wb-frmvld fieldset legend strong.error,
form.webform-submission-form .form-type-processed-text,
form.webform-submission-form #edit-actions {
  margin-left: 10px;
}
.wb-frmvld label strong.error,
.wb-frmvld legend strong.error {
  margin-bottom: 5px;
  margin-top: 3px;
  line-height: 2em;
}
form.webform-submission-form fieldset .fieldset-wrapper {
  position: relative;
  margin-left: 10px;
  margin-top: 30px;
  display: block !important;
}
.wb-frmvld div.form-item.has-error:not(error) strong.error {
  display: none !important;
}

/*form.webform-submission-form div.form-item label.control-label,*/
form.webform-submission-form fieldset span.fieldset-legend {
  position: relative;
  margin-bottom: 0px;
  margin-left: 15px;
}
section.alert-danger ul.item-list__comma-list li {
  display: block;
}
form.webform-submission-form div.form-item div.form-item--error-message.alert.alert-danger::before,
div.form-item--error-message.alert.alert-danger::before,
div.region-content form div.form-item label[for="*-required-"]::after,
div.form-item label.control-label.js-form-required.form-required::after,
fieldset span.fieldset-legend.js-form-required.form-required::after {
  content: none;
}
div#edit-actions,
div#edit-cancel-link  {
  display: inline-block;
  padding-right: 30px;
}
div[class^="form-type-"] label.form-required::before,
fieldset[class^="webform-type-"] span.form-required::before,
fieldset[class^=".webform-radio"] span.form-required::before,
fieldset[class^=".webform-check"] span.form-required::before,
div.form-item label.control-label.form-required span.field-name::before,
fieldset span.fieldset-legend.js-form-required.form-required::before {
  content: "";
  background-image: url(https://agriculture.canada.ca/themes/custom/wxt_bootstrap/images/required.svg);
  display: inline-block;
  line-height: 1;
  color: #e00;
  background-size: 7px 7px;
  width: 7px;
  vertical-align: super;
  margin-left: 4px;
  height: 7px;
  position: absolute;
  top: 6px;
  left: -15px;
}

div[class^="form-type-"].form-type-textarea.error label.form-required,
fieldset[class^="webform-type-"].error span.form-required,
fieldset[class^=".webform-radio"].error span.form-required,
fieldset[class^=".webform-check"].error span.form-required,
div.form-item.error label.control-label.js-form-required.form-required,
fieldset.error span.fieldset-legend.js-form-required.form-required {
  color: unset;
}

html:lang(en) div[class^="form-type-"].form-type-textarea label.form-required:after,
html:lang(en) fieldset[class^="webform-type-"] span.form-required:after,
html:lang(en) fieldset[class^=".webform-radio"] span.form-required:after,
html:lang(en) fieldset[class^=".webform-check"] span.form-required:after,
html:lang(en) div.form-item label.control-label strong.required:after,
html:lang(en) fieldset span.fieldset-legend.js-form-required.form-required:after {
  content: "(required)";
  display: inline-block;
  color: #e00;
  margin-left: 4px;
  position: unset;
  background-image: unset;
  background-size: unset;
  width: unset;
  vertical-align: unset;
  height: unset;
}

html:lang(fr) div[class^="form-type-"].form-type-textarea label.form-required:after,
html:lang(fr) fieldset[class^="webform-type-"] span.form-required:after,
html:lang(fr) fieldset[class^=".webform-radio"] span.form-required:after,
html:lang(fr) fieldset[class^=".webform-check"] span.form-required:after,
html:lang(fr) div.form-item label.control-label strong.required:after,
html:lang(fr) fieldset span.fieldset-legend.js-form-required.form-required:after {
  content: "(obligatoire)";
  display: inline-block;
  color: #e00;
  margin-left: 4px;
  position: unset;
  background-image: unset;
  background-size: unset;
  width: unset;
  vertical-align: unset;
  height: unset;
}

form.webform-submission-form .webform-element-description {
  color: #333;
}
form.webform-submission-form div.form-item .description.help-block,
form.webform-submission-form div.form-item .description.help-block strong .label {
  font-size: 16px
}
body.path-webform div.region-content form label[for="*-required-"] {
  position: relative;
  margin-bottom: 0px;
}
form.webform-submission-form div.form-item--error-message.alert.alert-danger {
  border-image: none !important;
  margin-bottom: 5px;
  margin-left: 0px;
  font-weight: bold;
  background: #f3e9e8;
  border-color: #d3080c;
}
form.webform-submission-form div.form-item.form-item-programorservice.form-group,
div.form-group.error.has-error label + div.select-wrapper {
  margin-bottom: 0px;
}
div.form-group.error.has-error label + div.select-wrapper select.form-select {
  width: 100%;
}

div.region section.alert-danger ul li {
  display: block;
}

div.region section.alert-danger ul li a {
  display: list-item;
  list-style-position: inside;
  margin-top: 15px;
}

div.region section.alert-danger ul li::after {
  content: none;
}
div.region section.alert-danger h2.sr-only {
  position: relative;
  font-size: 24px;
  width: 100%;
  height: 100%;
}
form.webform-submission-form > .btn { width: auto; }
form.webform-submission-form label.control-label.js-form-required.form-required {
  padding-bottom: 5px;
}
form.webform-submissoin-form div.fieldset-wrapper .alert-sm,
form.webform-submissoin-form div.help-block strong.error {
  display: flow-root;
  font-size: 16px;
  font-weight: bold;
  margin-bottom: 5px;
}
body.path-webform fieldset.required.error legend span {
  color: #a94442;
}
form[id$="-add-form"] section.alert ul li a,
form[id$="-edit-form"] section.alert ul li a {
  font-weight: normal;
}

form.webform-submission-form .iti__selected-flag {
  left: 10px;
}
form.webform-submission-form .iti,
div.form-type-date .control-label {
  display: block;
}

form.webform-submission-form fieldset.fieldgroup legend span.fieldset-legend,
form.webform-submission-form > div.form-item.form-group {
  margin-left: 10px;
}

form.webform-submission-form > section.webform-section-wrapper,
form.webform-submission-form section .webform-section-wrapper .js-webform-states-hidden.js-form-wrapper {
  margin-left: 10px;
}
form.webform-submission-form > div.js-webform-states-hidden {
  margin-left: 20px;
}
form.webform-submission-form > div.js-webform-states-hidden.form-item.form-group > label.control-label.form-required.js-form-required,
form.webform-submission-form > div.js-webform-states-hidden.form-item.form-group > label.control-label,
form.webform-submission-form > div.js-webform-states-hidden.form-item.form-group > div.description.help-block,
form.webform-submission-form > div.js-webform-states-hidden.form-item.form-group > div.form-item--error-message.alert.alert-danger.alert-sm,
form.webform-submission-form > div.js-webform-states-hidden.form-item.form-group.form-type-phone > input.form-email,
form.webform-submission-form > div.js-webform-states-hidden.form-item.form-group > input.form-email,
form.webform-submission-form > div.js-webform-states-hidden.form-item.form-group > input,
form.webform-submission-form > div.js-webform-states-hidden.form-item.form-group.form-type-email > input.form-phone {
  margin-left: unset;
}
form.webform-submission-form section#edit-psrf-section-productsrequired .webform-section-wrapper .form-item.form-type-select > * {
  margin-left: 0;
}
form.webform-submission-form label.control-label.form-required {
  color: #000;
}

form.webform-submission-form > section.form-wrapper.webform-section,
form.webform-submission-form > fieldset.webform-type-checkboxes div.fieldset-wrapper div.form-checkboxes div.form-item > label.control-label span.field-name,
form.webform-submission-form > fieldset.webform-checkboxes-other div.fieldset-wrapper div.form-checkboxes div.form-type-checkbox > label.option span.field-name,
form.webform-submission-form > fieldset.webform-type-radios div.fieldset-wrapper div.webform-options-display-one-column div.form-type-radio > label.option span.field-name ,
form.webform-submission-form > fieldset.webform-radios-other div.fieldset-wrapper div.webform-options-display-one-column div.form-type-radio > label.option span.field-name {
  margin-left: unset;
}
form.webform-submission-form div.form-item div.form-item--error-message.alert.alert-danger {
  margin-bottom: 5px !important;
}

section.alert-danger button.close {
  opacity: unset;
  border: 1px solid white;
  padding: 0px 9px 3px 9px;
  font-size: 30px;
}

section.alert-danger button.close:hover {
  border-radius: 3px;
  border-color: #999;
  box-shadow: 0px 0px 5px #999;
}
form.webform-submission-form > .btn {
  width: auto;
}

JS for webform

temporary solution is both the css and the js added to the webform through the Drupal gui insert this into the webforms settings css/js

(function ($, Drupal) {
  Drupal.behaviors.wxtOrWetBoew = {
    attach: function (context, settings) {
      if (context == document) {
        if (!$('body').hasClass('path-webform')) {
          // Not a webform, do nothing.
          console.log('Not a webform, validated by wxtOrWetBoew');
          return;
        }
        if (WxtHelper.timeout > 0) {
          console.log('WxtHelper.init()');
          WxtHelper.init();
        }
      }
    }
  };
})(jQuery, Drupal);
var WxtHelper = function() {
  var errorHtml = '';
  var erroredElements = [];
  var erroredMessages = [];
  var initialized = false;
  var lang = 'en';
  var validationType = 'drupal';
  var wetboewFail = false;
  var timeout = 950000;
  function isReady() {
    var isReady = false;
    if (typeof $ == 'undefined') {
      $ = jQuery;
    }
    if ($('.wb-frmvld').length > 0 && WxtHelper.validationType == 'drupal') {
      console.log('WxtHelper.validationType = wet-boew');
      WxtHelper.validationType = 'wet-boew';
    }
    if (WxtHelper.validationType == 'wet-boew' &&
    $('.wb-frmvld').hasClass('wb-frmvld-inited') &&
    $('.wb-frmvld').hasClass('wb-init') &&
    ($('form section[id*=errors]').length == 1 || $('div.highlighted section.alert-danger').length == 1)) {
      if ($('div.highlighted section.alert-danger').length) {
        WxtHelper.wetboewFail = true;
        console.log('Form type is : ' + WxtHelper.validationType + ' : ? wet-boew not initialized correctly for some reason.');        
      }
      else {
        console.log('Form type is : ' + WxtHelper.validationType);
      }
      isReady = true;
    }
    else if(WxtHelper.validationType == 'drupal' && $('section.alert-danger').length > 0 && $('.form-item--error-message').length > 0) {
      WxtHelper.validationType = 'drupal';
      console.log('Form type is : ' + WxtHelper.validationType)
      isReady = true;
    }
    if (isReady) {
      console.log('isReady - Form type is : ' + WxtHelper.validationType);
      if (WxtHelper.timeout > 0) {
        console.log('isReady took ' + (950000 - WxtHelper.timeout) + ' milliseconds to complete');
      }
    }
    return isReady;
  };
  // Code you want to run when isReady conditions exist in the DOM.
  function onReady() {
    console.log('onReady()');

    if (WxtHelper.validationType == 'drupal') {
      WxtHelper.validationHelper();
      WxtHelper.makeErrorTitleLikeWetboew();
      WxtHelper.moveAlertInsideLabel();
      WxtHelper.setErrorElementsText();
      WxtHelper.moveErrors();
      // WCAG next three lines.
      $('section.alert-danger').removeAttr('role');
      $('section.alert-danger').attr('tabindex', '-1');
      $('section.alert-danger li a').first().trigger('focus');
    }
    else {
      // Make sure the event bindings do not accumulate, turn off first.
      WxtHelper.validationWetboew();
      WxtHelper.moveAlertInsideLabel();
      $('form.webform-submission-form').off('submit.wxthelper');
      $('form.webform-submission-form').once('submit.wxthelper', function() {
        // Re-initialize on submit.
        WxtHelper.timeout = 900000;
        WxtHelper.initialized = false;
        WxtHelper.init();
        setTimeout(function() {
          WxtHelper.moveAlertInsideLabel();
        }, 100);
      });
    }
  };
  // Contingency plan code you want to run if isReady conditions DO NOT exist in the DOM by timeout.
  function opt_onTimeout() {
    // Do nothing.
  };
  function addErrorToFieldset(item, index, arr) {
    console.log('addErrorToFieldset()');
    if (1) {
      // Disabled, still reviewing this.
      return;
    }
    if (WxtHelper.validationType == 'drupal' && (jQuery('fieldset' + item).length || $('fieldset' + item + '--wrapper').length)) {
      WxtHelper.errorHtml = '<div class="clearfix"></div><div class="form-item--error-message alert alert-danger alert-sm">' + WxtHelper.erroredMessages[index] + '</div>';
      if (jQuery('fieldset' + item).length || $('fieldset' + item + '--wrapper').length) {
        $('fieldset' + item + '--wrapper').find(item).prepend(WxtHelper.errorHtml);
        $('fieldset' + item).find(item+'radios').prepend(WxtHelper.errorHtml);
      }
      if (!$('fieldset' + item + '--wrapper').find('.alert-danger').length) {
        // Prepend the error message on the fieldset wrapper.
        $('fieldset' + item + '--wrapper').find(item).prepend(WxtHelper.errorHtml);
      }
      if (!$('fieldset' + item).find('.alert-danger').length) {
        // For Radios.
        // If the error html is not already added to the fieldset item, add it.
        $('fieldset' + item).find(item+'radios').prepend(WxtHelper.errorHtml);
      }
    }
  }
  function cleanErrorMessage(text, index) {
    if ($('section.alert-danger').hasClass('wxt-processed')) {
      // Prevent extra processing.
      return;
    }
    if ($('div.region-highlighed section.alert-danger').hasClass('wxt-processed')) {
      // Prevent over-processing.
      return;
    }
    if (text.indexOf('field is required') > 1 || text.indexOf('champ est requ') > 1) {
      return text;
    }
    var result = text;
    if (WxtHelper.validationType == 'wet-boew') {
      result = text + WxtHelper.requiredSuffix();
      if (WxtHelper.lang == 'fr') {
        result = result.replace('Ce field est requis', 'Ce champ est requis');
      }
    }
    else if (WxtHelper.validationType == 'drupal') {
      if (WxtHelper.lang == 'en') {
        text = "Error " + index + ": " + text + WxtHelper.requiredSuffix((index - 1));
      }
      else {
        text = "Erreur " + index + ": " + text + WxtHelper.requiredSuffix((index - 1));
      }
      result = text;
    }
    return result;
  }
  function requiredSuffix(index) {
    var found = false;
    if ($(erroredElements[index]).length == 1 &&
    ($(erroredElements[index]).get(0).tagName.toLowerCase() == 'input' ||
    $(erroredElements[index]).get(0).tagName.toLowerCase() == 'fieldset' ||
    $(erroredElements[index]).get(0).tagName.toLowerCase() == 'textarea')) {
      var searching = erroredElements[index];
      if ($(searching).is('[class*=radios]') ||
      $(searching).hasClass('form-email') ||
      $(searching).hasClass('form-text') ||
      $(searching).hasClass('form-textarea') ||
      $(searching).hasClass('form-tel')) {
        if (!found && $(searching).find('.alert-danger').length == 1) {
          msg = $(searching).find('.alert-danger').first().text();
          found = true;
        }
        else if (!found && $(searching).parent().find('.alert-danger').length == 1) {
          msg = $(searching).parent().find('.alert-danger').first().text();
          found = true;
        }
        else if (!found && $(searching).parent().parent().find('.alert-danger').length == 1) {
          msg = $(searching).parent().parent().find('.alert-danger').first().text();
          found = true;
        }
      }
    }
    if (!found) {
      msg = "This field is required.";
      if (WxtHelper.lang == 'fr') {
        msg = "Ce champ est requis.";
      }
    }
    return ' - ' + msg;
  }
  function requiredSuffixFormElement(index) {
    var msg = '';
    var prefix = 'Error ' + (index+1) + ' - ';
    if (WxtHelper.lang == 'fr') {
      prefix = 'Erreur ' + (index+1) + ' - ';
    }
    if ($(erroredElements[index]).length == 1 && $(erroredElements[index]).get(0).tagName.toLowerCase() == 'fieldset') {
      msg = $(erroredElements[index]).find('.alert.alert-danger').text();
    }
    else {
      if ($(erroredElements[index]).closest('.error.form-wrapper').length == 1) {
        msg = $(erroredElements[index]).closest('.error.form-wrapper').find('.alert.alert-danger').text();
      }
      else if ($(erroredElements[index]).closest('.error.has-error').length == 1) {
        msg = $(erroredElements[index]).closest('.error.has-error').find('.alert.alert-danger').text();
      }
      else if ($(erroredElements[index]).closest('.error.form-item').length == 1) {
        msg = $(erroredElements[index]).closest('.error.form-item').find('.alert.alert-danger').text();
      }
      else {
        msg = "This field is required.";
        if (WxtHelper.lang == 'fr') {
          msg = "Ce champ est requis.";
        }
      }
    }
    return prefix + msg;
  }
  function makeErrorTitleLikeWetboew(force) {
    if (typeof force == 'undefined') {
      force = false;
    }
    if (WxtHelper.validationType == 'drupal' || force) {
      var h2prefix = "The form could not be submitted because ";
      if (WxtHelper.lang == 'fr') {
        h2prefix = "Le formulaire n'a pu Γͺtre soumis car ";
      }
      if ($("h2.sr-only").length == 1) {
        var original = $("h2.sr-only").next().text();
        $("h2.sr-only").text(h2prefix + original); // Set the error msg into h2
        $("h2.sr-only").next().remove(); // Remove extra html p element
      }
    }
  }
  function moveAlertInsideLabel() {
    console.log('moveAlertInsideLabel()');
    var textItem = 'div[class*=form-type-text], div.js-webform-states-hidden, div.form-item.has-error, .form-type-select.form-group';
    $(textItem).each(function(index, element) {
      var divAlert = $(this).find('.alert-danger').first();
      if ($(this).find(divAlert).length == 1) {
        var labelItem = $(this).find('label');
        var attachToThis = labelItem;
        var descriptionHelp = $(this).find('.description.help-block').first();
        var textarea = $(this).find('textarea').first();
        var textareaWrapper = $(this).find('.form-textarea-wrapper').first();
        if ($(descriptionHelp).length == 1) {
          attachToThis = descriptionHelp;
          $(divAlert).clone().insertBefore($(attachToThis));
          $(divAlert).remove();
        }
        else if ($(textarea).hasClass('required') && $(textareaWrapper).length == 1) {
          attachToThis = $(this).find('.form-textarea-wrapper');
          $(divAlert).clone().insertBefore($(attachToThis));
          $(divAlert).remove();
        }
        else if ($(this).hasClass('error') && $(this).find('.select-wrapper').length == 1) {
          attachToThis = $(this).find('.select-wrapper');
          $(divAlert).clone().insertBefore($(attachToThis));
          $(divAlert).remove();
        }
        else if (!$(this).hasClass('form-type-textarea') && $(this).find('.form-control').length == 1) {
          attachToThis = $(this).find('label.control-label');
          $(divAlert).clone().insertAfter($(attachToThis));
          $(divAlert).remove();
        }
      }
    });
    if (WxtHelper.validationType == 'wet-boew') {
      $(textItem).each(function(index, element) {
        var divAlert = $(this).find('strong.error').first();
        // DISABLED, LIKELY DO NOT NEED THIS HERE SECTION FOR WET-BOEW.
        if (0 && $(this).find(divAlert).length == 1) {
          var labelItem = $(this).find('label');
          var attachToThis = labelItem;
          var descriptionHelp = $(this).find('.description.help-block').first();
          var textarea = $(this).find('textarea').first();
          var textareaWrapper = $(this).find('.form-textarea-wrapper').first();
          if ($(descriptionHelp).length == 1) {
            attachToThis = descriptionHelp;
            $(divAlert).clone().appendTo($(attachToThis));
            $(divAlert).remove();
          }
          else if (!$(this).hasClass('form-type-textarea') && $(this).find('.form-control').length == 1) {
            attachToThis = $(this).find('label.control-label');
            $(divAlert).clone().appendTo($(attachToThis));
            $(divAlert).remove();
          }
          else if ($(textarea).hasClass('required') && $(textareaWrapper).length == 1) {
            attachToThis = $(this).find('.form-textarea-wrapper');
            $(divAlert).clone().prepend($(attachToThis));
            $(divAlert).remove();
          }
          else if ($(this).hasClass('error') && $(this).find('.select-wrapper').length == 1) {
            attachToThis = $(this).find('.select-wrapper');
            $(divAlert).clone().appendTo($(attachToThis));
            $(divAlert).remove();
          }
        }
      });
    }
  }
  function moveErrors() {
    var submit = '#edit-actions-submit';
    var force = false;
    if ($(submit).length == 1 && $(submit).is('[disabled]')) {
      force = true;
    }
    // Only needs to be done for validationType = drupal.
    if (WxtHelper.validationType == 'drupal' || force) {
      var highlightedSelector = 'div.highlighted';
      var firstErrorAnchorSelector = 'div.highlighted section.alert-danger ul li:first-child a:first-child'
      var firstErrorElement = $(firstErrorAnchorSelector).attr('href');
      var formElement = $(firstErrorElement).parent().closest('form');
      var attachToThis = formElement;
      var firstFormItem = $(formElement).find('.form-item').first();
      if ($(firstFormItem).length == 1 && !$(firstFormItem).hasClass('error')) {
        $(highlightedSelector).insertAfter($(firstFormItem));
      }
      else if ($(attachToThis).length == 1) {
        $(highlightedSelector).insertBefore($(attachToThis));
      }
    }
  }
  function setErrorElementsText() {
    if (WxtHelper.wetboewFail == true && WxtHelper.validationType == 'wetboew') {
      return;
    }
    var all = 'form.webform-submission-form .alert-danger';
    if ($('form.webform-submission-form').hasClass('wxt-processed')) {
      // Prevent over-processing.
      return;
    }
    console.log('setErrorElementText');
    $(all).each(function(index, element) {
      if ($(this).length == 1) {
        console.log('index of form alert-danger:' + index);
        var desired = WxtHelper.requiredSuffixFormElement(index);
        if (desired.indexOf($(this).text)) {
          $(this).text(desired);
        }
      }
    });
    // Add wxt-processed class to prevent over-processing.
    $('form.webform-submission-form').addClass('wxt-processed');
    // Now for the section element anchor text.
    var all = 'div.region-highlighted section.alert-danger ul a';
    if ($('div.region-highlighed section.alert-danger').hasClass('wxt-processed')) {
      // Prevent over-processing.
      return;
    }
    $(all).each(function(index, element) {
      if ($(this).length == 1) {
        console.log('index of section alert-danger:' + index);
        var desired = WxtHelper.getErroredMessage(index);
        if (desired.indexOf($(this).text)) {
          $(this).text(desired);
        }
      }
    });
    $('div.region-highlighed section.alert-danger').addClass('wxt-processed');
  }
  function init() {
    // How frequent to check for your object.
    var interval = 35;
    if (WxtHelper.validationType == 'wet-boew') {
      interval = 250;
    }
    WxtHelper.lang = jQuery('html').attr('lang');
    if (WxtHelper.isReady()) {
      if (WxtHelper.validationType == 'drupal') {
        WxtHelper.onReady();
      }
      else {
        // Wetboew is a bit slow to initialize, improve this later.
        var submit = '#edit-actions-submit';
        $(submit).removeAttr('disabled');
        if ($('div.highlighted section.alert-danger').length == 1) {
          WxtHelper.wetboewFail = true;
          setTimeout(function () {
            WxtHelper.beforeReadyOverride();
            WxtHelper.onReady();
          }, 200);
        }
        else {
          setTimeout(function () {
            WxtHelper.onReady();
          }, 200);
        }
      }
    }
    else {
      if (WxtHelper.timeout > 0) {
        WxtHelper.timeout -= interval;
        window.setTimeout(arguments.callee, interval);
      } else {
        console.log("WxtHelper.isReady() timed-out always returning false.");
      }
    }
  }
  function beforeReadyOverride() {
    WxtHelper.validationHelper();
    WxtHelper.setErrorElementsText();
    WxtHelper.moveAlertInsideLabel();
    WxtHelper.makeErrorTitleLikeWetboew(true);
    WxtHelper.moveErrors();
    $('section.alert-danger').removeAttr('role');
    $('section.alert-danger').attr('tabindex', '-1');
    $('section.alert-danger li a').first().trigger('focus');
  }
  function getErroredMessage(index) {
    console.log('getErroredMessage()');
    var erroredText = '';
    if (WxtHelper.erroredElements[index].length) {
      erroredText = WxtHelper.erroredMessages[index];
    }
    return erroredText
  }
  function validationHelper() {
    console.log('validationHelper()');
    var allErrors = jQuery('section.alert-danger ul a').each(function(index) {
      WxtHelper.erroredElements.push($(this).attr('href'));
      var fieldtype = 'unknown';
      if ($($(this).attr('href')).hasClass('field-type-email')) {
        fieldtype = 'form-type-email';
      }
      WxtHelper.erroredMessages.push(WxtHelper.cleanErrorMessage($(this).text(), (index+1)));
    });
    //WxtHelper.erroredElements.forEach(WxtHelper.addErrorToFieldset);
  }
  function validationWetboew() {
    console.log('validationWetboew()');
    if (WxtHelper.validationType == 'wet-boew') {
      $('div.wb-frmvld form fieldset span.label-danger, div.wb-frmvld form > div.has-error span.label-danger').each(function(index) {
        if (!$(this).hasClass('wxt-cleaned')) {
          var prefixSpan = $(this).find('.prefix').prop('outerHTML');
          var prefixText = $(this).find('.prefix').text();
          $(this).find('span.prefix').remove();
          var cleanedText = WxtHelper.cleanErrorMessage(prefixText + $(this).html());
          cleanedText = cleanedText.replace(prefixText, '');
          $(this).html(cleanedText);
          $(this).prepend(prefixSpan);
          $(this).addClass('wxt-cleaned');
        }
      });
    }
  }
  return {
    addErrorToFieldset: addErrorToFieldset,
    beforeReadyOverride: beforeReadyOverride,
    cleanErrorMessage: cleanErrorMessage,
    erroredElements: erroredElements,
    erroredMessages: erroredMessages,
    errorHtml: errorHtml,
    getErroredMessage: getErroredMessage,
    isReady: isReady,
    init: init,
    initialized: initialized,
    lang: lang,
    makeErrorTitleLikeWetboew: makeErrorTitleLikeWetboew,
    moveAlertInsideLabel: moveAlertInsideLabel,
    moveErrors: moveErrors,
    onReady: onReady,
    requiredSuffix: requiredSuffix,
    requiredSuffixFormElement: requiredSuffixFormElement,
    setErrorElementsText: setErrorElementsText,
    timeout: timeout,
    validationHelper: validationHelper,
    validationWetboew: validationWetboew,
    validationType: validationType,
    wetboewFail: wetboewFail,
  }
}();

Remaining tasks

create new twigs for fieldset and possibly others that may not be included in wxt_boostrap
perhaps add a hook that adds the above css into all new webforms.
or wrap css with body.path-webform

User interface changes

TBD

API changes

TBD

Data model changes

TBD

πŸ› Bug report
Status

Active

Version

4.4

Component

WxT Bootstrap

Created by

πŸ‡¨πŸ‡¦Canada joseph.olstad

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • πŸ‡¨πŸ‡¦Canada deepaniw

    (function ($, Drupal) {
    Drupal.behaviors.wxtOrWetBoew = {
    attach: function (context, settings) {
    if (context == document) {
    if (!$('body').hasClass('path-webform')) {
    // Not a webform, do nothing.
    console.log('Not a webform, validated by wxtOrWetBoew');
    return;
    }
    if (WxtHelper.timeout > 0) {
    console.log('timeout > 0');
    WxtHelper.init();
    }
    }
    }
    };
    })(jQuery, Drupal);
    var WxtHelper = function() {
    var errorHtml = '';
    var erroredElements = [];
    var erroredMessages = [];
    var initialized = false;
    var validationType = 'drupal';
    var timeout = 200000;
    function isReady() {
    console.log('isReady()');
    if (jQuery('.wb-frmvld').hasClass('wb-frmvld-inited') && jQuery('section.alert-danger').length > 0) {
    WxtHelper.validationType = 'wet-boew';
    return true;
    }
    else if($('.wb-frmvld').length == 0 && jQuery('section.alert-danger').length > 0 && $('.form-item--error-message').length > 0) {
    WxtHelper.validationType = 'drupal';
    return true;
    }
    console.log(WxtHelper.interval);
    return false;
    };
    //Code you want to run when isReady conditions exist in the DOM.
    function onReady() {
    console.log('onReady()');
    WxtHelper.validationHelper();
    WxtHelper.validationWetboew();
    if (WxtHelper.validationType == 'drupal') {
    WxtHelper.moveAlertInsideLabel();
    }
    var timeout = 0;
    // Make sure the event bindings do not accumulate, turn off first.
    $('form.webform-submission-form').off('.wxthelper');
    $('form.webform-submission-form').on('submit.wxthelper', function() {
    // Re-initialize on submit.
    WxtHelper.timeout = 50000;
    WxtHelper.initialized = false;
    WxtHelper.init();
    });
    };
    //Contingency plan code you want to run if isReady conditions DO NOT exist in the DOM by timeout.
    function opt_onTimeout() {
    // Do nothing.
    };
    function addErrorToFieldset(item, index, arr) {
    console.log('addErrorToFieldset()');
    if (WxtHelper.validationType == 'drupal' && (jQuery('fieldset' + item).length || $('fieldset' + item + '--wrapper').length)) {
    WxtHelper.errorHtml = '

    ' + WxtHelper.erroredMessages[index] + '

    ';
    if (!$('fieldset' + item + '--wrapper').find('.alert-danger').length) {
    $('fieldset' + item + '--wrapper').find(item).prepend(WxtHelper.errorHtml);
    }
    if (!$('fieldset' + item).find('.alert-danger').length) {
    $('fieldset' + item).find(item+'radios').prepend(WxtHelper.errorHtml);
    }
    }
    }
    function cleanErrorMessage(text) {
    console.log('cleanErrorMessage()' + text);
    const regex = /: (- |)([^]*)(field is required.|champ est requis.|field est requis.)/gm;
    // Alternative syntax using RegExp constructor
    const str = text;
    var lang = $('html').attr('lang');
    var subst = `: This $3`;
    if (lang == 'fr') {
    subst = `: Ce $3`;
    }
    // The substituted value will be contained in the result variable
    var result = str.replace(regex, subst);
    if (lang == 'fr' && WxtHelper.validationType == 'wet-boew') {
    result = result.replace('Ce field est requis', 'Ce champ est requis');
    }
    return result;
    }
    function bruteForce() {
    if ($('html').attr('lang') == 'fr') {
    return 'Ce champ est requis.';
    }
    return 'This field is required.';
    }
    function moveAlertInsideLabel() {
    console.log('moveAlertInsideLabel()');
    var textItem = 'div[class*=form-type-text], div[class*=form-type-email],.form-type-select.form-group';
    $(textItem).each(function(index, element) {
    if ($(this).find('.alert-danger').length) {
    var divAlert = $(this).find('.alert-danger');
    var labelItem = $(this).find('label');
    $(divAlert).clone().appendTo($(labelItem));
    $(divAlert).remove();
    }
    });
    var all = 'div[class*=form-type-text],div[class*=form-type],fieldset [class*=form-type],.form-type-select.form-group,fieldset.error';
    $(all).each(function(index, element) {
    if ($(this).find('.alert-danger').length) {
    var desired = WxtHelper.bruteForce();
    $(this).find('.alert-danger').text(desired);
    }
    });
    }
    function init() {
    // How frequent to check for your object.
    var interval = 200;
    if (WxtHelper.isReady()) {
    WxtHelper.onReady();
    } else {
    if (WxtHelper.timeout > 0) {
    WxtHelper.timeout -= interval;
    window.setTimeout(arguments.callee, interval);
    } else {
    console.log("WxtHelper.isReady() timed-out always returning false.");
    }
    }
    }
    function validationHelper() {
    console.log('validationHelper()');
    var allErrors = jQuery('section.alert-danger ul a').each(function(index) {
    WxtHelper.erroredElements.push($(this).attr('href'));
    WxtHelper.erroredMessages.push(WxtHelper.cleanErrorMessage($(this).text()));
    });
    WxtHelper.erroredElements.forEach(WxtHelper.addErrorToFieldset);
    }
    function validationWetboew() {
    console.log('validationWetboew()');
    if (WxtHelper.validationType == 'wet-boew') {
    $('div.wb-frmvld form fieldset span.label-danger, div.wb-frmvld form > div.has-error span.label-danger').each(function(index) {
    if (!$(this).hasClass('wxt-cleaned')) {
    var prefixSpan = $(this).find('.prefix').prop('outerHTML');
    var prefixText = $(this).find('.prefix').text();
    $(this).find('span.prefix').remove();
    var cleanedText = WxtHelper.cleanErrorMessage(prefixText + $(this).html());
    cleanedText = cleanedText.replace(prefixText, '');
    $(this).html(cleanedText);
    $(this).prepend(prefixSpan);
    $(this).addClass('wxt-cleaned');
    }
    });
    }
    }
    return {
    addErrorToFieldset: addErrorToFieldset,
    bruteForce: bruteForce,
    cleanErrorMessage: cleanErrorMessage,
    erroredElements: erroredElements,
    erroredMessages: erroredMessages,
    errorHtml: errorHtml,
    isReady: isReady,
    init: init,
    initialized: initialized,
    moveAlertInsideLabel: moveAlertInsideLabel,
    onReady: onReady,
    timeout: timeout,
    validationHelper: validationHelper,
    validationWetboew: validationWetboew,
    validationType: validationType,
    }
    }();

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    fixing edge case for required checkbox, still more work to do here.

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    Now focusing on pure drupal and wxt with webform version 6.1.4

    This js has not been tested with the wet-boew .wb-frmvld mechanism. It has been tested without wet-boew .wb-frmvld

    This way all conditionals work all the time. I'll improve this to make it be optimized either way.

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    tested vs wxt 4.4.2 vanilla

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    Fix Error label for french.

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    fieldset twig, seems to improve things.

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    refactored js to allow the webform module to specify the message.
    '#required_error': 'This field is required.'

    This js does however add the prefix to the message as follows:

    "Error 1: Blah blah blah"

    "Error 2: Blah blah blah blah"

    and so on and so forth.

    In french
    "Erreur 1: Test test test"

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    Fixing last update, pasted from wrong window

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    Extended basic support for all field types, next I'm going to test this against a vanilla wxt, I am about to run another test of this against a vanilla wxt 4.4.2, make sure it's still generic enough.

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    One line css addition with big improvement.

  • Assigned to joseph.olstad
  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    Updated css/js, this time make the errors go under the H1 , this only affects mode not using wb-frmvld, this is 'drupal' mode validation type

    see screenshot

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    Minor change, still tests ok on wxt 4.4.2 vanilla

  • πŸ‡¨πŸ‡¦Canada jamesyao

    Hi @joseph.olstad
    I listed the findings below. Could you please improve the JS and CSS to meet the requirements. Thanks

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    css change, see screenshot, now label black color, in brackets (required) is red

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    I'll need to push up a new patch for this to replace patch 20.
    new css and js, still some cleanup to do but so far testing well.

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    css changes

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    small css change

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    couple small changes

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    This fixes radios other and checkboxes other label alignment

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    last one was radios and checkboxes other, this time radios-other fix

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    Simplified and cleaned up the css approach which fixes alignment issues and also a supports section wrapper. As generic as possible with clean alignment.

  • πŸ‡¨πŸ‡¦Canada joseph.olstad

    Regression fix for the submit button (and previous next button) alignment.

Production build 0.71.5 2024