jQuery-плагин для стилизации select’ов

Внимание! Дальнейшее развитие и поддержка плагина остановлены в связи с тем, что теперь он является частью другого плагина.

* * *

Селекты

Одна из самых неприятных (и я бы даже сказал ужасных) вещей в веб-разработке – это верстка html-форм. К сожалению, не существует единого стандарта отображения элементов форм, независимо от браузера и операционной системы, так же, как и нет возможности произвольно оформить некоторые из этих элементов, используя каскадные таблицы стилей.

Не поддаются полной стилизации следующие элементы html-форм:

  • раскрывающийся список <select>;
  • флажок <input type="checkbox">;
  • переключатель <input type="radio">.
  • поле для отправки файла <input type="file">.

Как уже понятно из заголовка поста, здесь речь пойдет только про селекты.

Существует немало готовых решений в виде jQuery-плагинов для стилизации раскрывающихся списков. Но я (ввиду того, что ни один из плагинов меня не устроил по тем или иным причинам) решил пойти путем изобретения своего колеса и написал собственный плагин, которым и делюсь в данной статье.

Сразу хочу заметить, что данный плагин не подходит для всех возможных случаев применения селектов (читайте недостатки).

Демонстрация работы плагина

На отдельной странице вы можете посмотреть пример стилизации селектов с помощью моего плагина. Их оформление я сделал без использования изображений.

Достоинства

  • При отключенном JavaScript отображаются стандартные селекты.
  • Небольшой размер скрипта, примерно 4 килобайта.
  • Работает в IE6+ и всех современных десктопных браузерах.
  • Выводится внутристрочно.
  • Легко поддается оформлению через CSS.
  • Позволяет задать максимальную высоту для выпадающего списка (CSS-свойством max-height).
  • Автоматически подстраивает ширину, если она не указана.
  • Поддерживает прокрутку колесом мыши.
  • Имеет “умное позиционирование”, т.е. не уходит за видимую часть страницы при открытии списка.
  • Умеет “ловить” нажатие клавиши Tab и переключаться стрелками на клавиатуре.
  • Поддерживает атрибут “disabled”.
  • Работает и с динамически добавляемыми/изменяемыми селектами.

Недостатки

  • Не поддерживает атрибут multiple, т.е. не позволяет выбирать несколько пунктов (мультиселект).
  • Не поддерживает группировку элементов списка (тег <optgroup>).
  • Не поддерживает переключение стрелками на клавиатуре, когда список раскрыт кликом мыши.

Скачать

Плагин недоступен, т.к. он уже не актуален.

jQuery-плагин “SelectBox Styler”

Версия: 1.0.1 | Загрузок: 11104 | Размер: 7 Кб | Последнее обновление: 07.10.2012

Обновления

22.09.2012
Переделал скрипт в плагин (в том числе сделал минимизированный вариант), а также добавил поддержку динамического добавления/изменения селектов.
07.10.2012
Исправлено поведение скрипта при использовании метода onchange у тега <select>.

Подключение плагина

  1. Если на сайте еще не подключен jQuery, то добавьте следующую строку перед тегом </head>:

    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
  2. Сразу после jQuery подключите файл со скриптом:

    <script src="ПУТЬ_К_ФАЙЛУ/jquery.selectbox.min.js"></script>
  3. Далее задействуйте плагин:

    <script>
    (function($) {
     $(function() {
    
       $('select').selectbox();
    
    })
    })(jQuery)
    </script>
    

    Этот код поместите перед тегом </head> после вышеуказанных файлов.

  4. При динамическом изменении селектов необходимо запустить триггер refresh, например:

    (function($) {
    $(function() {
    
    	$('button').click(function() {
    		$('select').find('option:nth-child(5)').attr('selected', true);
    		$('select').trigger('refresh');
    	})
    
    })
    })(jQuery)
    

HTML-код после выполнения плагина

Его структура выглядит следующим образом:

