Search code examples
jqueryhtmlcssjquery-uijquery-ui-selectmenu

How to create 3 columns in the drop-down list using jQuery UI selectMenu widget?


I was given a task for creating a drop-down list consisting of 3 columns (in each row) as shown in the below image.

dropdown image

Now, I realized that the Selectmenu widget of the jQuery UI will fulfill my requirement and therefore, I used the following code (as given on their official documentation page).

<!doctype html>

<html lang="en">

<head>

<meta charset="utf-8">

<title>selectmenu demo</title>

<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/smoothness/jquery-ui.css">

<style>

label { display: block; }

select { width: 200px; }

.overflow { height: 200px; }

</style>

<script src="//code.jquery.com/jquery-1.12.4.js"></script>

<script src="//code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

</head>

<body>

<label for="number">Select a number:</label>

<select name="number" id="number">

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

<option value="2" selected>2</option>

<option value="3">3</option>

<option value="4">4</option>

<option value="5">5</option>

<option value="6">6</option>

<option value="7">7</option>

<option value="8">8</option>

<option value="9">9</option>

<option value="10">10</option>

<option value="11">11</option>

<option value="12">12</option>

<option value="13">13</option>

<option value="14">14</option>

<option value="15">15</option>

<option value="16">16</option>

<option value="17">17</option>

<option value="18">18</option>

<option value="19">19</option>

</select>

<script>

$( "#number" )

.selectmenu()

.selectmenu( "menuWidget" )

.addClass( "overflow" );

</script>

</body>

</html>

The problem was that I was looking to create 3 columns instead of the 1 default column. So, I tried two options for aligning the text content inside the option tag.

  1. Putting the text inside the span tag.
  2. Putting the text inside a table made of one row and three columns (cells).

Given below is the code which I tried.

<option value="1">
        <span align="left">Apple</span>
        <span align="center">Mango</span>
        <span align="right">Orange</span>
        </option>
      <option value="2" selected>
      <table>
        <tr>
          <td align="left">1</td>
          <td align="center">2</td>
          <td align="right">3</td>
        </tr>
        </table>
      </option>

But this didn't fix my problem as I was getting the Output as shown in the below image.

wrong output

Since I'm a beginner in HTML, CSS, and jQuery, therefore, I have no idea of what to do next for creating a drop-down list containing 3 columns in each row. Please note that I'll select the whole row consisting of all the 3 columns (as shown in the below image) and not any cell of any particular column.Can anyone kindly suggest what can be done to fix this problem and create the dropdown list as shown in the first image?


