Динамические выпадающие списки

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

Можно уменьшить время изменения значений списков, если возложить эту задачу на сторону клиента и задействовать серверный скрипт лишь для обработки уже заполненной формы. Например, можно использовать JavaScript и хранить все значения списков и отношения между ними в массиве. При этом, для избежания проблем с отключенным в браузере JavaScript придется дублировать значения списков в скрипте и html-разметке, при этом начинаются сложности с возможным изменением значений списков и увеличивается объем кода.

Однако, вышеописанных трудностей можно избежать и сделать динамически изменяемые списки, обрабатываемые на стороне клиента (браузером). При этом не требуется обращения к серверу при каждом выборе значений списка, а также значения в списках можно будет выбирать и при отключенном JavaScript. Разумеется, в таком случае нарушатся отношения между значениями, зато в списках остается возможность выбора этих значений. А это означает, что работоспособность формы сохраняется не зависимо от возможности выполнения клиентских скриптов, без затрат на соединение с сервером и выполнение серверных скриптов.

Порядок действий будет таким:

  • Нужно сделать html-разметку страницы. Написать выпадающие списки (select). Такая страница будет почти одинаково выглядеть практически в любом браузере при соблюдении правил разметки. На этом этапе не требуется применения каких-либо действий или стилей к элементам выпадающих списков.
  • Теперь надо добавить к разметке несколько id и class. Это может изменить внешний вид, но еще не дает возможности производить какие-либо действия при изменении значений списков.
  • Следующим этапом оставляем разметку и контент, переходим к обработчику. Берем код JavaScript, размещаем его во внешнем файле скрипта, подключаем его к странице и выполняем сразу после ее загрузки.
  • Перед применением кода скрипта не забываем проверить: может ли браузер его выполнить? Т.е. проверка на поддержку браузером JavaScript/DOM. ? в лучшем случае получаем полностью работоспособные динамически изменяемые списки. В худшем случае получаем обычные не взаимосвязанные списки, которые все-равно остаются работоспособными, хотя уже не такими удобными.

Разметка - основа примера.

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


Следующим шагом будет добавление некоторых атрибутов class и id элементам списков. Сперва добавим уникальные id для select, чтобы можно было легко обращаться к каждому из них. Затем нужно установить отношения между списками. Каждому элементу списка городов нужно добавить атрибут class, значение которого должно быть таким же, как значение атрибута value у элементов списка стран.

Тут следует сделать небольшое отступление на тему использования атрибута class. Дело в том, что он, хотя и должен использоваться для связи с таблицами стилей, является единственным из возможных атрибутов элемента option, со значением которого можно достаточно свободно обращаться. Поэтому для решения поставленной задачи с соблюдением правил разметки используется именно он.

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


JavaScript - двигатель примера.

Самый простой способ динамически изменить список — скрыть ненужные и показать нужные элементы. Все было бы хорошо и просто, если бы все браузеры могли адекватно применить конструкцию style.display для option. Поэтому, этот вариант придется отложить до лучших времен.

Объектная Модель Документа (DOM) позволяет легко и просто обращаться к элементам разметки, производить с ними какие-нибудь действия: удалять, создавать новые, копировать, клонировать и т.п.

Клонирование, как это будет выглядеть и что с этим можно сделать? Как только страница будет загружена браузером, с помощью скрипта клонируется динамически изменяемый второй список (в этом примере - список городов) и этот клон остается в памяти. Затем при загрузке страницы (событие onload) либо при выборе значения первого статичного списка (событие onchange для списка стран), список городов будет удаляться и, затем, будет создаваться новый список, состоящий из городов, принадлежащих выбранной стране из первого списка. Элементы option для динамического списка выбранных городов будут браться из клонированных ранее.

