Developer’s Corner: Restyling the Default Drop Down Menu

Posted By Sean Robertson In Technology 04/10/2014

Select lists are common in web forms, and clients often will want them to better match the look and feel of their site, but CSS alone doesn’t have enough control over them, so we need a better solution.

Unfortunately, select list elements are one of the most notoriously difficult form elements to style, especially in Internet Explorer on Windows.

Current Attempt:

This is what the default looks like in IE 10 even after a serious attempt at styling it:

Collapsed select list before

Expanded:

Select list expanded before

Obviously, that doesn't pass muster relative to the design comps.  Even in Chrome on OSX, while the collapsed list looks exactly as we want it, the expanded list is the system default list and the only thing you really have control over is text size and colors.  After a significant amount of searching, I found an interesting solution by Janko at Warp Speed: Reinventing a Drop Down with CSS and jQuery.  While a very good start, it isn't an entirely complete example and needs to be customized to work with a Views exposed filter (using AJAX) in Drupal.  The first thing we did was set up a standard views exposed filter as a select list, set the filter to use AJAX and output the view as a block.  None of the HTML output from that has been customized in any way.

Code Snippets:

Here is our javascript, modified from the version posted by the original author. I'll post it in it's entirety and then explain the individual pieces:

(function ($) {
  Drupal.behaviors.replaceSelect = {
    attach: function (context) {
      var isGP = $("body.page-mypage").length ? true : false;
      if (isGP) {
        var source = $("#edit-field-myfield-tid");
        var selected = source.find("option[selected]");
        var options = $("option", source);

        $(".form-item-field-myfield-tid").append('<dl id="target" class="dropdown"></dl>');
        $("#target").replaceWith('<dl id="target" class="dropdown"><dt><a href="#">' + selected.text() +
          '<span class="value">' + selected.val() +
          '</span></a></dt>')
        $("#target").append('<dd><ul></ul></dd></dl>')

        options.each(function(){
          $("#target dd ul").append('<li><a href="#">' +
            $(this).text() + '<span class="value">' +
            $(this).val() + '</span></a></li>');
        });
        source.hide();

        $(".dropdown dt a").click(function() {
          if (event.preventDefault) {
            event.preventDefault();
          } else {
            event.returnValue = false;
          }
          $(".dropdown dd ul").toggle();
        });

        $(document).bind('click', function(e) {
          var $clicked = $(e.target);
          if (! $clicked.parents().hasClass("dropdown"))
            $(".dropdown dd ul").hide();
        });

        $(".dropdown dd ul li a").click(function() {
          if (event.preventDefault) {
            event.preventDefault();
          } else {
            event.returnValue = false;
          }
          var text = $(this).html();
          $(".dropdown dt a").html(text);
          $(".dropdown dd ul").hide();

          var source = $("#edit-field-myfield-tid");
          source.val($(this).find("span.value").html());
          $('#edit-submit-myviewname').click();
        });
      }
    }
  }

})(jQuery);

The most important part of this from a Drupal standpoint is that we're using Drupal's behaviors rather than a standard $(document).ready(function(){. The reason is that because we're dealing with AJAX, we require the menu to be re-rendered each time that block reloads. The first two lines after the start of the jQuery closure set up the behavior. We then test to make sure we're on the right page by looking for the body class, since we only need this to run on one specific section of the site.

The next 15 lines of code are what actually replaces the select with our custom menu. First, it finds the original select list and assigns it to the variable source. The we assign the selected item to a variable for later reference. We also assign the entire list of options to another variable. The next five lines does the heavy lifting. This created a definition list element which will contain our menu. You'll note that on the next list, we replace that <dl> tag with itself and it's contents. The reason for that is the AJAX reload behaves in such a way that we can't simply append it's contents - the first line always executes even if we first test for the existence of that tag, so we end up with a duplicate menu container. The last five lines before our click functions loops over the options and creates the individual menu items in the list, and then hides the original select list.

The first click function sets the link we added to our list container to expand the list and prevent the default action in the browser (which would otherwise cause the page to reload and prevent the list from opening).

The second click function just binds it to the document so that if the menu is open and you click anywhere else on the page, the menu hides as expected.

Finally, we set up the click events for the individual items in the list. This is the meat and potatoes of this script. The last three lines are crucial. We find our hidden select list, set it to the value in the span tag within the clicked link (which we have hidden via CSS as per the original article), and then click the Apply button. Note that if you're using Better Exposed Filters with Views, you cannot use it to hide the Apply button or the form will never submit as apparently a CSS-hidden element can have its value changed but cannot be clicked. I found the only way to get it to work was to apply text-indent:-9999px; to the Apply button's parent div.

The End Result:

Here is the much nicer end result after styling everything to our liking:

Collapsed select list after

Expanded:

Select list expanded after

Are you facing the same problem? Have you come up with a similar solution? Drop us a line and let us know your thoughts.

Comments

Thanks, very useful.

if you get an error in FF this is likely because you are missin the event declaration from your function

$(".dropdown dt a").click(function(event) {

Cheers,
Ben
----
http://www.webstanz.be

Leave a Comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
To prevent automated spam submissions leave this field empty.
By submitting this form, you accept the Mollom privacy policy.