Search code examples
arraysecmascript-5splice

ES5 Javascript: Get unique value from 2 selector and add/update to array


On my web page there are 2 selectors - one is a tab, and another is a dropdown. Based on user clicks on the tab and the dropdown, I get the click value and push it unto an array. There are at most 2 elements in the array - both elements need to be the unique value mapped to tab and dropdown selection respectively:

Tag1 -> value from Tab selection
Tag2 -> value from drop down

To add/update the array to a specific location, I used Array.prototype.splice()

I managed to push the selection values from the tab and the dropdown in the array. However, I run into some problem scenarios as described below.

Kindly see test code below:

// Get all tabs
var tabs = document.querySelectorAll('.tab')
// Get select element
var select = document.querySelector('select')
// Create tag array
var tags = []
var tagText = document.querySelector('#tags')

// Register click event listeners for tabs
if (tabs.length > 0) {
  tabs.forEach(function (tab) {
    tab.addEventListener('click', handleTabClick, true)
  })
}

// Register click event listener for select element
if (select) {
  select.addEventListener('change', handleSelectChange, true)
}

// Handle tab click
function handleTabClick(e) {
  var currentElement = e.target

  // Remove all active modifiers
  tabs.forEach(function (tab) {
    tab.classList.remove('active')
  })

  // Apply the active modifer to the current element
  currentElement.classList.add('active')

  // Update the UI showing the correct pane by ID
  var id = currentElement.dataset.targetPane

  if (id != 'tab-all') {
    // Add selected id to array
    tags.splice(0, 1, id)
  } else {
    tags.splice(0, 1, '')
  }

  tagText.innerHTML = tags
}

// Handle select change
function handleSelectChange(e) {
  var id = e.target.value

  if (id != 'all') {
    // Add selected id to array
    tags.splice(1, 1, id)
  } else {
    tags.splice(1, 1, '')
  }

  tagText.innerHTML = tags
}
.tabs {
  padding-left: 0;
  list-style-type: none;
  display: flex;
}

.tab {
  flex: 1;
  text-align: center;
  padding: 10px;
  cursor: pointer;
  color: #000000;
  border: 1px solid #d1d1d1;
}

.active {
  border-bottom: 3px solid red;
}
Tab options (1st in tags array): Tag 1
<ul class="tabs">
  <li class="tab active" data-target-pane="tab-all">All
  </li>
  <li class="tab" data-target-pane="finance">
    Finance
  </li>
  <li class="tab" data-target-pane="energy">
    Energy
  </li>
</ul>
Dropdown options (2nd in tags array): Tag 2
<label class="select">
  <select>
    <option value="all" selected="">All</option>
    <option value="whitepaper">Whitepaper</option>
    <option value="articles">Articles</option>
    <option value="video">Video</option>
  </select>
</label>
<p>Return tags array: <b><span id="tags"></span></b></p>
<p>Tag 1 -> value from Tab<br>
Tag 2 -> value from dropdown<br><br>Expect the tags array output to always be [Tag 1 , Tag 2].<br>Max 2 item in array. If user selected "All", empty the previous selection within the same Tag (1 or 2) array location.</p>

Problem scenario (undesirable result)

1) If the user first selects a dropdown option (Tag2) and than selects another dropdown option (Tag2): the Tags array then has [Tag2, Tag2]. Expected: [Tag2]

2) If the user first selects a dropdown option (Tag2) and than selects a tab (Tag1): the Tags array first has [Tag2] and it gets replaced with [Tag1]. Expected: [Tag2 , Tag1]

3) If the user first selects a dropdown option (Tag2) and than selects another dropdown option (Tag2), and lastly selects the dropdown value "All" (Tag2): the Tags array first has [Tag2, Tag2] and with "All" selected it becomes [Tag2,]. Expected: first [Tag2] and with "All" selected, []

Ideal scenario