function dynamicSelect(id1, id2) {
// Сперва необходимо проверить поддержку W3C DOM в браузере
 if (document.getElementById && document.getElementsByTagName) {
// Определение переменных, ссылающихся на списки
  var sel1 = document.getElementById(id1);
  var sel2 = document.getElementById(id2);
// Клонирование динамического списка
  var clone = sel2.cloneNode(true);
// Определение переменных для клонированных элементов списка
  var clonedOptions = clone.getElementsByTagName("option");
// Вызов функции собирающей вызываемый список
  refreshDynamicSelectOptions(sel1, sel2, clonedOptions);
// При изменении выбранного элемента в первом списке:
// вызов функции пересобирающей вызываемый список
  sel1.onchange = function() {
  refreshDynamicSelectOptions(sel1, sel2, clonedOptions);
  }
 }
}
// Функция для сборки динамического списка
function refreshDynamicSelectOptions(sel1, sel2, clonedOptions) {
// Удаление всех элементов динамического списка
 while (sel2.options.length) {
  sel2.remove(0);
 }
 var pattern1 = /( |^)(select)( |$)/;
 var pattern2 = new RegExp("( |^)
          (" + sel1.options[sel1.selectedIndex].value + ")( |$)");
// Перебор клонированных элементов списка
 for (var i = 0; i 
// Если название класса клонированного option эквивалентно "select"
// либо эквивалентно значению option первого списка
  if (clonedOptions[i].className.match(pattern1) ||
  clonedOptions[i].className.match(pattern2)) {
// его нужно клонировать в динамически создаваемый список
   sel2.appendChild(clonedOptions[i].cloneNode(true));
  }
 }
}
// Вызов скрипта при загрузке страницы
window.onload = function() {
 dynamicSelect("Country", "City");
}

Вот собственно и все!
Живой работающий пример можно посмотреть здесь. Разумеется, все было бы не так интересно, если рассматривать возможность динамического изменения только одного списка. Логично продолжить, например, добавив третий список, как здесь. ?ли если на странице будет две пары списков, как здесь. При этом все примеры используют один и тот же JavaScript.

Спасибо Bobby van der Sluis!



Много комментариев (32) к “Динамические выпадающие списки”

  1. Teo :

    У вас в алгоритме ошибочка маленькая вот тут: /examples/dynamicselect/examp1.html

    Приведу алгоритм, к ней приводящий:

    Выбираем в первом списке, допустим, A
    Выбираем в втором списке, допустим, A-A
    ? в третьем списке выбираем, допустим, A-A-A
    Теперь меняем значение в первом списке, например, на B
    После этого третий список не «обнуляется»…

    Мелочь, конечно…
    Да и раз я уж тут, то хочу поблагодарить вас за ресурс. Спасибо ;)


  2. AKS :

    Можно, наверно, по умолчанию прятать первый список, чтобы при отключенном JS он не мешался. А JS будет его делать видимым. Это, конечно, применимо только для данного случая (со странами и городами)…


  3. Eddi :

    ?дея интересная, но
    попытка разобраться в коде споткнулась об ошибку в строке :
    (” + sel1.options[sel1.selectedIndex].value + “)( |$)”).

    Если не сложно , можно ли увидеть исхоный рабочий код ?


  4. Fixer :

    Когда списки реально содержат страны и города (а ещё могут содежать регионы), то всё это чушь.

    Название поста не соответствует содержанию, это не динамические списки, что здесь подругается??

    Фишка копирования тупых постов не делает чести вашему ресурсу.


  5. Баранов Андрей :

    Для Eddi: в последнем абзаце есть ссылки на исходники.


  6. Bond :

    Ваш код как раз кстати, то что я искал!
    Большое спасибо Вам и вашему гуманизму, не каждый найдёт время и желание “поделиться с ближним”!
    Реализация кода будет использована на сайте http://flat.net.ua


  7. Bond :

    Для Fixer :
    Наиболее проще обсуждать предоставленный код с отрицательной стороны, что соответствует нашему менталитету, гораздо труднее предложить свой вариант решение данного, обсуждаемого вопроса, что в итоге послужило бы аргументом для оценки Ваших способностей в данной сфере разработки. Слово – Дело!


  8. Wizard :

    Bond: Слушай, я позвонил по телефону 555555, и мне никто не хотел продать 222 кв.метровую квартиру за $50 штук, как же так!? Оптимистичный у Вас сайт =)


  9. ?лья :

    Присоеденяюсь к Бонд -, спасибо за материал! Только вот 1 вопрос есть: как данній скрипт подружить с selected? Т.е. указать, какой город, к примеру, должен выбираться по умолчанию?


  10. Мишук :

    Привет!
    Очень долго искал что-нибудь подобное! Огромное спасибо!

    Правда в процессе работы возникли такие вопросы:

    1. Если второй селект имеет 200 записей (а таких селектов допустим 20) – имеем немеряное (4000) количество опшенов. В связи с этим размер страницы увеличевается многократно. Нельзя ли каким-то образом подгружать списки из файла?

    2. Допустим есть три селекта. Как сделать, чтобы при выборе в первом, в двух других появлялся один и тот же набор опшенов?

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

    ? еще хотелось бы увидеть пару других скриптов для выполнения этой же цели.

    Салют!


  11. Wizard :

    ?так, у нас будет файл jscript и html документ.
    Как и на сайте, будем иметь дело со странами на материках. Но стран будет
    много.
    Начнём со скрипта.
    ?спользуя скрипт, нельзя одновременно задавать event тегу body, такие, как
    onload.
    Файл скрипта лучше всего подключать внутри тега head

    Комментарии на английском, прошу прощения.

    var contin = new Array(); //each cell will hold an array of all the
    //countries in a continent
    var numincont = new Array('0','51','2','47','15','42','27','15'); //number
    //of countries in each continent
    //prepare the continent arrays
    for( var z = 1; z //tell it to set up the select menu when the page loads
    window.onload = myprep;
    function myprep() {
    //Because Netscape 4 will act strangely on reloading . . .
    if( document.mainform.Country.options.length }
    //now that the document has fully loaded, take out all of the countries and
    //put them into
    //an array representing that continent (the continent arrays)
    var y = 1; //y = number of options to bypass at the start - 1
    for( var z = 1; z //each continent in turn. start at 1 because options[0] is 'Please select
    //one'
    for( x = 1; x //insert countries into arrays
    contin[z][x] = new Option(document.mainform.Country.options[x+y].text, document.mainform.Country.options[x+y].value);
    }
    //offset by the number we have already done
    y += numincont[z] + 1; //the 1 allows for the ' ------ Continent
    //name ------' options
    }
    refillme();
    }
    function refillme() {
    //erase the select menu then refill it with all countries from the selected
    //continent
    //the reason I deconstruct then reconstruct is to allow non JavaScript
    //browsers to work
    while( document.mainform.Country.options.length ) { document.mainform.Country.options[0] = 'null'; }
    if( document.mainform.Continent.selectedIndex ) {
    //they have selected a continent. insert a 'Please select one' option
    document.mainform.Country.options[0] = new Option("Please select one","");
    for( var z = 1; z //for the selected continent, put in each country
    document.mainform.Country.options[z] = contin[document.mainform.Continent.selectedIndex][z];
    }
    //give them an 'Other' option and enable the select menu (if it was
    //disabled)
    document.mainform.Country.options[z] = new Option("Other (please use the box below)","Other");
    document.mainform.Country.disabled = 'false';
    } else {
    //wait for them to select a continent
    document.mainform.Country.options[0] = new Option("Please select an area above","");
    document.mainform.Country.disabled = 'true';
    }
    document.mainform.Country.options[0].selected = 'true';
    document.mainform.other.disabled = 'true';
    }
    function ableother() {
    //If they have selected "other", enable the "other" box
    if( document.mainform.Country.options.length > 1 && document.mainform.Country.selectedIndex == document.mainform.Country.options.length - 1 ) {
    document.mainform.other.disabled = 'false';
    } else {
    document.mainform.other.disabled = 'true';
    }
    }
    /'_______________________________________________________________________________________
    The continent menu:

    The countries menu:

    The 'other' box

    '/

    Далее следует форма, куда и войдут представленные выше silect’ы:


    ttp://w3.org/TR/html4/strict.dtd%22>" rel="nofollow">http://w3.org/TR/html4/strict.dtd">


    CountrySelection



    form method="get" action="/tutorials/">







    Site navigation



    Details and download





Providing the correct countries for a continent



If you do not permit (or support) JavaScript, the countries menu will
simply
display all countries as well as continent headings.




Select a continent or area





Select a country (ok, so some of them aren't strictly countries but I
don't care)













  • Wizard :

    Спасибо, что исправили мой “маленький” комментарий!


  • Bond :

    Наиболее профессиональными , на мой взгляд, будут динамические списки на language=”JavaScript”, для 3-х SELECT-ов. Есть перечень преимуществ, и одно из них – «вес страницы», правда есть и недостаток: при отключении пользователем “JavaScript”,
    2-а SELECT-а превращаются в

    CountrySelection in http://flat.net.ua

    var makesPropertyStrPref = ’search’;
    var countryStrPref = ’sr_country’;
    var areaStrPref = ’sr_area’;
    var stateStrPref = ’sr_state’;
    var isSearch = 1;
    var defaultProperty = ”;
    var entryChoose = ‘Выберите…’;
    var entryOthers = ‘другой’;
    var entryAll = ‘все’;

    Страна

    Выберите…
    – Украина
    – Россия
    – Беларусь
    other

    document.write (’Город-Регион’);

    document.write(”");

    Город-Регион

    document.write (’Район’);

    document.write(”");

    Район

    //————————scriptz.js—————–
    function makeSelected(formNameStr, select1NameStr, select2NameStr, isSearch) {
    var iter;
    var select1Select = document.forms[formNameStr].elements[select1NameStr];
    var select2Select = document.forms[formNameStr].elements[select2NameStr];
    if (typeof(property[select1Select.value])==’string’) {
    var select2Data = property[select1Select.value].split(’#');
    var select2List = select2Data[0].split(’;');
    if ((select2Data.length > 1) && (isSearch == 1)) {
    var select2Index = new Array();
    for (iter = 0; iter 0) {
    var kidsPipeList = item.split(’,')[0];
    var groupName = item.split(’,')[1];
    var kids = kidsPipeList.split(’|');
    var toAdd = kidsPipeList + “,” + groupName + ” (__ALL__)”;
    select2List[counter] = toAdd;
    counter++;
    for (jter = 0; jter (sr_country.value=”UA”)
    //массив для (sr_area.value=”40000″)
    //индекс массива, используется также для Почтового ?ндекса в форме
    var property = new Array();
    property["UA"]=’40000,Киев;43000,Луцк;Ровно,Ровно;Тернополь,Тернополь;1,OTHER;’;
    property["40000"]=’2,Дарницкий;3,Голосеевский;1,OTHER;’;
    property["43000"]=’43026,40-й квартал;3,Центр;1,OTHER;’;
    property["RUS"]=’2,Москва 33;3,Астрахань;1,OTHER;’;
    property["BY"]=’4,Минск;1,OTHER;’;
    //————————-all and——————-
    Всё выше опубликованное разработано специально для ресурса http://flat.net.ua.
    Буду рад критике, замечаниям, благодарностям.


  • Bond :

    Наиболее профессиональными , на мой взгляд, будут динамические списки на language="JavaScript", для 3-х SELECT-ов. Есть перечень преимуществ, и одно из них - «вес страницы», правда есть и недостаток: при отключении пользователем "JavaScript",
    2-а SELECT-а превращаются в

    CountrySelection in http://flat.net.ua

    var makesPropertyStrPref = 'search';
    var countryStrPref = 'sr_country';
    var areaStrPref = 'sr_area';
    var stateStrPref = 'sr_state';
    var isSearch = 1;
    var defaultProperty = '';
    var entryChoose = 'Выберите...';
    var entryOthers = 'другой';
    var entryAll = 'все';

    Страна

    Выберите...
    -- Украина
    -- Россия
    -- Беларусь
    other

    document.write ('Город-Регион');

    document.write("");

    Город-Регион

    document.write ('Район');

    document.write("");

    Район

    //------------------------scriptz.js-----------------
    function makeSelected(formNameStr, select1NameStr, select2NameStr, isSearch) {
    var iter;
    var select1Select = document.forms[formNameStr].elements[select1NameStr];
    var select2Select = document.forms[formNameStr].elements[select2NameStr];
    if (typeof(property[select1Select.value])=='string') {
    var select2Data = property[select1Select.value].split('#');
    var select2List = select2Data[0].split(';');
    if ((select2Data.length > 1) && (isSearch == 1)) {
    var select2Index = new Array();
    for (iter = 0; iter 0) {
    var kidsPipeList = item.split(',')[0];
    var groupName = item.split(',')[1];
    var kids = kidsPipeList.split('|');
    var toAdd = kidsPipeList + "," + groupName + " (__ALL__)";
    select2List[counter] = toAdd;
    counter++;
    for (jter = 0; jter (sr_country.value="UA")
    //массив для (sr_area.value="40000")
    //индекс массива, используется также для Почтового ?ндекса в форме
    var property = new Array();
    property["UA"]='40000,Киев;43000,Луцк;Ровно,Ровно;Тернополь,Тернополь;1,OTHER;';
    property["40000"]='2,Дарницкий;3,Голосеевский;1,OTHER;';
    property["43000"]='43026,40-й квартал;3,Центр;1,OTHER;';
    property["RUS"]='2,Москва 33;3,Астрахань;1,OTHER;';
    property["BY"]='4,Минск;1,OTHER;';
    //-------------------------all and-------------------
    Всё выше опубликованное разработано специально для ресурса http://flat.net.ua.
    Буду рад критике, замечаниям, благодарностям.


  • Bond :

    Прямой код не пренимает, а времени переписывать с XHTML нет, т.ч. если комку надо пишите , скину на мыло ([email protected]).


  • dark-demon :

    мда,товарищи,.. вам не жалко пользователей, которые будут грузить килобайты ненужной им информации?

    правильных вариантов всего 2:
    1. убрать селект и поставить инпут. набрать текст руками гораздо проще, чем ковряться в куче списков.
    2. реализовать честную подгрузку.задержки между страницами – ничто по сравнению с задержкой перед загрузкой страницы, содержащей невменяемоего количество опшенов.


  • SlaDER :

    Лучше всего использовать ajax правда документации, не так много с хорошими примерами.


  • Master (ruslan) :

    Как раз искал такой скрипт, огромное спасибо Вам!


  • Master (ruslan) :

    Хотелось бы исправления ошибки, описанной в самом первом комментарии.


  • web-master :

    А зачем придумали Ajax? задержка при подгрузке нового списка уменьшает на порядок время по сравнению с приведенным примером


  • рулевой LoadingWeb.Ru :

    А вообще, Ajax можно втыкать в HTML на подобее java-script или там другие заморочки???


  • Lorinser :

    а не могли бы кто-нить оставить пример состоящих не из 2 а из 5-6 динамических списков, где конечный выбор ведёт на нужную страницу.

    с примером естественно


  • George :

    Предлагаю немного усовершенствовать, юзать class optgroup, в остальном так же. Будет удобно и тем, у кого жскрипт не пашет, и тем у кого пашет ;)


  • Андрей :

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


  • Александр :

    Здесь все понятно. Однако у меня возник такой вопрос:
    Допустим у нс 2 списка
    1) список городов (филиалов компании)
    2) и, например, список названий отделов компании

    как сделать так, чтобы в зависимости от города (не отдела) автоматически выбирался и адрес отправки ([email protected], [email protected])?


  • viktop :

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

    Описаниек библиотеки http://dklab.ru/lib/JsHttpRequest/manual.html

    Топик по динамическомму формированию селектов http://forum.dklab.ru/js/jshttprequest/DinamicheskieSelect.html

    На доске объявлений http://torgall.ru я реализовал выбор места (край/район/город/район города) с помощью этой библиотеки.


  • Кирюха :

    ?нтересно, а как реализовать построение уже “выбранного” списка ?

    Если первый список билдить в PHP, то тут все ясно, но если в том же PHP билдить второй селект с клчем в одном из option как selected, то при старте скрипта он тупо игнорирует правильно, подчеркиваю! , правильно выбранный пункт второго селекта.

    Все это происходит из-за refreshDynamicSelectOptions , но если это не использовать, то второй список до первого изменения будет содержать сразу все элементы. Но выбранный будет именно выбраным !

    Так что косяк =(


  • Кирюха :

    В догонку к прошлому посту – глюк замечен на осле6, опера 9-я варит все нормально. на ФФ не тестилось.


  • Oleg_AT :

    Select – это хорошо, но как быть если все варится в одном?!
    У меня задача следующая:
    1. делаю запрос на сервер MySQL, получаю массив, пусть q_method.
    2. формирую в скрипте динамический Select в цикле через
    addOption(objSel, q_method[k].text, k, true);

    А теперь главное!
    3. нужно в поле Select по первым символам ограничивать выборку в Select,
    чтоб пользованель вабрал из всего только частное.
    Т.е. изменять, как я понял, число Option в Select.
    На выходе получаем развернутый список вариантов. Чем бальще символов, тем
    меньше выборка.
    4.Если нет в q_method, а следовательно и выборки,
    то набрать полностью новую строку и далее ей обработать на сервере.
    P.S. дополнительная кнопка “добавить” не желательна!
    С уважением Олег


  • Юрий :

    Спасибо за решение если возможно подскажите, как подружить второй список с Selected. Первый реагирует, а вот второй не хочет.


  • lamour :

    А как сделать такое: есть главный один список и 10 второстепенных. В первом выбирается например компания, а в 10 второстепеных все одинаковые списки например отделов этой фирмы?


  • Alex :

    Подскажите, пожалуйста, вариант для этого скрипта:
    a href=/examples/dynamicselect/examp1.html с решенной проблемой window.onload() . не удобно ждать загрузки всего контента, а в javascript я пока плохо разбираюсь. сколько ни пытался переделать, ничего не получается. заранее благодарен!