Format dropdown width according to selected value [duplicate] - html

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

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>

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:
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>
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

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>

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.
Get the select element and listen for changes on it.
Create a new select element and option and pass the text of the current selectedIndex to the option.
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.
Append the option to the select.
Append the select to the original select element.
Get the needed dimensions of that new one using getBoundingClientRect().width
Set the width of the original one based on the dimensions of the new one.
Remove the new one.
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>

A Pure Javascript 2022 version based on #Kmas's answer.
Uses querySelectorAll to get all the <select>'s
Uses a visibility: hidden element to copy/paste the value to get the width
Uses getBoundingClientRect to get the width of the hidden element
Uses dispatchEvent to trigger the first resize
function resize(event) {
const fakeEl = document.querySelector('#fakeEl');
const option = event.target.options[event.target.selectedIndex];
fakeEl[0].innerHTML = event.target.value;
event.target.style.width = fakeEl.getBoundingClientRect().width + 'px';
}
for (let e of document.querySelectorAll('select.autoresize')) {
e.onchange = resize;
e.dispatchEvent(new Event('change'));
}
<select class='autoresize'>
<option>Foo</option>
<option>FooBar</option>
<option>FooBarFooBarFooBar</option>
</select>
<select id='fakeEl' style='visibility: hidden'><option></option></select>
You Might Not Need Jquery

Try the following simple JavaScript and convert it in jQuery :)
<html><head><title>Auto size select</title>
<script>
var resized = false;
function resizeSelect(selected) {
if (resized) return;
var sel = document.getElementById('select');
for (var i=0; i<sel.options.length; i++) {
sel.options[i].title=sel.options[i].innerHTML;
if (i!=sel.options.selectedIndex) sel.options[i].innerHTML='';
}
resized = true;
sel.blur();
}
function restoreSelect() {
if (!resized) return;
var sel = document.getElementById('select');
for (var i=0; i<sel.options.length; i++) {
sel.options[i].innerHTML=sel.options[i].title;
}
resized = false;
}
</script>
</head>
<body onload="resizeSelect(this.selectedItem)">
<select id="select"
onchange="resizeSelect(this.selectedItem)"
onblur="resizeSelect(this.selectedItem)"
onfocus="restoreSelect()">
<option>text text</option>
<option>text text text text text</option>
<option>text text text </option>
<option>text text text text </option>
<option>text</option>
<option>text text text text text text text text text</option>
<option>text text</option>
</select>
</body></html>
Here is a jsfiddle for it: https://jsfiddle.net/sm4dnwuf/1/
Basically what it does is temporarily remove unselected elements when it is not in focus (causing it to size to just the size of the selected).

You can use a simple function:
// Function that helps to get the select element width.
$.fn.getSelectWidth = function() {
var width = Math.round($(this).wrap('<span></span>').width());
$(this).unwrap();
return width;
}
Then just use it on your form select element:
$(this).getSelectWidth();

Here's yet another plain jQuery solution:
$('#mySelect').change(function(){
var $selectList = $(this);
var $selectedOption = $selectList.children('[value="' + this.value + '"]')
.attr('selected', true);
var selectedIndex = $selectedOption.index();
var $nonSelectedOptions = $selectList.children().not($selectedOption)
.remove()
.attr('selected', false);
// Reset and calculate new fixed width having only selected option as a child
$selectList.width('auto').width($selectList.width());
// Add options back and put selected option in the right place on the list
$selectedOption.remove();
$selectList.append($nonSelectedOptions);
if (selectedIndex >= $nonSelectedOptions.length) {
$selectList.append($selectedOption);
} else {
$selectList.children().eq(selectedIndex).before($selectedOption);
}
});