Solution

  • Firstly, we will want to create a proper HTML Structure. There is an element of select you will want to learn about: <optgroup>. Here is a basic suggested structure.

    <label for="options">Select an Option:</label>
    <select id="options">
      <optgroup label="PREFERRED OPTIONS">
        <option value="L">Standard Screw Adjustment</option>
      </optgroup>
      <optgroup label="STANDARD OPTIONS">
        <option value="C">Tamper Resistant - Factory Set</option>
        <option value="K">Handknob</option>
      </optgroup>
      <optgroup label="ADDITIONAL OPTIONS">
        <option value="F">Hex Head Screw with Locknut</option>
      </optgroup>
    </select>

    This is just the start. You can use $("#options").selectmenu(); and you get a lot closer to what you want. We still need to add more elements and incorporate the extra data.

    The selectmenu widget is built with the widget factory and can be extended. When extending widgets, you have the ability to override or add to the behavior of existing methods.

    This is exactly what we will do, taking an example from .autocomplete(). See this example here: http://jqueryui.com/autocomplete/#categories

    We will perform the same type of override within .selectmenu(). The widget factory and it's code can be a bit advanced and a bit hard to understand sometimes, so you'll have to dig through this more on your own.

    HTML

    <label for="options">Select an Option:</label>
    <select id="options">
      <optgroup label="PREFERRED OPTIONS">
        <option data-short="L" data-price="$0.00" value="L">Standard Screw Adjustment</option>
      </optgroup>
      <optgroup label="STANDARD OPTIONS">
        <option data-short="C" data-price="$5.00" value="C">Tamper Resistant - Factory Set</option>
        <option data-short="K" data-price="$6.00" value="K">Handknob</option>
      </optgroup>
      <optgroup label="ADDITIONAL OPTIONS">
        <option data-short="F" data-price="-$4.00" value="F">Hex Head Screw with Locknut</option>
      </optgroup>
    </select>
    

    What you see above is sort of extended from what we built first. I have added data attributes. They do not mean much to our HTML yet they can be useful for our jQuery. the benefit here is that if we encounter a browser that does not support JavaScript (and hence cannot load the jQuery library), the code will fall back to basic HTML Markup and still be a similar experience for the user.

    CSS

    .ui-selectmenu-category {
      color: #5F5F5F;
      padding: .5em .25em;
    }
    .ui-menu-item {
    }
    .ui-menu-item .ui-menu-item-wrapper {
      display: inline-block;
      padding: 1em 2px;
    }
    
    .ui-menu-item .ui-menu-item-wrapper.ui-state-active {
      border-width: 1px 0 1px 0;
      border-color: #CCCCCC;
      background-color: #E4EBF1;
      color: #000;
    }
    
    .ui-menu-item .ui-menu-item-wrapper.ui-state-active.short {
      color: #2E6D99;
    }
    
    .ui-menu-item div.ui-menu-item-wrapper {
      width: 295px;
    }
    
    .ui-menu-item .short {
      color: #2E6D99;
      font-weight: strong;
      width: 30px;
      padding-left: .5em;
    }
    
    .ui-menu-item .price {
      font-weight: strong;
      width: 75px;
      margin-right: -6px;
    }
    

    We're going to override and extend a number of theme elements for the .selectmenu(). this is all just to help make it fit the look and feel desired.

    JavaScript

    $(function() {
      $.widget("custom.mySelectMenu", $.ui.selectmenu, {
        _renderMenu: function(ul, items) {
          var that = this,
            currentCategory = "";
          $.each(items, function(index, item) {
            var li, name, short, price;
            if (item.optgroup != currentCategory) {
              ul.append("<li class='ui-selectmenu-category'>" + item.optgroup + "</li>");
              currentCategory = item.optgroup;
            }
            li = that._renderItemData(ul, item);
            console.log(ul);
            name = li.text();
            short = item.element.data("short");
            price = item.element.data("price");
            console.log(li, short, price);
            li.prepend($("<span>", {
              class: "short"
            }).html(short));
            li.append($("<span>", {
              class: "price"
            }).html(price));
            if (item.optgroup) {
              li.attr("aria-label", item.optgroup + " : " + item.label);
            }
          });
        }
      });
      $("#options").mySelectMenu({
        width: 400
      });
    });
    

    Working Example: https://jsfiddle.net/Twisty/sn1z5vzn/7/

    Here is where we're taking the $.ui.selectmenu and extending it to our own use. You can see we're only writing a function for the _renderMenu extension point. Everything else about the selectmenu will be carried through untouched.

    This is important since we'll need to define a width option. We need to know the width so that we can create proper columns, essentially.

    This will go through each of the items and create li elements to be used with the ul that is created by selectmenu. The resulting items will look like:

    <li aria-label="PREFERRED OPTIONS : Standard Screw Adjustment" class="ui-menu-item">
      <span class="short ui-menu-item-wrapper ui-state-active" id="ui-id-1" tabindex="-1" role="option">L</span>
      <div id="ui-id-2" tabindex="-1" role="option" class="ui-menu-item-wrapper ui-state-active">Standard Screw Adjustment</div>
      <span class="price ui-menu-item-wrapper ui-state-active" id="ui-id-3" tabindex="-1" role="option">$0.00</span>
    </li>
    

    jQuery UI does a lot of extra work for us, assigning unique IDs and applying hover classes, etc. Our additions allow us to style and theme different parts of each item.

    The category items don't want to play well. So far, I found if I try to add any HTML element into it's li, it cannot render the rest of the menu properly. I may go back and see if _renderItem will help here.

    Easy, right!? Comment if you have questions.

    References


    Update

    If you change your Option like so:

    <option data-icon-url="https://www.gravatar.com/avatar/b3e04a46e85ad3e165d66f5d927eb609?d=monsterid&r=g&s=16" data-price="$0.00" value="L">Standard Screw Adjustment</option>
    

    You can then update the <span> to use this as it's background.

    CSS Snippet

    .ui-menu-item .icon {
      background-repeat: no-repeat;
      background-size: 30px;
      background-position: center center; 
      color: #2E6D99;
      font-weight: strong;
      width: 30px;
      padding-left: .5em;
    }
    

    JavaScript Snippet

        li.prepend($("<span>", {
          class: "icon",
          style: "background-image: url('" + iconUrl + "')"
        }).html("&nbsp;"));
    

    The &nbsp; renders a Non-breaking Space character.

    A common character entity used in HTML is the non-breaking space: &nbsp;

    A non-breaking space is a space that will not break into a new line.

    Two words separated by a non-breaking space will stick together (not break into a new line). This is handy when breaking the words might be disruptive.

    Working Example: https://jsfiddle.net/Twisty/sn1z5vzn/12/