1) If the user first selected a tab (Tag1) before selecting a dropdown option (Tag2): Tags array should get [Tag1, Tag2].

2) If the user selected "All", empty the previous selection within the same Tag(1 or 2) array location. For example, user first selected a tab (Tag1) and than selects tab "All". Tags array gets []. If the user first selected a tab (Tag1) and selected a dropdown option (Tag2), and than selects the dropdown "All" value. Tag array should become [Tag1]

I expect the tags array to be one of the following:

1) Max 2 items in array -> [Tag1 , Tag2] or [Tag2 , Tag1]

2) Single array element [Tag1] or [Tag2]

But it should never have [Tag1, Tag1] or [Tag2, Tag2]

In short, the array should have unique values corresponding to the tab selection and dropdown selection.


Solution

  • As you sometimes want a result with [Tag 1, Tag 2] and other times [Tag 2, Tag 1], it would be good to remember in which slot you last stored the Tag 1 selection, so that you know which of the two to replace.

    I will assume that the last made selection will always be the last value in the array as well.

    So I have introduced the variable tabIndex which is either 0 or 1. Also, I have adapted the code so that it first removes the current value from the array (from the index that can be derived from tabIndex) and then pushes the selected value to the end of the array, updating tabIndex at the same time.

    // Get all tabs
    var tabs = document.querySelectorAll('.tab')
    // Get select element
    var select = document.querySelector('select')
    // Create tag array
    var tags = []
    var tagText = document.querySelector('#tags')
    var tabIndex = 0;
    
    // Register click event listeners for tabs
    tabs.forEach(function (tab) {
        tab.addEventListener('click', handleTabClick, true)
    })
    
    // Register click event listener for select element
    if (select) {
      select.addEventListener('change', handleSelectChange, true)
    }
    
    // Handle tab click
    function handleTabClick(e) {
        var currentElement = e.target
    
        // Remove all active modifiers
        tabs.forEach(function (tab) {
            tab.classList.remove('active')
        })
    
        // Apply the active modifer to the current element
        currentElement.classList.add('active')
    
        // Update the UI showing the correct pane by ID
        var id = currentElement.dataset.targetPane
    
        tags.splice(tabIndex, 1)
        if (id != 'tab-all') {
            // Add selected id to array
            tabIndex = tags.length
            tags.push(id)
        }
        tagText.textContent = JSON.stringify(tags)
    }
    
    // Handle select change
    function handleSelectChange(e) {
        var id = e.target.value
    
        tags.splice(1-tabIndex, 1)
        if (id != 'all') {
            // Add selected id to array
            tabIndex = 1 - tags.length;
            tags.push(id)
        }
    
        tagText.textContent = JSON.stringify(tags)
    }
    .tabs {
      padding-left: 0;
      list-style-type: none;
      display: flex;
    }
    
    .tab {
      flex: 1;
      text-align: center;
      padding: 10px;
      cursor: pointer;
      color: #000000;
      border: 1px solid #d1d1d1;
    }
    
    .active {
      border-bottom: 3px solid red;
    }
    Tab options (1st in tags array): Tag 1
    <ul class="tabs">
        <li class="tab active" data-target-pane="tab-all">All</li>
        <li class="tab" data-target-pane="finance">Finance</li>
        <li class="tab" data-target-pane="energy">Energy</li>
    </ul>
    Dropdown options (2nd in tags array): Tag 2
    <label class="select">
      <select>
        <option value="all" selected="">All</option>
        <option value="whitepaper">Whitepaper</option>
        <option value="articles">Articles</option>
        <option value="video">Video</option>
      </select>
    </label>
    <p>Return tags array: <b><span id="tags"></span></b></p>
    <p>Tag 1 -> value from Tab<br>
    Tag 2 -> value from dropdown<br><br>Expect the tags array output to always be [Tag 1 , Tag 2].<br>Max 2 item in array. If user selected "All", empty the previous selection within the same Tag (1 or 2) array location.</p>