Here is how I've achieved the following behavior:
On document load: The select's width is already based on the width of the selected option (repainting does not occur).
On change: The select's width is updated to the width of the newly selected option.
/* WIDTH, ADJUSTMENT, CONTENT BASED */
$( document ).on( 'change', '.width-content-based', function(){
let text_modified_font;
let text_modified_string;
let descendants_emptied_elements;
text_modified_font =
$( this ).css( 'font-weight' ) + ' ' + $( this ).css( 'font-size' ) + ' ' + $( this ).css( 'font-family' );
if
(
$( this ).is( 'select' )
)
{
text_modified_string =
$( this ).find( ':selected' ).text();
descendants_emptied_elements =
$( this ).find( '[data-text]' );
}
else
{
text_modified_string =
$( this ).val();
}
$( this ).css( { 'width': text_width_estimate( text_modified_string, text_modified_font ) + 'px' } );
if
(
descendants_emptied_elements.length
)
{
descendants_emptied_elements.each( function(){
$( this ).text( $( this ).attr( 'data-text' ) ).removeAttr( 'data-text' );
});
}
});
/* DOCUMENT, ON READY */
$( document ).ready( function(){
$( '.width-content-based' ).trigger( 'change' );
});
/* TEXT WIDTH ESTIMATE */ function text_width_estimate( text, font ) { let canvas = text_width_estimate.canvas || ( text_width_estimate.canvas = document.createElement( 'canvas' ) ); let context = canvas.getContext( '2d' ); context.font = font; let metrics = context.measureText( text ); return metrics.width + 3; }
select { -webkit-appearance: none; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<select class="width-content-based">
<option value="AF" data-text="Afghanistan"></option>
<option value="AX" data-text="Åland Islands"></option>
<option value="AL" selected>Albania</option>
</select>

Here's an example of one way to start with a select box auto sized and then how to auto adjust the width when a new box is selected.
Just copy and paste this into a visual studio code document and call it index.html and double click the file (for super noobs).
$(document).ready(function() {
// Auto set the width of the select box for Manufacture on startup
$("#width_tmp_option").html($('#Manufacture')[0][0].label);
$('#Manufacture').width($('#width_tmp_select').width());
// Adust the width of the select box each time Manufacture is changed
$('#Manufacture').change(function(){
$("#width_tmp_option").html($('#Manufacture option:selected').text());
$(this).width($("#width_tmp_select").width());
});
});
#Manufacture {
font-size:17px;
}
#width_tmp_select{
display : none;
font-size:17px;
}
<!-- JS (Jquery) -->
<script src="https://code.jquery.com/jquery-3.6.0.js"
integrity="sha256-H+K7U5CnXl1h5ywQfKtSj8PCmoN9aaq30gDh27Xc0jk="
crossorigin="anonymous"></script>
<!-- HTML -->
<!-- These are hidden dummy selects that I use to store some html in them and then retrieve the width to resize my select boxes. -->
<select id="width_tmp_select">
<option id="width_tmp_option"></option>
</select>
<select id="Manufacture">
<option>Toyota</option>
<option>Ford</option>
<option>Lamborghini</option>
</select>

I transformed João Pimentel Ferreira's jquery answer to vanilla js for anyone who needs a plain js solution
function changeWidth() {
let ghostSelect = document.createElement('select');
const select = document.getElementById('select');
var x = select.options[select.selectedIndex].text;
const ghostOption = document.createElement("option");
ghostOption.setAttribute("value", x);
var t = document.createTextNode(x);
ghostOption.appendChild(t);
ghostSelect.appendChild(ghostOption);
window.document.body.appendChild(ghostSelect)
select.style.width = ghostSelect.offsetWidth + 'px';
window.document.body.removeChild(ghostSelect)
}
<select id="select" onChange="changeWidth()">
<option value="all">Choose one</option>
<option value="1">aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</option>
<option value="2">bbbbbbbbbbbbbbb</option>
<option value="3">cccc</option>
<option value="4">ddd</option>
<option value="5">ee</option>
<option value="6">f</option>
</select>
So what it actually does is that it creates a dummy select with only the selected option, adds it temporarily to the DOM, then calculates the width, sets the original's select width to the new width and then it removes the dummy select from the DOM.

try this:
const select = $('select');
function calcTextWidth(text, font) {
let canvas = calcTextWidth.canvas || (calcTextWidth.canvas = document.createElement("canvas"));
let context = canvas.getContext("2d");
context.font = font;
let metrics = context.measureText(text);
return metrics.width;
}
select.change(function () {
select.width(calcTextWidth(select.find(":selected").text(), 'normal normal 500 15px sans-serif'));
});

Using OffscreenCanvas to measure selected option text with, and then updating select element with
function updateWidth(el) {
const text = el.selectedOptions[0].text;
const canvas = new OffscreenCanvas(0, 0);
const ctx = canvas.getContext('2d')!;
ctx.font = getComputedStyle(el).font;
const width = ctx.measureText(text).width;
const padding = 35;
el.style.width = width + padding + 'px';
}
const select = document.querySelector('select');
updateSize(select);
select.addEventListener('change', evt => updateSize(evt.target));

You can try this also
select option{width:0; }
select option:hover{width:auto;}

Related

Css make tabs vertical if has been overflowed

