🛠️ Tabby: Refactoring
      We wrote two chunks of code for Tabby. One for listening to a user’s click. Another for listening to Left and Right arrow keys.
We can simplify these two events.
The click event
Here’s the code for the click event:
tabsList.addEventListener('click', event => {
  const tab = event.target
  const target = tab.dataset.target
  const tabContent = tabby.querySelector('#' + target)
  // Selects a tab
  tabs.forEach(t => {
    t.classList.remove('is-selected')
    t.setAttribute('tabindex', '-1')
  })
  tab.classList.add('is-selected')
  tab.removeAttribute('tabindex')
  // Selects the corresponding tab content
  tabContents.forEach(c => c.classList.remove('is-selected'))
  tabContent.classList.add('is-selected')
})
Most of the code here is used to select a tab (and its tab-contents). We can group these code into a function called selectTab.
const selectTab = _ => {
  // Do something
}
First, let’s copy-paste everything over.
const selectTab = _ => {
  const tab = event.target
  const target = tab.dataset.target
  const tabContent = tabby.querySelector('#' + target)
  // Selects a tab
  tabs.forEach(t => {
    t.classList.remove('is-selected')
    t.setAttribute('tabindex', '-1')
  })
  tab.classList.add('is-selected')
  tab.removeAttribute('tabindex')
  // Selects the corresponding tab content
  tabContents.forEach(c => c.classList.remove('is-selected'))
  tabContent.classList.add('is-selected')
}
If you look at the code, you know we need these variables:
- tab
- tabs
- tabContent
- tabContents
We can get them from these locations:
- tab: from- event.target
- tabs: from the global scope
- tabContent: from- tab
- tabContents: from the global scope
This means: We only need the tab variable in selectTab. We can pass tab directly into selectTab. We don’t have to introduce the entire event object.
const selectTab = tab => {
  const target = tab.dataset.target
  const tabContent = tabby.querySelector('#' + target)
  // ...
}
Using selectTab:
tabsList.addEventListener('click', event => {
  const tab = event.target
  selectTab(tab)
})
It became much easier to understand what the click event handler does!
The keydown event
Here’s what we wrote for switching tabs with Left and Right arrow keys.
document.addEventListener('keydown', event => {
  const { key } = event
  if (key !== 'ArrowLeft' && key !== 'ArrowRight') return
  if (!event.target.matches('.tab')) return
  const index = tabs.findIndex(t => t.classList.contains('is-selected'))
  let targetTab
  if (key === 'ArrowLeft' && index !== 0) targetTab = tabs[index - 1]
  if (key === 'ArrowRight' && index !== tabs.length - 1)
    targetTab = tabs[index + 1]
  if (targetTab) targetTab.click()
})
In the first three lines, we checked whether we want to act on the event. These three lines cannot be moved anywhere else.
document.addEventListener('keydown', event => {
  const { key } = event
  if (key !== 'ArrowLeft' && key !== 'ArrowRight') return
  if (!event.target.matches('.tab')) return
  // ...
})
The next few lines are used to get the target tab.
document.addEventListener('keydown', event => {
  // ...
  const index = tabs.findIndex(t => t.classList.contains('is-selected'))
  let targetTab
  if (key === 'ArrowLeft' && index !== 0) targetTab = tabs[index - 1]
  if (key === 'ArrowRight' && index !== tabs.length - 1)
    targetTab = tabs[index + 1]
})
Here, we tried to:
- Decide whether we should get previous or next tab
- Find the previous or next tab
- Trigger a click event
To simplify the code, we can create getPreviousTab and getNextTab functions.
const getPreviousTab = _ => {
  // Do something
}
const getNextTab = _ => {
  // Do something
}
First, let’s copy-paste the code we used to get the previous and next tabs.
const getPreviousTab = _ => {
  if (key === 'ArrowLeft' && index !== 0) targetTab = tabs[index - 1]
}
const getNextTab = _ => {
  if (key === 'ArrowRight' && index !== tabs.length - 1)
    targetTab = tabs[index + 1]
}
The purpose of getPreviousTab is to find the previous tab. Likewise for getNextTab. The key a user pressed should not matter, so let’s remove the key part.
const getPreviousTab = _ => {
  if (index !== 0) targetTab = tabs[index - 1]
}
const getNextTab = _ => {
  if (index !== tabs.length - 1) targetTab = tabs[index + 1]
}
This makes more sense.
We need to return the previous tab in getPreviousTab. We also need to return the next tab in getNextTab.
const getPreviousTab = _ => {
  if (index !== 0) {
    return tabs[index - 1]
  }
}
const getNextTab = _ => {
  if (index !== tabs.length - 1) {
    return tabs[index + 1]
  }
}
You can tell we need two variables for both getPreviousTab and getNextTab
- tabs
- index
We can get tabs from the global scope, so we only need to pass in index to getPreviousTab and getNextTab.
const getPreviousTab = index => {
  if (index !== 0) {
    return tabs[index - 1]
  }
}
const getNextTab = index => {
  if (index !== tabs.length - 1) {
    return tabs[index + 1]
  }
}
Using getPreviousTab and getNextTab:
document.addEventListener('keydown', event => {
  // ...
  const index = tabs.findIndex(t => t.classList.contains('is-selected'))
  let targetTab
  if (key === 'ArrowLeft') targetTab = getPreviousTab(index)
  if (key === 'ArrowRight') targetTab = getNextTab(index)
  // ...
})
This makes more sense now. It reads:
- If key is left, we grab the previous tab
- If key is right, we grab the next tab
There’s one final improvement we can make. When we found targetTab, we triggered a “click” with it.
document.addEventListener('keydown', event => {
  // ...
  if (targetTab) {
    targetTab.click()
  }
})
If you read this code, you understand we triggered a click event, which selects the appropriate tab with selectTab.