<span class="selectbox" style="display: inline-block; position: relative">
	<div class="select" style="float: left; position: relative; z-index: 10000">
		<div class="text">-- Выберите --</div>
		<b class="trigger"><i class="arrow"></i></b>
	</div>
	<div class="dropdown" style="position: absolute; z-index: 9999; overflow-y: auto; overflow-x: hidden; list-style: none; left: 0; display: none">
		<ul>
			<li>-- Выберите --</li>
			<li>Пункт 1</li>
			<li>Пункт 2</li>
			<li>Пункт 3</li>
		</ul>
	</div>
</span>
<select style="position: absolute; top: -9999px">
	<option>-- Выберите --</option>
	<option>Пункт 1</option>
	<option>Пункт 2</option>
	<option>Пункт 3</option>
</select>

CSS-классы, используемые для оформления селекта

Чтобы оформить селекты с помощью CSS, используйте следующие классы:

.selectbox родительский контейнер для всего селекта
.selectbox .select селект в свернутом состоянии
.selectbox.focused .select фокус на селекте, когда нажата клавиша Tab
.selectbox .select .text вложенный тег для свернутого селекта на случай вставки фонового изображения по технике “раздвижных дверей”
.selectbox .trigger правая часть свернутого селекта (условный переключатель)
.selectbox .trigger .arrow вложенный тег для переключателя (стрелка)
.selectbox .dropdown обертка для выпадающего списка
.selectbox .dropdown ul выпадающий список
.selectbox li пункт (опция) селекта
.selectbox li.selected выбранный пункт селекта
.selectbox li.disabled отключенный (недоступный для выбора) пункт селекта

Заключение

Создание подобного скрипта – довольно кропотливое занятие, поскольку приходится учитывать множество различных моментов. Очень надеюсь, что никаких серьезных багов не вылезет. Но, если что, сообщайте в комментариях.

В ближайших постах планирую поделиться аналогичными скриптами для <input type="checkbox"> и <input type="radio">.

