HTML & CSS
Article

A Content-switching Component Built 3 Ways: jQuery, JS, CSS

By Scott O`Hara

Not too long ago, a friend of mine had constructed a UI component to change a page’s content based on the the current selection of a <select> element. Her code worked, but being new to JavaScript, she had built the component in jQuery and had asked me to help optimize it.

While not replicating the code exactly, I’ve created the following example to showcase the type of UI component she was trying to create:

<div class="select-area">
  <select name="choose" id="choose" class="input-select">
    <option value="nul" selected>Make a Selection</option>
    <option value="opt1">Option 1</option>
    <option value="opt2">Option 2</option>
    <option value="opt3">Option 3</option>
  </select>
</div>

<section class="jqueryOptions opt1">
  <div class="content">
    <h2>Option 1 Content</h2>
    <p>
      ...
    </p>
  </div>
</section>

<section class="jqueryOptions opt2">
  <div class="content">
    <h2>Option 2 Content</h2>
    <p>
      ...
    </p>
  </div>
</section>

<section class="jqueryOptions opt3">
  <div class="content">
    <h2>Option 3 Content</h2>
    <p>
      ...
    </p>
  </div>
</section>

After optimizing it a bit, here is the jQuery we ended up using to toggle the display state of the selected content block.

$(function() {
  $('.jqueryOptions').hide();

  $('#choose').change(function() {
    $('.jqueryOptions').slideUp();
    $('.jqueryOptions').removeClass('current-opt');
    $("." + $(this).val()).slideDown();
    $("." + $(this).val()).addClass('current-opt');
  });
});

So what’s going on here?

The above jQuery function looks for all the content blocks that have a class of ‘jqueryOptions’ and hides them by default.

Then when a user changes the selected option of the select input (which has an ID of ‘choose’), the function closes any potentially open content blocks using jQuery’s .slideUp() method and then opens the selected option with slideDown(). It does this by taking the value of the selected option (referenced as this) and referencing the element with the class name that matches the value in “this”.

So:

<option value="opt1">Option 1</option>

Matches:

<section class="options opt1">
  ...
</section>

And here’s a demo:

See the Pen Content switching component with jQuery by SitePoint (@SitePoint) on CodePen.

Or see the working full page demo here.

That wasn’t too hard, huh?

So why not leave it at that?

The problem with the jQuery solution is that we were including jQuery (98kb compressed) for just this small bit of functionality. We could do better.

Without changing the markup, let’s take a look at how we can create the same sort of effect first using vanilla JavaScript and then using only CSS.

First, let’s think about what needs to happen to recreate this effect.

  • The content needs to be hidden by default.
  • Upon making a selection, we need to show the chosen content.
  • We need to also hide any previously open content when a new option is selected.

There are some additional pieces to this that will be explained in the code, but those are the three main points of this script we need to keep in mind. One of which, hiding the content by default, we can set by default with CSS, so that’s one crossed off. Two to go.

Creating a Vanilla JavaScript Version

To start, let’s set up a few variables to make our life easier.

var selectInput = document.getElementById('choose'),
    panels = document.querySelectorAll('.options'),
    currentSelect,
    i;

Now we have variables to easily access our input select (selectInput), the different content panels (panels), a placeholder for a current selection, and an iterator.

Next we need to write some functions to help us take care of the items from the above bulleted list.

Starting with the last bullet first, we need to make sure that any content that was in view gets hidden when a new option is selected. For that we’ll create its own function:

function clearShow() {
  for ( i = 0; i < panels.length; i++ ) {
    panels[i].classList.remove('show');
  }
}

The clearShow() function is doing a couple of things. First, it’s taking our panels variable (which is a list of all nodes on the page with a class of “options”) and it iterates over each (three in this case) and removes the class of “show” from all of them.

The “show” class is what makes the content visible on our page.

Now that we have a way to remove the “show” class from each content block, we need a way to add “show” to the selected panel.

function addShow(showThis) {
  var el = document.getElementsByClassName(showThis);
  for ( i = 0; i < el.length; i++ ) {
     el[i].classList.add('show');
   }
}

The addShow() function receives an argument called showThis and adds the “show” class to the panel node that has a class that matches the current value of the select input. Now we still need one more piece to pass the showThis value to addShow().

function vUpdate() {
  currentSelect = selectInput.value;

  clearShow();
  addShow(currentSelect);
}

selectInput.addEventListener('change', vUpdate);

The vUpdate() function executes whenever the select input is updated (detected via the change event). So when vUpdate() runs, it does the following:

  • Grabs the current value of selectInput and stores it in the currentSelect variable.
  • Executes the clearShow function to remove any traces of .show from the panels.
  • Executes the addShow() function, passing in currentSelect to complete the missing piece of that function
  • Assigns .show to the panel with the class that matches the current value of the select input.

You can check out the demo below to see it all in action:

See the Pen Content switching component with vanilla JavaScript by SitePoint (@SitePoint) on CodePen.

Or you can check out the full page demo here.

But before we move on… If you chose an option from the select input and then refresh the page, the value of the input may stay but the appropriate panel isn’t shown.

We can fix that by adding the following:

if (selectInput.value !== 'nul') {
  currentSelect = selectInput.value;
  addShow(currentSelect);
}

This if construct will check to see if the selectInput value is something other than nul. If that’s the case, it will pass in the value of the current selection to the addShow() function, firing that on page reload. Handy to fix the rare case where the page was refreshed and the select element displayed a value, but the appropriate content wasn’t shown.

And finally, in the instance that you need to support Internet Explorer 9 or lower, we can’t use classList(). To work around this, here are functions to add and remove classes from an element. The above CodePen demos are using these functions:

function addClass(elm, newClass) {
    elm.className += ' ' + newClass;
}

function removeClass(elm, deleteClass) {
  elm.className = elm.className.replace(new RegExp("\\b" + deleteClass + "\\b", 'g'), '').trim();
  /* the RegExp here makes sure that only
     the class we want to delete, and not
     any other potentially matching strings 
     get deleted.
  */
}

Can it be done with only CSS?

With a lot of components I make, I like to try to see how closely I can come to replicating them in a pure CSS solution. However, where the jQuery and vanilla JavaScript solutions could be based on the same markup, the CSS-only solution needs to be modified.

The biggest, and most important, change from the previous versions is that the select needed to be completely replaced. Where the previous examples were relying on the value of the select element to change which content panel would be shown, CSS alone isn’t aware of these value changes and thus can’t utilize the different values to toggle different selector chains.

The select element will just have to be recreated, and to do that, we’ll use what is known as the ‘check box / radio button hack’.

Here’s the markup for our new ‘select element’.

<input type='checkbox' class="invis" id="open_close" />
<input type='radio' name='opts' class="invis" id="opt1" />
<input type='radio' name='opts' class="invis" id="opt2" />
<input type='radio' name='opts' class="invis" id="opt3" />

<header class="header-base">
  <div class="content">
    <p>
      Choose an Option
    </p>

    <div class="select-area">
      <label for="open_close" class="input-select">
        ...
      </label>

      <ul class="select-options">
        <li>
          <label for="opt1">Option 1</label>
        </li>
        <li>
          <label for="opt2">Option 2</label>
        </li>
        <li>
          <label for="opt3">Option 3</label>
        </li>
      </ul>
    </div>

  </div>
</header>

What’s been done here is basically a recreation of a select element as best as HTML and CSS can allow without any help from JavaScript. The inputs above the header act as the controls for the styling anchors for the select and content panels on the site. And the new unordered list and labels becomes our new select element.

The labels for opt1, opt2, and opt3 change the checked state of the radio buttons with the corresponding IDs. In the CSS, depending on which radio button is selected, the corresponding panel is set to display using the general sibling selector.

#opt1:checked ~ main .opt1,
#opt2:checked ~ main .opt2,
#opt3:checked ~ main .opt3 {
  display: block;
  height: 100%;
  overflow: visible;
  visibility: visible;
}

There are a bunch of little details to replicate the select element that are reliant on the appropriate radio button being checked and CSS’s general sibling selector. For more information on the general sibling selector and other neat things you can do with only CSS, you should check out my articles You Can Do That With CSS? and CSS Morphing Menu.

What I don’t cover in those articles are some of the tougher aspects of recreating the select element.

It’s pretty straightforward that we want to have the content of the page immediately updated based on which option is pressed. The problem we run into is that if the select element and the content blocks were all based on the same radio buttons, clicking on the select element would make the content disappear. Not only that, but if the select options were opened, and we clicked on one of those, the options would then disappear and the content block would show up.

To mitigate this UX flaw, I added the <input type="checkbox" class="invis" id="open_close" />. Now the select element and current content could be open at the same time. The downside here is that the select element doesn’t close when an option is clicked, but instead only closes when clicked again. Not ideal. However, I felt it was a better experience than the one I mentioned previously. To help draw attention to the select element needing to be clicked again to close, I changed the down arrow to an X when the element is opened:

#open_close:checked ~ .header-base .select-area .select-options {
  opacity: 1;
  visibility: visible;
}

#open_close:checked ~ .header-base .select-area:after {
  border: none;
  content: 'X';
  top: -24px;
}

An additional feature of true select elements I wanted to recreate was that when a new option is selected, the main select element’s text changes to that option. Using the :before pseudo-element, that text can change based on which radio button is selected:

.input-select:before {
  content: 'Make a Selection';
}

#opt1:checked ~ .header-base .input-select:before {
  content: 'Option 1';
}

#opt2:checked ~ .header-base .input-select:before {
  content: 'Option 2';
}

#opt3:checked ~ .header-base .input-select:before {
  content: 'Option 3';
}

You can see the above, and more, in action by viewing the source code of the CSS-only CodePen demo:

See the Pen Content switching component with pure CSS by SitePoint (@SitePoint) on CodePen.

And here is the full page version.

In conclusion, a short summary of the pros and cons…

We just considered three different ways to create a similar interface using jQuery, then using vanilla JavaScript, and then again using just CSS. Each technique has its own pros and cons.

The jQuery solution is by far the easiest to implement, having very little code.

The downside to the jQuery solution is that if you only need the library for this one interface, you’re adding an entire library for a single purpose.

The Vanilla JavaScript solution, utilizing CSS transforms and transitions is a very close replication to the jQuery solution. Minified it clocks in at 486 bytes – obviously much smaller than the jQuery alternative.

The downside to this solution is that some of the JavaScript used is not supported in all browsers, and to support those browsers, you’ll need to write additional work-around functions (as I’ve done).

The CSS only solution is great for those looking to experiment with CSS or for those that aren’t familiar enough with JavaScript. However, it requires quite a bit of repetitious, markup-reliant CSS to get it to work properly, and can have a negative impact on a site’s accessibility, since you have to use elements in ways they weren’t meant to be used.

Regardless of which solution you gravitate towards, be sure to weigh the pros and cons of each and aim to offer the best user experience.

Free Guide:

7 Habits of Successful CTOs

"What makes a great CTO?" Engineering skills? Business savvy? An innate tendency to channel a mythical creature (ahem, unicorn)? All of the above? Discover the top traits of the most successful CTOs in this free guide.

  • Gary

    Excellent article!! Thanks!

  • Rex Mahel

    In your jQuery example to code is hiding all html element with the class “jqueryOptions”, but the code shown does not have this class, they have the class “options”

  • http://scottohara.me/ Scott O’Hara

    jeez, thanks for pointing that out. sorry about that. I’ve made a request to update the code in the article to match final code that was used in the codepens.

    Totally my fault there. Thanks again for the catch.

  • LouisLazaris

    Thanks, Rex. We’ve corrected that. The code in the demo had the right version, so it was working, but it looks like we missed that in the article due to the changes that we made while the article was being worked on.

  • http://www.shaz3e.com/ Shaz3e

    I am just wondering if I can use this method with href instead of select box?

    • http://scottohara.me/ Scott O’Hara

      this example is for keeping the content all on the same page. If you’re going to use a href, then you’d be linking to a separate page, and this example wouldn’t really be applicable.

      • http://www.shaz3e.com/ Shaz3e

        Thank you for clarifying it for me.

  • Philip Snow

    I am wondering why you add the class “current-opt” on the jquery example? thanks

    • http://scottohara.me/ Scott O’Hara

      Hi Philip,

      Check out what happens when you toggle current-opt on/off with developer tools.

      It’s basically a visual hack to make it appear the right column takes up the entire height of the viewport.

      Depending on if you would need the column to take up the full column height, you could simply remove that CSS and the color background would just apply to the jqueryOptions areas.

      • Philip Snow

        Ah! Thanks a lot for your time answering me. your explanations dissipated the fog I was in.

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in Front-end, once a week, for free.