1

I have a set of radio buttons on a form, and using jQuery I want to detect when a user has changed the value of these inputs. I can do something like $('input.myinputs').on('change', ... );, except that if a user is using a keyboard to navigate, that fires every time they move from one button to the next. In other words it fires multiple times in the process of the user making a selection. (Clicking with a mouse works fine.)

I want to make sure the function only fires when the user leaves the radio group (the set of same-named radio buttons). I should be able to tab into the radio group, navigate up or down via arrow keys, and then when I tab out of the radio group, THEN the function fires once.

Is there a way to do this? Everything I've tried fires every time I hit an arrow key if the focus is within the group.

Example:

You've got a small form:

  1. a text input
  2. a radio group containing four options labelled A, B, C, D; and
  3. a second text input

Click on the first text input. Now put down your mouse and hit the Tab key. Use the arrows to select the third option. Now hit Tab to move to the second text input. That triggers the "change/blur" event on the radio group three times - when the focus moved into the radio group, which selected A; then when you arrowed to B, then again when you arrowed to C.

I want an event triggered one time when I move the focus from the radio group to the second text input (that is, out of the radio group), but only if I changed what was selected in the radio group. I need "on change" for the group instead of for each individual option.

It should still work for mouse or mobile (touch) users of course; I'm accommodating keyboard navigators, while acknowledging that most users use a mouse or their finger.

Example HTML:

<input type="text">
<div class="inputApprove">
    <label><input type="radio" name="R1" value="A" checked="checked"> A</label>
    <label><input type="radio" name="R1" value="B"> B</label>
    <label><input type="radio" name="R1" value="C"> C</label>
    <label><input type="radio" name="R1" value="D"> D</label>
</div>
<input type="text">
Stephen R
  • 3,512
  • 1
  • 28
  • 45

2 Answers2

1

The only thing which comes into my mind is detecting the TAB KEY on the radio inputs. I believe this is your best bet to go.

$("input[name='gender']").on('keydown', function(e){
 if (e.keyCode == 9){ //if tab key is pressed
  $("input[name='gender']").blur(function(){
   $(".output").append("<p>Changed!</p>");  
  });  
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form id="form">
  <input type="radio" name="gender" value="male"> Male<br>
  <input type="radio" name="gender" value="female"> Female<br>
  <input type="radio" name="gender" value="other"> Other<br> 
  <p>
  Say Hello:
  </p>
  <input type="text" name="hello">
</form>
<div class="output">

</div>
Evik Ghazarian
  • 1,803
  • 1
  • 9
  • 24
  • This problem is a catch-22. Tab is a good idea, but while it works for keyboard, it *doesn't* work for mouse. I of course need to accommodate all users. How is it possible that there's no event for the final value after the group is changed (but not during the process of selecting)? I guess I have to separately look for tab key AND mouse clicks? *sigh* Thanks, though. This may be the way to go, albeit a pain. – Stephen R Sep 25 '19 at 15:57
  • 1
    you can create custom events and trigger those events by mouse click or tab key pressed. – Evik Ghazarian Sep 25 '19 at 15:59
  • Okay, so detecting tab key works great for keyboard, but do you know what I should use to detect changes made via mouse? It seems `on( 'click', ... )` registers (as before) every time I use an arrow key to navigate within the radio group. `mousedown` didn't seem to work. What detects mouse clicks but not keyboard? – Stephen R Sep 25 '19 at 23:58
  • Figured it out. My jQuery selector was registering `mouseup` on the little circle that is the button itself, but not the label surrounding it. I had to use this: `$( 'div.container label' ).on( 'mouseup', function() { if( $(this).find( 'input[name=R1]' ) ) { //user clicked on a label that contains the input element// } } );` – Stephen R Sep 26 '19 at 00:49
  • Have you tried this https://stackoverflow.com/questions/7465006/differentiate-between-mouse-and-keyboard-triggering-onclick – Evik Ghazarian Sep 26 '19 at 15:30
  • Yes. Deeper down the rabbit hole I go. I’m now stuck on the point that on an arrow keypress, it triggers the “keydown” every and then separately triggers the “click”. So even if I properly recognize the keystroke, I still get the click, which defeats the whole thing. It’s unimaginable that there isn’t some straightforward way of making this distinction. I can’t possibly be the first person to try this – Stephen R Sep 27 '19 at 02:48
0

Solved, with help from @evik-ghazarian and another question: Differentiate between mouse and keyboard triggering onclick

We listen for keydown or change. If an arrow key is pressed, set a boolean so that we ignore the change event that's about to fire. Otherwise (assuming we haven't been told to ignore change in the previous step), watch for either change, or tab keydown accompanied by an actual change of value.

$(document).ready(function () {
    let radioGroup = $( 'input[name=R1]' );
    let initialValue = radioGroup.filter( ':checked' ).val();
    let firstEvent = true;
    radioGroup.on( 'keydown change', function( e ) {
        let newValue = $( this ).filter( ':checked' ).val();
        if( e.type === 'keydown' && [37,38,39,40].indexOf( e.keyCode ) !== -1 ) { // arrow key
            firstEvent = false;
        } else if (
            true === firstEvent && ( // ignore change event if arrow key, but recognize change when tabbing out
                ( e.type === 'keydown' && e.keyCode === 9 && newValue !== initialValue ) || // tab key
                ( e.type === 'change' )
            )
        ) {
            // **Do whatever you want to do on change here**

            alert( "value = " + newValue );
            initialValue = newValue;
        } else {
            firstEvent = true;
        }
    } );
});

Tested 2019-09-27 in Firefox 69, Chrome 77, Edge 44, IE 11, and Safari on iOS 13.0.

Suggestions for improvement are welcome. I would love to be able to somehow abstract this out to a function somehow, but my JS-fu is not worthy. I hate the idea of copy/pasting this entire pile every time I want to use it, but I do have to be able to customize the "do stuff here" part, and I can't get rid of those darned globals.

Stephen R
  • 3,512
  • 1
  • 28
  • 45