2

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.

I have found a solution in the question Auto resizing the SELECT element according to selected OPTION's width, but it doesn't work with bootstrap, and I don't know why.

Here's my attempt:

$(document).ready(function() {
  $('select').change(function(){
    $("#width_tmp").html($('select option:selected').text());
    // 30 : the size of the down arrow of the select box 
    $(this).width($("#width_tmp").width()+30); 
  });
});
.btn-select {
  color: #333;
  background-color: #f5f5f5;
  border-color: #ccc;
  padding:7px;
}
select {
  width: 60px;
}

#width_tmp{
  display : none;
}
<link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.2/css/bootstrap.css" rel="stylesheet"/>
<link href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.css" rel="stylesheet"/>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.2/js/bootstrap.js"></script>

<div class="form-group">
  <div class="input-group">

    <span class="input-group-btn">
      <select class="btn btn-select align-left">
        <option>All</option>
        <option>Longer</option>
        <option>A very very long string...</option>
      </select>
      <span id="width_tmp"></span>
    </span>

    <input type="text" class="form-control" placeholder="Search by...">

    <span class="input-group-btn">
      <button class="btn btn-default" type="button">
        <i class="fa fa-search"></i>
      </button>
    </span>

  </div>
</div>
Community
  • 1
  • 1
Nadia
  • 189
  • 1
  • 3
  • 12
  • What happens when you set the width of the select to auto and then set the widths on the options? – Waxi Feb 03 '15 at 22:22
  • Does this answer your question? [Auto resizing the SELECT element according to selected OPTION's width](https://stackoverflow.com/questions/20091481/auto-resizing-the-select-element-according-to-selected-options-width) – João Pimentel Ferreira Dec 15 '21 at 21:36
  • duplicate of: https://stackoverflow.com/questions/20091481/auto-resizing-the-select-element-according-to-selected-options-width – João Pimentel Ferreira Dec 15 '21 at 21:36

1 Answers1

10

This issue occurs because the #width_tmp is nested under .input-group-btn so it inherits the font-size: 0; property from bootstrap. Therefore, the calculated width of the element is always zero:

screenshot

To fix this, just move the <span id="width_tmp"></span> so it's not inside the input group.


Pure JS Solution

// resize on initial load
document.querySelectorAll("select").forEach(resizeSelect)

// delegated listener on change
document.body.addEventListener("change", (e) => {
    if (e.target.matches("select")) {
        resizeSelect(e.target)
    }
})

function resizeSelect(sel) {
  let tempOption = document.createElement('option');
  tempOption.textContent = sel.selectedOptions[0].textContent;

  let tempSelect = document.createElement('select');
  tempSelect.style.visibility = "hidden";
  tempSelect.style.position = "fixed"
  tempSelect.appendChild(tempOption);
  
  sel.after(tempSelect);
  sel.style.width = `${+tempSelect.clientWidth + 4}px`;
  tempSelect.remove();
}

Demo in jsFiddle and Stack Snippets

// resize on initial load
document.querySelectorAll("select").forEach(resizeSelect)

// delegated listener on change
document.body.addEventListener("change", (e) => {
    if (e.target.matches("select")) {
        resizeSelect(e.target)
    }
})

function resizeSelect(sel) {
  let tempOption = document.createElement('option');
  tempOption.textContent = sel.selectedOptions[0].textContent;

  let tempSelect = document.createElement('select');
  tempSelect.style.visibility = "hidden";
  tempSelect.style.position = "fixed"
  tempSelect.appendChild(tempOption);
  
  sel.after(tempSelect);
  sel.style.width = `${+tempSelect.clientWidth + 4}px`;
  tempSelect.remove();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<select>
  <option value="2">Some longer option</option>
  <option value="1">Short option</option>
  <option value="3">An very long option with a lot of text</option>
</select>

<select>
  <option>Short option 2</option>
  <option>Some longer option 2</option>
  <option selected>An very long option with a lot of text 2</option>
</select>

jQuery Plugin Solution

Another alternative would be to use a plugin to handle the whole operation start to finish. Here's one I just whipped up.

The plugin dynamically creates and destroys the span so it doesn't clutter up your html. This helps separate concerns and lets you delegate that functionality to another file and easily re-use with multiple elements across multiple pages.

(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);
.btn-select {
  color: #333;
  background-color: #f5f5f5;
  border-color: #ccc;
  padding:7px;
}
<link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.2/css/bootstrap.css" rel="stylesheet"/>
<link href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.css" rel="stylesheet"/>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.2/js/bootstrap.js"></script>

<div class="form-group">
  <div class="input-group">

    <span class="input-group-btn">
      <select class="btn btn-select resizeselect">
        <option>All</option>
        <option>Longer</option>
        <option>A very very long string...</option>
      </select>
    </span>

    <input type="text" class="form-control" placeholder="Search by...">

    <span class="input-group-btn">
      <button class="btn btn-default" type="button">
        <i class="fa fa-search"></i>
      </button>
    </span>

  </div>
</div>
KyleMit
  • 30,350
  • 66
  • 462
  • 664
  • 2
    The first line needs to be changed from `(function($, window){` to `$(function($, window){` which is the equivalent of saying `$( document ).ready() { (function($, window){` otherwise your class attributes will only change after links on the page are clicked (since the document will load after the script executes) – rmoro Jul 28 '16 at 04:16
  • 2
    I would suggest changing the line `var $test = $("").html(text);` to `var $test = $("").html(text);` to ensure that the font size of the temporary span that is created matches that of the select element. If the font size of the span is smaller or larger, the dynamic resizing will be offset by the difference in size. – Garywoo Oct 30 '16 at 04:43
  • 1
    I would like to suggest to take not only the 'font-size' as @Garywoo suggessted, but to retrieve `window.getComputedStyle(this)` and assign the same `font-weight`, `font-size`, `font-family` and `line-height` to the span. – Mikhail Tulubaev Feb 17 '21 at 16:15
  • 1
    Here's a more modern vanilla JS approach to solve this: https://stackoverflow.com/a/67239947/4168960 – kilian Apr 24 '21 at 07:12
  • @rmoro - not a bad idea to wrap a document ready fn, but can't quite do like that. Currently, it's setup to use an IIFE to pass in the jQuery and window global vars. We can add another wrapper function to wait until load (or lazily, just put the script at the bottom of your page) – KyleMit Apr 25 '21 at 15:30
  • @kilian - great idea. 7 years later and it's definitely time for a vanilla JS approach. I adapted yours into this answer with a little more robust handling. Using a delegated handler for multiple invocations, and not relying on firing the change event to kick off initial render (which can have unwanted side effects). You might want to adapt into your answer in the other post – KyleMit Apr 25 '21 at 16:04
  • @Garywoo and Mikhail Tulubaev - good ideas - included – KyleMit Apr 25 '21 at 16:05
  • if you're using jQuery use this simpler solution: https://stackoverflow.com/a/55343930/1243247 – João Pimentel Ferreira Dec 15 '21 at 21:37