I have horizontal tabs 1 as,
How can I make tabs vertical on window resize than it has been overflowed?
like this:
I don't want usign #media because tabs are dinamic and may be only 1-2 tabs and it will display good in horizontal view
Use JS/jQuery to calculate total width of tabs and check if it's grater than container width.
var totalWidth = 0;
var availableWidth = $('.tabs-container').width();
$('.tabs').each(function () {
totalWidth += $(this).outerWidth(true);
});
$('.tabs-container')
.toggleClass('v-tabs', totalWidth >= availableWidth)
.toggleClass('h-tabs', totalWidth < availableWidth);
You could use Jquery to add a new class on a window resize
$( window ).resize(function() {
$("tabs").addClass("nice looking class");
    }
other than this i would use a responsive grid.
Basically join Justinas and Troajans answers together:
function checkTabWidths(){
var totalWidth = 0;
var availableWidth = $('.tabs-container').width();
$('.tabs-container').removeClass('v-tabs h-tabs');
$('.tabs').each(function () {
totalWidth += $(this).outerWidth(true);
});
if (totalWidth >= availableWidth) {
$('.tabs-container').addClass('v-tabs');
} else {
$('.tabs-container').addClass('h-tabs');
}
}
$( window ).resize(function() {
checkTabWidths(); // Check widths on resize
}
checkTabWidths(); // To check on load
You can use jquery to do that:
//first get the container width
var container=$("#containID").width();
//then get the total width of each tabs
var totalWidthOfTabs=0;
$("#containID .tabs").each(function(){
totalWidthOfTabs+=$(this).width();
});
//when window resize, do something
$( window ).resize( function(){
if(totalWidthOfTabs>container){
$("#containID .tabs").css("width","100%");
}
});
just an idea to do what you want. I do not check it whether it is correct or not, you can change it if the example is wrong~

applying global style for tds except tds with custom made select element

I applied a global style overflow: hidden for all tds of a table except the ones with a custom made select element as its child.
snippets:
css:
th, td {
text-overflow: ellipsis;
overflow: hidden;
}
html:
<td>
<select class="ks-selectList">
<option>data 1.2</option>
<option>data 2.2</option>
<option>data 3.2</option>
</select>
</td>
javascript:
// Iterate over each select element
$('.ks-selectList').each(function () {
// Cache the number of options
var $this = $(this),
numberOfOptions = $(this).children('option').length;
$this.css("visibility", "hidden");
// Wrap the select element in a div
$this.wrap('<div class="select"></div>');
// Insert a styled div to sit over the top of the hidden select element
$this.after('<div class="select-content"></div>');
// Cache the styled div
var $styledSelect = $this.next('div.select-content');
// Show the first select option in the styled div
$styledSelect.wrapInner('<span>'+$this.children('option').eq(0).text()+'</span>');
// Insert an unordered list after the styled div and also cache the list
var $list = $('<ul />', {
'class': 'options'
}).insertAfter($styledSelect);
// Insert a list item into the unordered list for each select option
for (var i = 0; i < numberOfOptions; i++) {
$('<li />', {
text: $this.children('option').eq(i).text(),
rel: $this.children('option').eq(i).val()
}).appendTo($list);
}
// Cache the list items
var $listItems = $list.children('li');
// Show the unordered list when the styled div is clicked (also hides it if the div is clicked again)
$styledSelect.click(function (e) {
e.stopPropagation();
$('div.select-content.active').each(function () {
$(this).removeClass('active').next('ul.options').hide();
});
$(this).toggleClass('active').next('ul.options').toggle();
});
// Hides the unordered list when a list item is clicked and updates the styled div to show the selected list item
// Updates the select element to have the value of the equivalent option
$listItems.click(function (e) {
e.stopPropagation();
$styledSelect.text($(this).text()).removeClass('active');
$this.val($(this).attr('rel'));
$list.hide();
});
// Hides the unordered list when clicking outside of it
$(document).click(function () {
$styledSelect.removeClass('active');
$list.hide();
});
});
result:
screenshot #1
expected result:
screenshot #2
I know I can override this using class, however I want to do this without using class.

How do you make text or other elements copy something other than that?

I've seen several sites that when you copy and paste text from their website, it turns out something like this:
<text here> - From <website name here>
And I am wondering how to do that. I have tried using the alt parameter and others. Thanks in advance.
This is not so simple as it would seem to be. I've done this before so I will show you how to do it using jquery and the rangy selection library (https://code.google.com/p/rangy/)
Basically you do this:
take the current selection
copy it in some invisible element
add your text on that element
select the invisible element
restore the selection state with a setTimeout
Here's a fiddle and the code:
http://jsfiddle.net/P4yfw/
$("body").on("copy", function() {
var selection = rangy.getSelection();
var ranges = selection.getAllRanges();
var container = $("<div>");
for (var i = 0; i < ranges.length; i++)
container.append(ranges[i].cloneContents());
container.append(" - whatever text you want to append");
var wnd = $(window);
var scrollTop = wnd.scrollTop();
var scrollLeft = wnd.scrollLeft();
var offscreen = $("<div>").css({
position: "absolute",
left: "-1000px",
width: "1px",
height: "1px",
overflow: "hidden"
}).appendTo("body");
offscreen.css({ top: scrollTop });
offscreen.append(container);
selection.selectAllChildren(container[0]);
event.stopPropagation();
setTimeout(function() {
selection.setRanges(ranges);
offscreen.detach(); // offscreen.remove() will remove events attached to the original elements (not cloned elements) on IE8
wnd.scrollTop(scrollTop);
wnd.scrollLeft(scrollLeft);
}, 0);
});

Selection element - prevent selection changed

How to cancel change event for combo box (SELECT) element. There is no onbefore or onchanging event for HTML SELECT element. If I put:
<SELECT onchange="return false;">
<OPTION value=a>A</OPTION>
<OPTION value=b>B</OPTION>
</SELECT>
event is not canceled.
You need to save the original value of the <select> somewhere. So that you can change the value back.
Cancelling the event, only does that. It just cancels the event, it doesn't change the value.
Example (using jQuery):
$(function(){
$('select').each(function(){
$(this).data('selected', $(this).val()); // back up value
}).change(function(){
if(confirm('Are you sure you want to change the element?')){
$(this).data('selected', $(this).val()); // back up new value
}
else{
$(this).val($(this).data('selected')); // restore old value
}
});
});
DEMO: http://jsfiddle.net/pL2B4/
Pure JavaScript solution (no jQuery):
var select = document.getElementsByTagName('select');
for (var i = 0, len = select.length; i < len; i++) {
var el = select[i];
el.setAttribute('data-selected', el.value);
el.addEventListener('change', function() {
if (confirm('Are you sure you want to change the element?')) {
el.setAttribute('data-selected', el.value);
}
else {
el.value = el.getAttribute('data-selected');
}
});
}​
DEMO: http://jsfiddle.net/pL2B4/1/

How to use divs as radio button selectors for hidden field with jquery?

I'm trying to create a good looking product order form with jquery skills.
I have some shoe size values as like divs:
<div id="select-size">
<div id="size-value-19">39</div>
<div id="size-value-20">40</div>
<div id="size-value-21">41</div>
<input type="hidden" name="option[229]" id="option-size" value="">
</div>
When a customer clicks on a shoe size numbers it should take the size-value-?? part from div id and put it into #option-size hidden field.
How can I do that?
BTW: I had found a prototypejs example for this work but prototype and jquery can't work together properly.
Let me give the prototype example code for you:
<script type="text/javascript">
//<![CDATA[
document.observe("dom:loaded", function() {
function deActivate(elt) {
elt.removeClassName('active');
}
function watchClick(evt) {
var element = Event.element(evt);
if (element.hasClassName('outstock')) return;
$$('#select-size div').each(function(elt) {deActivate(elt)});
element.addClassName('active');
var eid = element.id.split('-')[2];
$('option-size').setValue(eid);
}
$$('#select-size div').invoke('observe', 'click', watchClick);
});
//]]>
</script>
I still can't believe that how js framework developers use same $ sign...
$('div').click(function(){
var id = $(this).attr('id');
$('#option-size').val(id);
});
Code presented by OP, translated to jQuery:
$(function() { // On DOM ready ...
var $sizes = $('#select-size div'), // Size elements
$input = $('#option-size'); // Hidden input
$sizes.click(function() { // On click on size element ...
var $this = $(this); // Reference to this element
if (!$this.hasClass('outstock')) { // If not out of stock ...
$sizes.removeClass('active'); // Remove .active on all
$this.addClass('active'); // Add .active on this
$input.val(this.id.split('-')[2]); // Set value in hidden field
}
});
});
(Updated demo)
On a general note:
You should be able to suppress jQuery's use of the $ symbol via the .noConflict() setting. However, it is advisable to translate the functionality into jQuery if this library is already present and you have no other use for prototypejs.
add jquery
<script type="text/javascript" src="script/jquery-1.5.1.js"></script>
and add this script to your page
<script type="text/javascript">
$(document).ready(function () {
$('#size-value-19').click(function () {
$('#option-size').val($(this).text());
});
$('#size-value-20').click(function () {
$('#option-size').val($(this).text());
});
$('#size-value-21').click(function () {
$('#option-size').val($(this).text());
});
});
</script>