Random Crap

Where my stupid ideas go to die

Download as .zip Download as .tar.gz View on GitHub

Too many goddamn bootstrap tabs make people whine about things

The project I work on uses bootstrap. This was actually a decision I made because we were drowning under custom css for so many forms. It’s a pretty good framework, and a lot of the built-in interface elements are super helpful.

Unfortunately, it doesn’t always fit with what you need. See, I had a tabbed interface. It was built with a server-side command that read through all the possible categories of settings an object could have. Recently, I got to the point that some objects had over 12 or even 14 categories, which caused the tabs to wrap around.

I looked around the internet for a while, trying to find some solution. At minimum, I would have been happy with a scrollbar underneath the tabs (although that’s pretty ugly). I did find one person who built a jquery plugin that would automatically check the interface width, see how many tabs fit, and put the other tabs in a bootstrap dropdown. However, it was pretty old (it didn’t appear to have been updated since bootstrap 2, and I work with bootstrap 3), so I was a little worried about compatability.

You know what? I am a dang programmer. I’ll do this myself just to see if I can


// Get the container.  in this case, i've got it in a <ul> with id="setting-tabs-container"
var $tabContainer = $("#setting-tabs-container");

// Get the tabs in the page under the container.  While I'm at it, I'm going to initialize the sizes so I don't have to 
//	recalculate them every time the function runs by placing the value in the jQuery.data() of the tab.
var $tabs = $("> li", $tabContainer).each(function(){
	var w = $(this).outerWidth();
	$(this).data("tab-width", w);
});

// timer that will cause the function to run.  This prevents it from running too often while the resize event executes
var tabResizeTimer = null;

//flag because I am lazy.
var tabdropdown = false;

// html code for dropdown.
var dropHtml = '<li class="dropdown">\
				<a href="#" id="tab-more" class="dropdown-toggle" data-toggle="dropdown">More <b class="caret"></b></a>\
				<ul class="dropdown-menu" role="menu" aria-labelledby="tab-more"></ul></li>';

// bind event
$(window).resize(function(){
	// ok.  if the timer is waiting, kill the timer
	if(tabResizeTimer != null) {
		clearTimeout(tabResizeTimer);
	}

	//set up timer.  the function here is where the work actually happens.
	tabResizeTimer = setTimeout(function(){
		var tc_width = $tabContainer.width();	//current width of container.
		var t_width = 0;	//use this for checking the width of the sum of the tab widths.
		var $ok = $([]);	//i dont even know why i need this.
		var $notok = $([]);	//i definitely need this.  it's used to keep references to the tabs we want to move.

		//figure out which tabs need to be moved into a dropdown.
		$tabs.each(function(){
			t_width += $(this).data("tab-width");
			if((t_width + 77) < tc_width) { // 77 = width of "more" dropdown tab.  I am lazy.  I said this already.
				$ok = $ok.add(this);
			} else {
				$notok = $notok.add(this);	//add tab to the list of tabs we need to move.
			}
		});
		if($notok.size() > 0) {	//in this case, we have tabs that should be moved.
			// re-render tabs with dropdown.
			var $drop = $("li.dropdown", $tabContainer);
			if($drop.size() > 0) {	//dropdown exists.  move stuff out of it first.
				$("li", $drop).each(function(){
					var $t = $(this);
					//.detach() is cool because it doesn't destroy the .data() already applied to the tabs.
					//	also, note the selector here.  what I am doing is inserting the tabs after the last non-dropdown
					//	tab.  This could cause a problem if there are no tabs.
					//	Crap.  I hadn't thought of that until now.
					$t.detach().insertAfter($("> li:not(.dropdown):last", $tabContainer));
				});
			} else {
				//dropdown doesn't exist - add it to the tab container
				$drop = $(dropHtml).appendTo($tabContainer);
			}
			// now loop through each tab, detach it from the tab container and re-insert it into the dropdown.
			$notok.each(function(){
				$t = $(this);
				$t.detach().appendTo($("ul.dropdown-menu", $drop));
			});
			tabdropdown = true;	//flag because I am lazy.
		} else if(tabdropdown) {
			//no tabs to move, but we have a dropdown, so we need to move things out of the dropdown
			//	and put them into the tab container, then remove the dropdown.

			var $drop = $("li.dropdown", $tabContainer);	//get the dropdown <li>
			//get the dropdown's members and put them back into the tab container.
			$("li", $drop).each(function(){
				var $t = $(this);
				$t.detach().appendTo($tabContainer);
			});
			$drop.remove();
			tabdropdown = false;
		}
		tabResizeTimer = null;
	}, 200);	// 1/5 second delay.
}).trigger("resize");

When I implemented this, it worked like a charm. I haven’t put it through the QA department yet. They will probably find something wrong with it. They generally do.