J. Adam Connor
J. Adam Connor

Reputation: 1734

Check if active element has particular class via javascript

I'm writing a single-field form and want the Enter key to advance the form to the next input field. Since there's another form on the page, I only want the Enter key to advance the form when one of the inputs in that form is the activeElement.

I've achieved this with what seems like an extremely verbose if() statement here:

document.addEventListener( 'keydown', function( ev ) {
        var keyCode = ev.keyCode || ev.which;
        var ids = [ 'this', 'that', 'there', 'thing', 'other' ];
        if ( document.getElementById( ids[0] ) === document.activeElement || document.getElementById( ids[1] ) === document.activeElement || document.getElementById( ids[2] ) === document.activeElement || document.getElementById( ids[3] ) === document.activeElement || document.getElementById( ids[4] ) === document.activeElement) {
            if( keyCode === 13 ) {
                ev.preventDefault();
                self._nextQuestion();
            }
        }
    } );

Each of the inputs are the same class: .questions. I've tried something like:

document.addEventListener( 'keydown', function( ev ) {
    var keyCode = ev.keyCode || ev.which;
    var ids = [ 'this', 'that', 'there', 'thing', 'other' ];
    if ( document.querySelector('.questions') === document.activeElement) {
        if( keyCode === 13 ) {
            ev.preventDefault();
            self._nextQuestion();
        }
    }
} );

But of course, this only accesses the first instance of .questions on the page. I don't want to iterate over nodes, as it doesn't seem much better than what I have already.

I'm a novice, and I'm looking for more concise logic.

Upvotes: 4

Views: 10867

Answers (4)

Maciej Smoliński
Maciej Smoliński

Reputation: 326

First of all, you can utilise classList api to check element for given className presence, e.g. document.activeElement && document.activeElement.classList.contains('question').

All the proposed approaches are really helpful and should fix the problem. On the other hand, you might want to keep you application's state in one place so you can manage it easily. By doing so, you make your application more predictable and easier to debug.

Here's an approach you might want to take:

HTML Code:

<input class="question" value="1"/>
<input class="question" value="2"/>
<input class="question" value="3"/>
<input class="question" value="4"/>
<input class="question" value="5"/>

JavaScript Code:

var component = {
  activeQuestion: 0,
  questionSelector: '.question',
  get activeQuestionElement () {
    return component.allQuestionElements[component.activeQuestion];
  },
  get allQuestionElements () {
    return document.querySelectorAll(component.questionSelector);
  },
  next: function () {
    var activeElement = document.activeElement;

    if (activeElement && activeElement === component.activeQuestionElement && component.allQuestionElements[component.activeQuestion+1]) {
      component.activeQuestion++;
      component.highlightQuestion();
    }
  },
  highlightQuestion: function () {
    component.activeQuestionElement.focus();
  },
  install: function () {
    document.addEventListener('keyup', function (event) {
      if (event.which === 13) {
        component.next.call(component);
      }
    });
    document.addEventListener('click', function (event) {
      var className = component.questionSelector.slice(1);

      if (event.target.classList.contains(className)) {
        component.activeQuestion = [].slice.call(document.querySelectorAll(component.questionSelector)).indexOf(event.target);
        component.highlightQuestion();
      }
    })
  }
};

component.install();

As you can see above, component is a single object instance that holds useful information like activeQuestion index, question selector, some computed properties that return DOM elements. The install method binds event listeners which manage the state when events occur.

Here's a demo:

http://jsfiddle.net/maciejsmolinski/xzyvye8z/embedded/result/

When you click enter when any of the fields is focused, it is going to move focus to the next one. In addition to that, clicking on any of the fields changes active question as well. You can easily alter the behaviour by modifying the code I posted above. It is just a basic skeleton you can use to build your own functionality on top of.

This is a very basic component. You can build anything on top of it. You can introduce ES6 syntax (classes, arrow functions, const instead of var) but I intentionally left that part untouched to make it easier to understand.

Hope it helps!

Upvotes: 3

CollinD
CollinD

Reputation: 7573

Just check if the activeElement has the questions class.

var pattern = /(?:^|\s)questions(?:\s|$)/
if (document.activeElement.className.match(pattern)) {
  ...
}

squint provided an improved regex that will account for more funky situations in the classname.

Upvotes: 7

J. Adam Connor
J. Adam Connor

Reputation: 1734

You guys pointed me in the right direction with document.activeElement.className, so I upvoted all who mentioned it, but the most concise solution that works here seems to be this:

document.addEventListener( 'keydown', function( ev ) {
        var keyCode = ev.keyCode || ev.which;
        // enter
        if ( document.activeElement.className === 'questions') {
            if( keyCode === 13 ) {
                ev.preventDefault();
                self._nextQuestion();
            }
        }
    } );

I'll give you guys some time to critique this before I mark it as the answer...

Upvotes: 0

qxz
qxz

Reputation: 3864

Try this:

if ((" "+document.activeElement.className+" ").indexOf(" questions ") > -1) {
    ...
}

Upvotes: 3

Related Questions