0
\$\begingroup\$

I tried creating a function to check for a match between two email fields, let's say EMAIL and CONFIRM email. I absolutely try to avoid jQuery validate. It is not worth it for a single field. For the rest I use HTML5 properties like required, pattern, etc. and it works well. This is why I want to deliver seamless experience with setCustomValidity.

Here's my working code, which of course, may be improved by someone with more experience in JS and jQuery.

HTML

<input type="email" name="addr1" id="addr1" required>
<input type="email" name="addr2" data-equal-to="addr1" data-msg-error="Emails do not match." required>

jQuery

$('[data-equal-to]').on('blur', function() {
    var to_confirm = $(this),
        to_equal = $('#' + to_confirm.data('equal-to'));
    if (to_confirm.val() != to_equal.val()) {
        this.setCustomValidity(to_confirm.data('msg-error'));
    }
    else {
        this.setCustomValidity('');
    }
    to_equal.one('blur', function() {
        to_confirm.trigger('blur');
    });
});

Here are the problems I encountered:

  1. I was using '.bind'. It turns out its deprecated. Then I started using '.on'
  2. I forgot the last 3 lines. There was no event listener for the first email input, so I was only checking the CONFIRM input. This got fixed.
  3. I was using 'input' instead of 'blur'. 'input' provided real-time validation (as you type) which is good, but consumed too much in my opinion. Also it showed errors while you are still typing (and haven't finished), so some users may find it confusing. The only drawback of 'blur' I find is that feedback on validity comes after you click another element (unfocus).
  4. Binding on every input a new listener to 'to_equal' turned out to be a bad idea. I ended up with multiple listeners, causing the browser to crash. I found out that '.one' exists, which should do the same as .off before .on (or maybe after?)

My question is - can this little script be improved in terms of speed, or using less event listeners? Is there a case I haven't thought of? I searched a lot to find a good no plugin solution, but with no luck. Also I insist on using data- attributes to provide the matching id + the error message (as the website is multi-lingual and its easier to modify HTML than JS on the fly).

I think that having the 'perfect' version of this script will help a lot of people, especially in confirm email/password (for passwords case sensitivity must be in mind!) aspect.

\$\endgroup\$
  • \$\begingroup\$ If you want to do the validation check while they're typing, but without annoying them before they're done, search for "debouncing". \$\endgroup\$ – Barmar Nov 3 '17 at 19:29
  • \$\begingroup\$ Yes, that is helpful and also mentioned by wOxxOm. Thank you Barmar! \$\endgroup\$ – Svetoslav Genov Nov 4 '17 at 8:29
1
\$\begingroup\$

Switch to the native DOM methods like getElementById, which is orders of magnitude faster and you can keep input:

$('[data-equal-to]').on('input', function() {
  var target = document.getElementById(this.dataset.equalTo);
  var isEqual = !target || target.value == this.value;  
  this.setCustomValidity(isEqual ? '' : this.dataset.msgError);
});

You can also debounce the verification by 200ms, for example:

;(function() {
  var timeout;

  function validateEquality(element) {
    var target = document.getElementById(element.dataset.equalTo);
    var isEqual = !target || target.value == element.value;  
    element.setCustomValidity(isEqual ? '' : element.dataset.msgError);
  }

  $('[data-equal-to]').on({
    input: function() {
      clearTimeout(timeout);
      timeout = setTimeout(validateEquality, 200, this);
    }),
    blur: function() {
      clearTimeout(timeout);
      validateEquality(this);
    }),
  });
})();

You can also make this a jQuery plugin.


As for mirroring the check for the target element, the preferable approach is to explicitly declare data-equal-to attribute on both input elements that points to the other one. But you can also do it implicitly by adding equalOf expando property pointing to the linked element so you can re-triggering input event on it:

$('[data-equal-to]')
  .on('input', function() {
    var target = document.getElementById(this.dataset.equalTo);
    var isEqual = !target || target.value == this.value;  
    this.setCustomValidity(isEqual ? '' : this.dataset.msgError);
  })
  .map(function() {
    var target = document.getElementById(this.dataset.equalTo);
    target.equalOf = this;
    return target;
  })
  .on('input', function() {
    $(target.equalOf).trigger('input');
  });
\$\endgroup\$
  • \$\begingroup\$ Hi wOxxOm! Thanks for taking the time to review my code. All is cool, but I don't see where you check if the value of the original (first) field is changed? It doesn't have [data-equal-to], please refer to my HTML. \$\endgroup\$ – Svetoslav Genov Oct 31 '17 at 15:01
  • \$\begingroup\$ I meant that your code is missing the part where I check for the validity of name="addr1" after there was already a match with name="addr2". For example if you enter both addresses correctly, get a match, then change only addr1 without editing the value of addr2. \$\endgroup\$ – Svetoslav Genov Oct 31 '17 at 23:49
  • \$\begingroup\$ Ah, well, I would simply specify data-equal-to attribute on both elements. \$\endgroup\$ – wOxxOm Oct 31 '17 at 23:51
  • \$\begingroup\$ But you must show error notifications only on the confirmation input. Thank you for your time, but I think the idea needs more careful consideration. Anyways, your code is a good example for speed improvement using native JS functions. \$\endgroup\$ – Svetoslav Genov Nov 1 '17 at 8:52
  • \$\begingroup\$ I've added an example of using an additional expando property to re-trigger the event. \$\endgroup\$ – wOxxOm Nov 1 '17 at 9:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.