How can I configure Featherlight to support tabindex within the modal?
Asked Answered
C

7

6

My form (look at the demo fiddle here, and I've also pasted some code below) seems not to support tabindex inside the Featherlight modal.

I think it's because of this part of Featherlight here.

How can I display a form within a modal and still let a user press the TAB key to bounce to the next field?

It seems like I need to add a hack into the Featherlight library, which I'd rather not do.

Thanks!


<div style="display: none;">
  <div id="modal">
    My form NEEDS to support tabindex. If Featherlight is going to set tabindex to -1 for anything (even temporarily), it should only be for elements that aren't inside the modal.
    <div class="mainInputWrapper">
      <input type="text" name="fullname" placeholder="Your Name" required id="firstname" /><span class="forValidationDisplay"></span>
    </div>
    <div class="mainInputWrapper">
      <input type="email" name="email" placeholder="Your Best Email Address*" required id="email" /><span class="forValidationDisplay"></span>
    </div>
    <button>
      Submit
    </button>
  </div>
</div>
<a class="role-element leadstyle-link" href="#" data-featherlight="#modal">Click here if you're interested</a>
Calcite answered 14/2, 2017 at 19:40 Comment(0)
C
9

I found a way to override Featherlight and avoid editing its source code.

    $.featherlight._callbackChain.beforeOpen = function (event) {
        //https://mcmap.net/q/1599760/-how-can-i-configure-featherlight-to-support-tabindex-within-the-modal/470749
        //By overriding this function, I can prevent the messing up of tabindex values done by: https://github.com/noelboss/featherlight/blob/master/src/featherlight.js#L559            
    };
    $.featherlight._callbackChain.afterClose = function (event) {
        //See note above in $.featherlight._callbackChain.beforeOpen
    };
    $.featherlight.defaults.afterContent = function (event) {
        var firstInput = $('.featherlight-content #firstname');
        console.log('Considering whether to focus on input depending on window size...', $(window).width(), $(window).height(), firstInput);
        if (Math.min($(window).width(), $(window).height()) > 736) {//if the smallest dimension of the device is larger than iPhone6+
            console.log('yes, focus');
            firstInput.attr('autofocus', true);
        }
    };
Calcite answered 19/2, 2017 at 23:43 Comment(4)
Absolutely brilliant!Loren
Worth noting: just include this after you include featherlight in your page, the last function (afterContent) is not required for this to work.Zachariahzacharias
This fixes the issue, but comes with other problems (namely, it breaks the disable scrolling feature).Noncompliance
It is now 4 years later and this bug still exists in featherlight. Thanks for this answer, I was pulling my non-existing hair trying to figure out why Gravity Forms was suddenly adding "-1" for all tabindex's. Edit: I just realized that featherlight is not maintained.Junction
N
4

Unfortunately, the accepted answer breaks the disable scrolling functionality.

Luis Paulo Lohmann's answer is much better IMHO. Here is a slightly improved version of it:

afterContent: function () {
    $('.featherlight-content').find('a, input[type!="hidden"], select, textarea, iframe, button:not(.featherlight-close), iframe, [contentEditable=true]').each(function (index) {
        if (index === 0) {
            $(this).prop('autofocus', true);
        }
        $(this).prop('tabindex', 0);
    });
}

Improvements:

Noncompliance answered 4/9, 2018 at 9:1 Comment(2)
Not a nice move to go downvoting all the answers that made it easier for you to construct your own. Imagine if everyone on this site used the voting that way.Calcite
@Calcite I downvoted the answers, which destroy existing features and commented what they break. I upvoted the answer which helped (the one from Luis). This leads to a better sorting of the answers. Everyone should use the site that way.Noncompliance
D
3

Just in case you wonder, the last statement of @Ryan's code is not required.

I added this at the end of the source code (because in my case I don't mind editing the source code) and it worked:

$.featherlight._callbackChain.beforeOpen = function (event) {
        //https://mcmap.net/q/1599760/-how-can-i-configure-featherlight-to-support-tabindex-within-the-modal/470749
        //By overriding this function, I can prevent the messing up of tabindex values done by: https://github.com/noelboss/featherlight/blob/master/src/featherlight.js#L559
};
$.featherlight._callbackChain.afterClose = function (event) {
    //See note above in $.featherlight._callbackChain.beforeOpen
};
Dacca answered 8/6, 2017 at 11:16 Comment(1)
This fixes the issue, but comes with other problems (namely, it breaks the disable scrolling feature).Noncompliance
R
2

My workaround to the tabindex=-1 problem:

        afterContent: function(event) {
            $j('.featherlight-content input').each(function(index){
                if(index == 1) {
                    $j(this).prop('autofocus',true);
                }
                $j(this).prop('tabindex',index);
            });
        }
Rivard answered 9/8, 2017 at 20:11 Comment(0)
C
1

I use a small jquery function to fix tabIndex :

$.fn.fixTabIndex = function(){

    // We don't modify tabindex or focus for hidden inputs
    this.find('input[type!="hidden"]').each(function(i, e){

        var $this = $(this);

        // Focus on first input with data-focus attribute
        if($this.data('focus')){
            $this.focus();
        }

        // Set tabindex with current index plus a random number (default 100)
        $this.prop('tabindex', 100 + i);
    });

    return this; // chainable methods
};

One should add data-focus="true" to desired input.

Usage :

    $(featherlight-selector).featherlight({
        targetAttr: 'href',
        afterContent: function(){
            $(form-selector)fixTabIndex();
        }
    });

Maybe there is a better way to select form...

Copalm answered 16/8, 2018 at 8:48 Comment(0)
F
0

There doesn't seem to be a good reason why the buttons inside the featherlight would remain tabbable but not the input.

I'll fix the bug.

Sorry about this.

Feininger answered 14/2, 2017 at 20:20 Comment(0)
Z
0

Kudos to @Ryan for pointing me in the right direction. I have this working in a definitely-a-hack sort of way. Instead of resetting the content within featherlight I updated the original .not to ignore anything contained within the featherlight instance. That left me with the following. There are certainly some not best practices bits in here that could use some work, like the temp instance and timeout to wait for the contents to update, but I don't have any more time to work on it right now.

This is based off of the source with a change to the second .not. It maintains the no tabbing outside modal, no scroll, and return to original focus point.

$.featherlight._callbackChain.beforeOpen = function (_super, event) {
  $(document.documentElement).addClass('with-featherlight');

  // Remember focus:
  this._previouslyActive = document.activeElement;

  // Disable tabbing outside the current instance:
  // See https://mcmap.net/q/98426/-which-html-elements-can-receive-focus
  // Starting point https://mcmap.net/q/1599760/-how-can-i-configure-featherlight-to-support-tabindex-within-the-modal/42235457#42235457.
  // Updates here. Don't give the tabindex to elements within the currently open featherlight container.
  // This wasn't working with the current state of the content. The intended content is not yet in the instance,
  // the timeout forces its hand.
  var tempInstance = this.$instance.get(0);
  setTimeout(function() {
    this._$previouslyTabbable = $("a, input, select, textarea, iframe, button, iframe, [contentEditable=true]")
      .not('[tabindex]')
      .not(function (index, element) {
        return $.contains(tempInstance, element)
      });

    this._$previouslyWithTabIndex = $('[tabindex]').not('[tabindex="-1"]');
    this._previousWithTabIndices = this._$previouslyWithTabIndex.map(function (_i, elem) {
      return $(elem).attr('tabindex');
    });

    this._$previouslyWithTabIndex.add(this._$previouslyTabbable).attr('tabindex', -1);
  }.bind(this), 100);

  if (document.activeElement.blur) {
    document.activeElement.blur();
  }
  return _super(event);
};
Zeba answered 24/8, 2020 at 20:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.