3
\$\begingroup\$

I'm wondering how to "beautify"/"simplify" this code:

function handleKeyDown (e) {
  if (e.key === 'Enter') {
    e.preventDefault()
    myCustomEvent(e)
    return
  }
  if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
    e.preventDefault()
    e.key === 'ArrowDown'
      ? document &&
        document.activeElement &&
        document.activeElement.nextElementSibling &&
        document.activeElement.nextElementSibling.focus()
      : document &&
        document.activeElement &&
        document.activeElement.previousElementSibling &&
        document.activeElement.previousElementSibling.focus()
  }
}

It seems too verbose to me. Is there something I'm doing wrong? How can I write it better?

\$\endgroup\$

3 Answers 3

4
\$\begingroup\$

Through clever usage of destructuring and using objects to store possible events, you can do this.

function focus(key){
  const {activeElement:{[key]: elementSibling} = {}} = document;
  if(elementSibling){
    elementSibling.focus();
  }
}

const ACTIONS = {
  ArrowDown: () => focus('nextElementSibling'),
  ArrowUp: () => focus('previousElementSibling'),
  Enter: (e) => myCustomEvent(e)
}

function handleKeyDown (e) {
  const handler = ACTIONS[e.key];
  if(handler) {
    e.preventDefault();
    handler(e);
  }
}

Here is a working example:

function focus(key){
  const {activeElement:{[key]: elementSibling} = {}} = document;
  if(elementSibling){
    elementSibling.focus();
  }
}

const ACTIONS = {
  ArrowDown: () => focus('nextElementSibling'),
  ArrowUp: () => focus('previousElementSibling'),
  Enter: (e) => myCustomEvent(e)
}

function handleKeyDown (e) {
  console.log(e.key, e.target);
  const handler = ACTIONS[e.key];
  if(handler) {
    e.preventDefault();
    handler(e);
  }
}

// simulating an event
window.addEventListener('keyup', handleKeyDown);
<h3>Click on an element then press ArrowUp or ArrowDown</h3>
<input type="text"/>
<input type="text"/>
<input type="text"/>
<input type="text"/>

\$\endgroup\$
5
  • 1
    \$\begingroup\$ Really amazing! Do you mean const { activeElement: { [key]: elementSibling } = {} } = document, right? \$\endgroup\$
    – Fred Hors
    Commented Jun 14, 2020 at 20:24
  • 1
    \$\begingroup\$ @FredHors good catch and no worries \$\endgroup\$
    – kockburn
    Commented Jun 14, 2020 at 20:28
  • \$\begingroup\$ I'm trying to use this code with Typescript today and it errors with: (parameter) key: string Type 'Element | {} | null' has no matching index signature for type 'string'.ts(2537). Any fix? \$\endgroup\$
    – Fred Hors
    Commented Mar 15, 2021 at 18:32
  • \$\begingroup\$ Idk, I need more information to be able to help you. \$\endgroup\$
    – kockburn
    Commented Mar 15, 2021 at 23:45
  • \$\begingroup\$ stackoverflow.com/questions/66643784/… \$\endgroup\$
    – Fred Hors
    Commented Mar 16, 2021 at 10:18
2
\$\begingroup\$

well I have a question, does document && document.activeElement is mandatory?

If so, then there is a way to refactor it:

function handleKeyDown (e) {
  if (e.key === 'Enter') {
    e.preventDefault()
    myCustomEvent(e)
    return
  }
  if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
    e.preventDefault()
    focusSibling(e.key === 'ArrowDown' && document && document.activeElement)
  }
}

function focusSibling(isNextElemSibling) {
  if (isNextElementSibling && document.activeElement.nextElementSibling)
    document.activeElement.nextElementSibling.focus()
  else if (document.activeElement.previousElementSibling)
    document.activeElement.previousElementSibling.focus()
}

Note: Probably you didn't know, but when you do an if or a boolean (true or false) condition the program executes an AND && operator from left to right, and if the first value is false, it stops the comparison, because if one value in an AND is false, the statement is false. So, that was a way to simplify your code.

If document && document.activeElement is not mandatory, then only change this line:

    focusSibling(e.key === 'ArrowDown')

I hope it helped you.

\$\endgroup\$
2
\$\begingroup\$

It may be wise to consider not omitting semicolons in your code

To streamline the code, you can try extracting the 'focus'-function and use a small object to determine where to focus.

const tryToFocus = key => {
  e.preventDefault();
  const prevNext = {
    ArrowDown: "nextElementSibling",
    ArrowUp: "previousElementSibling"
  };
  if (Object.keys(prevNext).find(k => key === k)) {
    document &&
    document.activeElement &&
    document.activeElement[prevNext[key]] &&
    document.activeElement[prevNext[key]].focus();
  }
}

function handleKeyDown (e) {
  if (e.key === 'Enter') {
    e.preventDefault(); // may be handled in myCustomEvent?
    return myCustomEvent(e);
  }
  tryToFocus(e.key);
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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