Logo
Published on
ยท11 min read

Controlling Multiple Select Boxes Simultaneously with a Single Set of Data (JavaScript)

I apologize in advance for any awkward expressions in English.

English is not my native language, and I have relied on ChatGPT's assistance to proceed with the translation.

Explanation

In the Aspect Ratio Comparison Service, the -- Ratio --, -- Resolution --, and -- Usage -- select boxes all utilize the same data source.

When you select an aspect ratio in one of these select boxes, the corresponding aspect ratios in the other select boxes are automatically disabled.

For instance, if you select Instagram - 1:1 in the -- Usage -- select box, the 1:1 in the -- Ratio -- select box and 1080x1080 - 1:1 in the -- Resolution -- select box will be disabled simultaneously.

Please refer to the GIF image below:

img

Pre-Entry Notes

Here is a source code shared for the purpose of explanation, which has undergone some minor edits. Some parts that were split into functions in the actual source code have been combined, and unnecessary code for this feature has been omitted. Please note that there may be redundant code due to comments for explanation.

Implementation

1. Define Data for Each Select Box

Define the data for aspect ratios, usages, and resolutions, each with its own key.

The code is as follows:

const AspectRatioInfo = function () {
  const originalRatioData = [
    {
      key: '1', // Used as the key and as the value for select > option
      width: 1, // Used for aspect ratio calculation
      height: 1, // Used for aspect ratio calculation
      text: '1:1', // Displayed in the select box
      range: [1], // To find directly calculated ratios within this range
      usages: ['Instagram'], // List of use cases (an array)
      resolutions: ['1080x1080'], // List of resolutions (an array)
    },
    {
      key: '1.33',
      width: 4,
      height: 3,
      text: '4:3 (1.33:1)',
      range: [1.33],
      usages: ['iPad 5/6/7/8/9', 'iPad Mini 2/3/4/5', 'iPad Air 1/2/3', 'iPad Pro 12.9'],
      resolutions: ['800x600', '1024x768', '1600x1200', '2048x1536'],
    },
    {
      key: '1.77',
      width: 16,
      height: 9,
      text: '16:9 (1.77:1)',
      range: [1.77, 1.78],
      usages: ['iPhone 5/6/7/8', 'iMac', 'Galaxy Note 5/9', 'YouTube'],
      resolutions: [
        '640x360',
        '1280x720',
        '1600x900',
        '1920x1080',
        '2560x1440',
        '3200x1800',
        '3840x2160',
        '5120x2880',
        '7680x4320',
      ],
    },
    // ... and so on ...
  ]

  // ... Function for Data Filtering (explained below) ...
}

2. Filtering Functions for Data Display in Each Select Box

const AspectRatioInfo = function () {
  // ... Declaration of originalRatioData, as explained above, is omitted ...

  // Data sorting for the '-- Ratio --' select box
  const getListOfWidthHeight = function () {
    const listOfWidthHeight = originalRatioData
      .sort(function (a, b) {
        return a.width - b.width
      })
      .map(function (ratio) {
        return {
          key: ratio.key,
          width: ratio.width,
          height: ratio.height,
          text: ratio.text,
          ratio: ratio.range[0],
        }
      })

    return listOfWidthHeight
  }

  // Data filtering and sorting for the '-- Resolution --' select box
  const getListOfResolution = function () {
    const resolutionArray = []

    // Utilizing the 'resolutions' array in originalRatioData
    originalRatioData.forEach(function (ratio) {
      ratio.resolutions.forEach(function (resolution) {
        resolutionArray.push({
          key: ratio.key,
          resolution: resolution,
          text: ratio.text,
        })
      })
    })

    return resolutionArray.sort(function (a, b) {
      const [a1, a2] = a.resolution.split('x').map(Number)
      const [b1, b2] = b.resolution.split('x').map(Number)
      return a1 - b1 || a2 - b2
    })
  }

  // Data filtering and sorting for the '-- Usage --' select box
  const getListOfUsage = function () {
    const usageArray = []

    // Utilizing the 'usages' array in originalRatioData
    originalRatioData.forEach(function (ratio) {
      ratio.usages.forEach(function (usage) {
        usageArray.push({
          key: ratio.key,
          usage: usage,
          text: ratio.text,
        })
      })
    })

    return usageArray.sort(function (a, b) {
      return a.usage.localeCompare(b.usage)
    })
  }
}

3. Creating Select Boxes

<select id="selRatio" class="input-select-short">
  <option value="">-- Ratio --</option>
</select>
<select id="selResolution" class "input-select-short">
  <option value="">-- Resolution --</option>
</select>
<select id="selUsage" class="input-select-short">
  <option value="">-- Usage --</option>
</select>
const aspectRatioInfo = new AspectRatioInfo()

window.addEventListener('load', function () {
  setSelectBox()
})

function setSelectBox() {
  // Select Box -- Ratio --
  createSelectBox(document.getElementById('selRatio'), aspectRatioInfo.getListOfWidthHeight())
  // Select Box -- Resolution --
  createSelectBox(document.getElementById('selResolution'), aspectRatioInfo.getListOfResolution())
  // Select Box -- Usage --
  createSelectBox(document.getElementById('selUsage'), aspectRatioInfo.getListOfUsage())
}

//  Creating Select Boxes
function createSelectBox(element, list) {
  list.forEach(function (item) {
    const optionElement = document.createElement('option')
    optionElement.value = item.key
    // Parts where the displayed text content should vary based on each select box
    if (element.id === 'selResolution') {
      optionElement.innerHTML = `${item.resolution} - ${item.text}`
    } else if (element.id === 'selUsage') {
      optionElement.innerHTML = `${item.usage} - ${item.text}`
    } else {
      optionElement.innerHTML = item.text
    }
    element.appendChild(optionElement)
  })
}

4. Disabling the Corresponding Ratio in All Select Boxes When Selected in One

const AspectRatioInfo = function () {
  const getRatioInfoByKey = function (key) {
    const ratioInfo = originalRatioData.find(function (ratio) {
      return ratio.key === key
    })
    return ratioInfo
  }
}

// ---------------------------------

const aspectRatioInfo = new AspectRatioInfo()
let selectedAspectRatiosState = [] // Selected aspect ratio

// Event handling when a select box is chosen
window.addEventListener('load', function () {
  document.addEventListener('change', function (event) {
    if (event.target.tagName.toLowerCase() === 'select') {
      const selectKey = event.target.value
      // Retrieve data for the selected key and add it to selectedAspectRatiosState
      const info = aspectRatioInfo.getRatioInfoByKey(selectKey)
      info && addSelectedAspectRatiosState(info)
      event.target.value = ''
    }
  })
})

// If there is already a selected ratio, return; otherwise, add it
function addSelectedAspectRatiosState(info) {
  if (selectedAspectRatiosState.find((item) => item.key === info.key)) {
    return
  }
  setSelectedAspectRatiosState([...selectedAspectRatiosState, info])
}

// Add the selected ratio and call the disable function
function setSelectedAspectRatiosState(newState) {
  selectedAspectRatiosState = newState
  selectedKeyDisabled()
}

// Disable the selected ratios
function selectedKeyDisabled() {
  const optionElementList = document.querySelectorAll('option')

  optionElementList.forEach(function (optionElement) {
    if (
      selectedAspectRatiosState.find(function (selectedKey) {
        return selectedKey.key === optionElement.value
      })
    ) {
      optionElement.disabled = true
    } else {
      optionElement.disabled = false
    }
  })
}

Check it on JSFiddle

https://jsfiddle.net/sosone/96zvLr4s/