Комментарии (105)

  1. Alex
    19 августа 2012 г. в 03:57

    Никто не говорит про multiple?
    Множественный выбор немного другое свойство.
    Я про про боксовый селект.
    Скорее всего такой скрипт не решит такой задачи.

    1. 19 августа 2012 г. в 09:47 / ответ на коммент Alex

      Не понимаю, что за “боксовый селект”.

  2. Дмитрий
    25 августа 2012 г. в 14:06

    Спасибо, удобная штука. Не громоздкая и названия классов нормальные.

  3. 27 августа 2012 г. в 20:41

    Классная статья. Но только пока не могу определиться, где применять этот скрипт массово..

  4. Sheo
    10 сентября 2012 г. в 17:56

    Спасибо за данное решение, очень помогло, как раз искала что-нибудь легкое без всяких наворотов.

  5. zjMaster
    12 сентября 2012 г. в 09:14

    как быть если имеются связанные поля типа Страна-Регион-город, стиль только применяется к стране, как быть?

    1. 12 сентября 2012 г. в 09:41 / ответ на коммент zjMaster

      Пока скрипт это не умеет, в будущем планирую сделать.

      1. zjMaster
        12 сентября 2012 г. в 09:42 / ответ на коммент Dimox

        Спасибо ждемс

  6. Лавров Сергей
    18 сентября 2012 г. в 12:03

    Здравствуйте! Почитал комментарии и немного не понял как сделать разные селекты? Не могли бы Вы описать попобробнее? А лучше с примерами кода и какие изменения куда вносить?)

    1. Например, так:

      <div class="select1">
      	<select class="styled">...</select>
      </div>
      
      <div class="select2">
      	<select class="styled">...</select>
      </div>
      
      .select1 .selectbox {...}
      .select1 .selectbox .select {...}
      .select2 .selectbox {...}
      .select2 .selectbox .select {...}
      и так далее
      
      1. Лавров Сергей
        18 сентября 2012 г. в 12:20 / ответ на коммент Dimox

        Хммм.. Логично! Спасибо большое. И чё я сам не догадался?))

  7. Alex
    18 сентября 2012 г. в 16:32

    http://msdn.microsoft.com/en-us/library/windows/desktop/aa511484.aspx
    Вот красочный пример того что такое листбокс – список не выпадающий а развернутый.
    А multiple это свойство позволяющее выбирать сразу несколько пунктов в списке.
    В общем я так понимаю что плаг не поддерживает listbox.
    Может кто-то знает решение для listbox?

  8. Владимир
    25 сентября 2012 г. в 09:28

    Все замечательно, одна проблема не отрабатывает событие onchange привязанное к элементу. Как бы это реализовать?

    1. 25 сентября 2012 г. в 09:35 / ответ на коммент Владимир

      Объясните поподробнее, что хотите сделать.

  9. Владимир
    25 сентября 2012 г. в 09:40

    Смотрите есть список

    <div class="town">
                <select id="city">
                    <option value="ekb">Екатеринбург</option>
                    <option value="chel">Челябинск</option>
                </select>
            </div>
    

    и есть скрипт

    $(document).ready(function () {
                $(".town").selectbox();
    
            });
            $("select").change(function () {
    
                id = $('#city option:selected').val();
                if (id == 'ekb') {
                    $('#ekb').show();
                    $('#chel').hide();
                }
                else {
                    $('#chel').show();
                    $('#ekb').hide();
                }
            });
    

    При выборе города должен скрываться/показываться один из divов, но этого не происходит если подключен ваш плагин, если убрать $(“.town”).selectbox(); то все отрабатывает как надо, но выглядит не красиво ))

    1. 25 сентября 2012 г. в 10:16 / ответ на коммент Владимир

      Только что проверил – все работает. В вашем коде сразу 2 ошибки. Правильно вот так:

      $(document).ready(function () {
        $("#city").selectbox();
      
      	$("select").change(function () {
      		id = $('#city option:selected').val();
      		if (id == 'ekb') {
      			$('#ekb').show();
      			$('#chel').hide();
      		}
      		else {
      			$('#chel').show();
      			$('#ekb').hide();
      		}
      	});
      });
      
  10. Владимир
    25 сентября 2012 г. в 10:21

    Огромное спасибо и за оперативность и за подсказку. На самом деле все работает, пробовал разные варианты и получилась “каша” в итоге поэтому и не работало.

    1. 25 сентября 2012 г. в 10:50 / ответ на коммент Владимир

      Пожалуйста ;)

  11. Евгений
    26 сентября 2012 г. в 20:28

    Селект просто прелесть, но есть и проблема. Отправляя ajax запрос, параметры формы плагином jquery.js получить не удается. То есть, значения методом: var login=$(“#login”).val()
    Может подскажете как их совместить?

    1. 26 сентября 2012 г. в 20:33 / ответ на коммент Евгений

      Приведите конкретный пример скрипта. Пока непонятно, что пытаетесь сделать.

      1. Евгений
        26 сентября 2012 г. в 21:00 / ответ на коммент Dimox
        function regs()
        {
                        $.ajax({  
                            type: "POST",  
                            url: "../modules/registr_ajax.php",  
                            data: "seleсteds="+$("#seleсteds").val()+"&amp;login="+$("#login").val()+'.$arr.'"&amp;password="+$("#password").val()+"&amp;email="+$("#email").val(),
        					
                            success: function(html){  
                                $("#content").html(html); 
        				 
                            }  
                        });  
                        return false;  
                    }
        

        Перестает работать, подключая в

        	
        (function($) {
          $(function() {
        
            $('select').selectbox();
        
        })
        })(jQuery)
        

        Удалив, все запросы выполняются и ответ с сервера идет.

        1. 27 сентября 2012 г. в 09:35 / ответ на коммент Евгений

          По идее должно бы работать. На выходных попробую у себя это воспроизвести.

        2. 27 сентября 2012 г. в 20:17 / ответ на коммент Евгений

          Проверил – у меня все работает. Значит вы где-то сделали ошибку.

          Вот мои тестовые файлы – https://dl.dropbox.com/u/7312900/ajax.zip

          1. Евгений
            29 сентября 2012 г. в 05:54 / ответ на коммент Dimox

            Спасибо. Нашел проблему. Подводные камни были не в Вашем замечательном селекте, а в совместимости библиотек jQuery. Теперь все отлично работает.

            Спасибо так же, за оперативность.

            1. 29 сентября 2012 г. в 09:50 / ответ на коммент Евгений

              Ну вот и отлично. Пожалуйста.

  12. Андрей
    28 сентября 2012 г. в 09:38

    Привет! У меня возникла следующая ситуация:

    <li class="tour-selection-input-wrapper">
                                                    <span class="label">Откуда:</span>
                                                    <?php echo SearchController::actionGetDepartCities() ?>
                                                </li>
                                                <li class="tour-selection-input-wrapper">
                                                    <span class="label">Куда:</span>
                                                    <span id="countries"></span>
                                                </li>
    
    $(document).ready(function(){
        LoadCountries($("#departCities").val());
        
        $("#departCities").change(function(){
            var id = $(this).val();
            LoadCountries(id);
        });
    
        function LoadCountries(id){
            $.ajax({
                url: '<?php echo $this->createUrl('search/LoadCountries')?>',
                cache: false,
                data: 'depCity='+id,
                success: function(html){
                    $("#countries").replaceWith(html);
                    //$('select').trigger('refresh');
                    //console.log($("#countries").val());
                    $('select').selectbox();
                }
            });
        };
    })
    

    При таком подходе получается, что старый селект остаётся, а новый добавляется “не проинициализированным” надеюсь правильно выразился)
    В итоге дом после 1-го изменения #departCities выглядит так:

    <li class="tour-selection-input-wrapper">
                                                    <span class="label">Куда:</span>
                                                    <span class="selectbox" style="display: inline-block; position: relative; z-index: 2; "><div class="select" style="float:left;position:relative;z-index:7000"><div class="text">Индия</div><b class="trigger"><i class="arrow"></i></b></div><div class="dropdown" style="position: absolute; z-index: 9999; overflow-x: hidden; overflow-y: auto; list-style: none; height: auto; bottom: auto; top: 28px; display: none; "><ul><li class="" style="">Египет</li><li class="">Израиль</li><li class="selected sel">Индия</li><li>Италия</li><li>Китай</li><li>ОАЭ</li><li>Таиланд</li><li>Турция</li><li>Кипр</li><li>Греция</li></ul></div></span><select id="countries" name="to">
    <option value="40">Египет</option>
    ....
    <option value="35">Греция</option>
    </select>
                                                </li>

    Помогите, как обойти эту ситуацию,а то я не силён в JS?
    PS Извините что громоздко

    1. 28 сентября 2012 г. в 09:50 / ответ на коммент Андрей

      Мне сложно понять, что тут происходит.

  13. Андрей
    28 сентября 2012 г. в 11:11

    Согласен, не совсем понятно расписано. Попробую проще:
    1. Есть 2 селекта один создаеётся ещё в пхп, второго на странице на момент загрузки не существует (на его месте просто пустой span с таким же id).
    После того как вся страница загрузилась, запускается функция: LoadCountries($(“#departCities”).val()); , которая получает аяксом весь select и заменяет пустой спан на этот селект.
    После этого запускаю $(‘select’).selectbox();
    Первый селект оформляется нормально, а второй почему-то нет…

    1. 28 сентября 2012 г. в 11:20 / ответ на коммент Андрей

      По логике все правильно сделано. Не знаю, почему не срабатывает.

      1. Андрей
        28 сентября 2012 г. в 11:37 / ответ на коммент Dimox

        Самое интересное, что до этого работало, но я подгружал не весь select а только его внутренние options После этого делал, например, $(‘#countries’).trigger(‘refresh’); и всё срабатывало.
        Может быть вас это натолкнёт на какую-то мысль?!

  14. Дмитрий
    7 октября 2012 г. в 07:28

    Замечательный скрипт но есть недоделки. Кое что подправил кое что не могу, потому как мало знаком с jquery.
    Надо исправить:
    когда выбираешь пункт option с checked – ом(уже выбранный) происходит событие onChenge. По моему это неправильно. В моем случае запускается реагирующая функция. Вот от этого я хотел бы избавится!

    1. 7 октября 2012 г. в 09:31 / ответ на коммент Дмитрий

      Можете показать живой пример, чтобы я убедился, что это действительно нужно исправить?

      1. Дмитрий
        7 октября 2012 г. в 11:44 / ответ на коммент Dimox
        <select name="razdel" onchange="validateForm(this);">
              <option value="">-- Не выбрано --</option>
              <option value="бытовая техника">бытовая техника</option>
              <option checked value="автомобили">автомобили</option>
              <option value="другое">другое</option>
              <option value="oooooooo">oooooooo</option>
            </select>

        При таком раскладе, выбрав автомобили запускается функция validateForm, а не должна потому что value не изменилось!!!

        1. 7 октября 2012 г. в 15:23 / ответ на коммент Дмитрий

          Понял. Сегодня исправлю этот недочет.

        2. 7 октября 2012 г. в 18:23 / ответ на коммент Дмитрий

          Исправил. Скачайте новую версию плагина. Спасибо, что сообщили об этой проблеме!

  15. Дмитрий
    8 октября 2012 г. в 00:47
    /* при клике на пункт списка */
    					li.filter(':not(.disabled)').click(function() {
    						if(optionSelected.text() != $(this).text()) {
    							optionSelected.text($(this).text());
    							$(this).addClass('selected sel').siblings().removeClass('selected sel');
    							divText.text($(this).text());
    							option.removeAttr('selected').eq($(this).index()).attr('selected', true);
    							select.change();
    						}
    						dropdown.hide();
    					});
    					dropdown.mouseout(function() {
    						dropdown.find('li.sel').addClass('selected');
    					});

    Вот так лучше!

    1. 8 октября 2012 г. в 10:45 / ответ на коммент Дмитрий

      Да, согласен.

  16. alex
    11 октября 2012 г. в 11:18

    Недостатоток огараничение ширины у выпадающего списка, то есть если select 100px то и выпадающий список тоже 100px, и значение option будет записываться больше одной строке!

  17. Сергей
    11 октября 2012 г. в 23:44

    Привет.
    не работает. Он все равно активен.
    Подскажи в чем дело. спасибо.

  18. Андрей
    12 октября 2012 г. в 20:37

    у вас проблема со скриптом. Если значение value у option не совпадает с текстом в нём, то что глюк в скрипте?

    <select><option value="0">пыаывавыа</option>
    <option value="1">иепопвапрпа</option></select>
    

    я сделал так.
    При генерации списка ddlist добавил значение option в id li
    А при клике:

    li.filter(':not(.disabled)').click(function() {
                                    var liText = $(this).text();
                                    var value = $(this).attr('id');
                                    if(value != '') {
                                        el.val(value);
                                    } else {
                                        el.val(liText);
                                    }
                                    el.change();
                                    $(divText).val(liText);
                                    $(this).addClass('selected sel').siblings().removeClass('selected sel');
                                    $(this).toggleClass('selected');
                                    dropdown.hide();
                                });
    
    1. 12 октября 2012 г. в 20:52 / ответ на коммент Андрей

      Не понял, что вы хотели этим сказать.

  19. Сергей
    12 октября 2012 г. в 22:04

    Привет.

    <select disabled></select>

    не работает. Селект все равно активен.
    Подскажи в чем дело. спасибо.

    1. 12 октября 2012 г. в 22:43 / ответ на коммент Сергей

      Сюрприз, однако. disabled я реализовал только для тега option, а про select и думать забыл, что у него тоже может этот параметр использоваться. В скором времени добавлю поддержку.

      1. Сергей
        13 октября 2012 г. в 03:11 / ответ на коммент Dimox

        Спасибо. Ждем.

  20. 13 октября 2012 г. в 10:25

    Дальнейшее развитие и поддержка плагина остановлены в связи с тем, что теперь он является частью другого плагина.

Ваш комментарий

Жирный текст

Ссылка

Цитата

Внутристрочный код

CSS-код

HTML-код

JavaScript-код

PHP-код