Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Auto resizing the SELECT element according to selected OPTION's width

I've got this select element with different option in it. Normally the select element would get its width from the biggest option element, but I want the select element to have the default option value's width which is shorter. When the user selects another option, the select element should resize itself so the whole text is always visible in the element.

$(document).ready(function() {
    $('select').change(function(){
        $(this).width($('select option:selected').width());
    });
});

Problem:

  • On Chrome (Canary) it always gets a width returned of 0.
  • On Firefox the width get's added and not resized when selected a shorter option.
  • Safari: Same result as Chrome.

Demo @ JSFiddle

like image 554
Th3Alchemist Avatar asked Nov 20 '13 09:11

Th3Alchemist


People also ask

How do you auto adjust width in CSS?

Using width, max-width and margin: auto; Then, you can set the margins to auto, to horizontally center the element within its container. The element will take up the specified width, and the remaining space will be split equally between the two margins: This <div> element has a width of 500px, and margin set to auto.


4 Answers

You are right there is no easy or built-in way to get the size of a particular select option. Here is a JsFiddle that does what you want.

It is okay with the latest versions of Chrome, Firefox, IE, Opera and Safari.

I have added a hidden select #width_tmp_select to compute the width of the visible select #resizing_select that we want to be auto resizing. We set the hidden select to have a single option, whose text is that of the currently-selected option of the visible select. Then we use the width of the hidden select as the width of the visible select. Note that using a hidden span instead of a hidden select works pretty well, but the width will be a little off as pointed out by sami-al-subhi in the comment below.

$(document).ready(function() {
  $('#resizing_select').change(function(){
    $("#width_tmp_option").html($('#resizing_select option:selected').text()); 
    $(this).width($("#width_tmp_select").width());  
  });
});
#resizing_select {
  width: 50px;
} 
 
#width_tmp_select{
  display : none;
} 
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>

<select id="resizing_select">
  <option>All</option>
  <option>Longer</option>
  <option>A very very long string...</option>
</select>

<select id="width_tmp_select">
  <option id="width_tmp_option"></option>
</select>
like image 100
kmas Avatar answered Oct 13 '22 10:10

kmas


Here's a plugin I just wrote for this question that dynamically creates and destroys a mock span so it doesn't clutter up your html. This helps separate concerns, lets you delegate that functionality, and allows for easy reuse across multiple elements.

Include this JS anywhere:

(function($, window){
  $(function() {
    let arrowWidth = 30;

    $.fn.resizeselect = function(settings) {  
      return this.each(function() { 

        $(this).change(function(){
          let $this = $(this);

          // get font-weight, font-size, and font-family
          let style = window.getComputedStyle(this)
          let { fontWeight, fontSize, fontFamily } = style

          // create test element
          let text = $this.find("option:selected").text();
          let $test = $("<span>").html(text).css({
            "font-size": fontSize, 
            "font-weight": fontWeight, 
            "font-family": fontFamily,
            "visibility": "hidden" // prevents FOUC
          });

          // add to body, get width, and get out
          $test.appendTo($this.parent());
          let width = $test.width();
          $test.remove();

          // set select width
          $this.width(width + arrowWidth);

          // run on start
        }).change();

      });
    };

    // run by default
    $("select.resizeselect").resizeselect();                       
  })
})(jQuery, window);

You can initialize the plugin in one of two ways:

  1. HTML - Add the class .resizeselect to any select element:

    <select class="btn btn-select resizeselect">
       <option>All</option>
       <option>Longer</option>
       <option>A very very long string...</option>
     </select>
     
  2. JavaScript - Call .resizeselect() on any jQuery object:

    $("select").resizeselect()

Demo in jsFiddle and StackSnippets:

(function($, window){
  $(function() {
    let arrowWidth = 30;

    $.fn.resizeselect = function(settings) {  
      return this.each(function() { 

        $(this).change(function(){
          let $this = $(this);

          // get font-weight, font-size, and font-family
          let style = window.getComputedStyle(this)
          let { fontWeight, fontSize, fontFamily } = style

          // create test element
          let text = $this.find("option:selected").text();
          let $test = $("<span>").html(text).css({
            "font-size": fontSize, 
            "font-weight": fontWeight, 
            "font-family": fontFamily,
            "visibility": "hidden" // prevents FOUC
          });

          // add to body, get width, and get out
          $test.appendTo($this.parent());
          let width = $test.width();
          $test.remove();

          // set select width
          $this.width(width + arrowWidth);

          // run on start
        }).change();

      });
    };

    // run by default
    $("select.resizeselect").resizeselect();                       
  })
})(jQuery, window);
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>

<select class="resizeselect">
  <option>All</option>
  <option>Longer</option>
  <option>A very very long string...</option>
</select>

Updated to include sizing suggestions from Garywoo & Eric Warnke

like image 35
KyleMit Avatar answered Oct 13 '22 10:10

KyleMit


This working solution makes use here of a temporary auxiliary select into which the selected option from the main select is cloned, such that one can assess the true width which the main select should have.

The nice thing here is that you just add this code and it's applicable to every selects, thus no need to ids and extra naming.

$('select').change(function(){
  var text = $(this).find('option:selected').text()
  var $aux = $('<select/>').append($('<option/>').text(text))
  $(this).after($aux)
  $(this).width($aux.width())
  $aux.remove()
}).change()
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<select>
  <option>REALLY LONG TEXT, REALLY LONG TEXT, REALLY LONG TEXT</option>
  <option>ABCDEFGHIJKL</option>
  <option>ABC</option>
</select>
like image 13
João Pimentel Ferreira Avatar answered Oct 13 '22 10:10

João Pimentel Ferreira


Here's a more modern vanilla JS approach to solve this. It's more or less the same principle like in this answer just without jQuery.

  1. Get the select element and listen for changes on it.
  2. Create a new select element and option and pass the text of the current selectedIndex to the option.
  3. Add position: fixed and visibility: hidden styles to the new select element. This ensures, that it is not affecting your layout but its bounding box can still be measured.
  4. Append the option to the select.
  5. Append the select to the original select element.
  6. Get the needed dimensions of that new one using getBoundingClientRect().width
  7. Set the width of the original one based on the dimensions of the new one.
  8. Remove the new one.
  9. Dispatch a change event to trigger this logic initially.

const select = document.querySelector('select')

select.addEventListener('change', (event) => {
  let tempSelect = document.createElement('select'),
      tempOption = document.createElement('option');

  tempOption.textContent = event.target.options[event.target.selectedIndex].text;
  tempSelect.style.cssText += `
      visibility: hidden;
      position: fixed;
      `;
  tempSelect.appendChild(tempOption);
  event.target.after(tempSelect);
  
  const tempSelectWidth = tempSelect.getBoundingClientRect().width;
  event.target.style.width = `${tempSelectWidth}px`;
  tempSelect.remove();
});

select.dispatchEvent(new Event('change'));
<select>
  <option>Short option</option>
  <option>Some longer option</option>
  <option>A very long option with a lot of text</option>
</select>
like image 4
kilian Avatar answered Oct 13 '22 11:10

kilian