DOM + javascript порою позволяют создавать удивительные вещи. Одна из этих вещей, которой я хочу с вами поделиться — это динамически формируемые выпадающие списки.
Постановка задачи
Так как я по сути практик, то лучше сразу рассмотреть практическую задачу. Допустим, требуется создать форму выбора модели картриджа принтера. При этом, сначала пользователь может выбрать брендовое название или производителя, а потом уже ограниченный первым выбором — конкретную модель картриджа. В итоге это определяет, например, стоимость услуги связанной с данным картриджем.
Итак, наша задача отталкивается от работы с некоторой таблицей. При формировании массива данных из php удобно пользоваться библиотекой json. А в конечных движках для этих целей бывают реализованы собственные встроенные средства. Например, Drupal имеет функцию drupal_to_js($variable). Не стану вдаваться в подробности json и конкретных движков, будем считать, что нам удалось объявить прайс как двумерный ассоциативный массив в javasctipt. Выглядит это примерно так:
1 2 3 4 5 |
var priceData = [ { "firm": "Brother", "device": "Brother HL-1030/1230/1240/1250/1270", "cart": "TN6300/6600", "p": "380" }, { "firm": "Brother", "device": "Brother HL-1430/1440/1450/1470", "cart": "TN6300/6600", "p": "360"}, { "firm": "Brother", "device": "Brother MFC-8350/8750/9650/9660/9750/9760", "cart": "TN6300/6600", "p": "400"}, { "firm": "Brother", "device": "Brother MFC-9850/9860/9870/9880", "cart": "TN6300/6600", "p": "420"}, ... ]; |
После этого нам понадобятся два списка select, в которых мы и будем отображать наши списки динамически. Вот HTML код этих списков.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<form> Выберите бренд: <select id="firm"> <option value="">-- бренд не выбран --</option> </select> <div id="modelCont" style="display: none;"> Выберите модель картриджа: <select id="model"> <option value="">-- модель не выбрана --</option> </select> </div> </form> |
Обратите внимание, что списки уже содержат по одному элементу option. Второй список изначально скрыт от пользователя установкой стиля контейнера display = none. Мы будем также прятать список моделей, если бренд не выбран.
Вернемся к данным прайса. В каждой строке у нас есть — наименование бренда (firm), модель устройства (device), совместимый с устройством картридж (cart) и цена заправки в рублях (p). Для первого списка нам нужно выбрать не повторяющиеся названия брендов, а для второго использовать название устройства или название картриджа. Чтобы за один проход выбрать из прайса не повторяющиеся названия брендов, лучше всего позаботиться при формировании массива, чтобы строки были отсортированы по firm. Тогда можно написать такую функцию инициализации первого списка:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function firm_init() { var firmSelObj = document.getElementById('firm'); var priceLen = priceData.length; var selLen = firmSelObj.options.length; var lastFirm = ""; for (var i=0; i < priceLen; i++) { var t = priceData[i]; if (lastFirm != t.firm) { firmSelObj.options[selLen ++] = new Option(t.firm, t.firm); lastFirm = t.firm; } } } |
Для создания новых элементов списка мы используем конструктор Option(text, value), где text — это отображаемая метка элемента списка, а value — её значение. Сортированный по firm массив позволяет нам последовательно выбрать значения бредов и добавить их в список.
Далее нам нужно будет создавать и показывать список картриджей. Этот функционал привяжем к событию onchange первого списка. Но перед тем как добавить нужные элементы в список, его нужно очистить. Вот что получается:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function onchangeFirmSelect() { var firmSelObj = document.getElementById('firm'); var modelContainer = document.getElementById('modelCont'); var currentFirm = firmSelObj.value; if (currentFirm == "") { modelContainer.style.display = "none"; //бренд не выбран } else { var modelSelObj = document.getElementById('model'); //очистим список моделей - все, кроме первого элемента for (var i=modelSelObj.options.length-1; i > 0; i--) { modelSelObj.remove(i); } //загрузим список моделей соответствующего бренда var priceLen = priceData.length; //число строк прайса var selLen = modelSelObj.options.length; //текущее число строк будет равно единице for (var i=0; i < priceLen; i++) { var t = priceData[i]; if (currentFirm == t.firm) modelSelObj.options[selLen ++] = new Option(t.device + " (" + t.cart + ")", t.p); //модель устройства (картридж), цена заправки в рублях } modelContainer.style.display = "block"; //бренд выбран, покажем список } } |
Осталось лишь что то делать при выборе конкретной модели. Привяжем к событию onchange второго списка функцию для вывода цены заправки выбранной модели картриджа.
1 2 3 4 |
function onchangeModelSelect(th) { if (th.value != "") alert("Цена заправки выбранного картриджа : " + th.value + " рублей."); } |
Я позволил себе небольшую оптимизацию для данной функции, передавая в неё как входной параметр ссылку на второй список. Давайте посмотрим как все выглядит вместе (без объявления прайса).
HTML часть:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<form> Выберите бренд: <select id="firm" onchange="onchangeFirmSelect();"> <option value="">-- бренд не выбран --</option> </select> <div id="modelCont" style="display: none;"> Выберите модель картриджа: <select id="model" onchange="onchangeModelSelect(this);"> <option value="">-- модель не выбрана --</option> </select> </div> </form> |
javascript часть:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function firm_init() { var firmSelObj = document.getElementById('firm'); var priceLen = priceData.length; var selLen = firmSelObj.options.length; var lastFirm = ""; for (var i=0; i < priceLen; i++) { var t = priceData[i]; if (lastFirm != t.firm) { firmSelObj.options[selLen ++] = new Option(t.firm, t.firm); lastFirm = t.firm; } } } function onchangeFirmSelect() { var firmSelObj = document.getElementById('firm'); var modelContainer = document.getElementById('modelCont'); var currentFirm = firmSelObj.value; if (currentFirm == "") { modelContainer.style.display = "none"; //бренд не выбран } else { var modelSelObj = document.getElementById('model'); //очистим список моделей - все, кроме первого элемента for (var i=modelSelObj.options.length-1; i > 0; i--) devElm.remove(i); //загрузим список моделей соответствующего бренда var priceLen = priceData.length; //число строк прайса var selLen = modelSelObj.options.length; //текущее число строк будет равно единице for (var i=0; i < priceLen; i++) { var t = priceData[i]; if (currentFirm == t.firm) modelSelObj.options[selLen ++] = new Option(t.device + " (" + t.cart + ")", t.p); //модель устройства (картридж), цена заправки в рублях } modelContainer.style.display = "block"; //бренд выбран, покажем список } } function onchangeModelSelect(th) { if (th.value != "") alert("Цена заправки выбранного картриджа : " + th.value + " рублей."); } firm_init(); |
Ну и для демонстрации — примерчик вживую:
А как сделать чтобы согласно выбраному элементу выпадающего списка открывалась форма с несколькими значениями соответствующими этому элементу
А чем данный пример отличается от того, что вам нужно? Выбираем в списке №1 — Brother, далее формируем 2й список, где только модели для Brother. Можно всячески усложнять пример.
Следующий шаг, мне кажется, это добавление использования ajax. Формирование следующих шагов формы в зависимости от выбранных опций на первых этапах. На стороне сервера гораздо проще выполнить нужные выборки из базы данных, упорядочить их, агрегировать, чем делать это средствами javascript из массивов данных.
При этом понятно, что если форма сложная, объем данных большой, то все значительно усложняется, хоть и остаётся реализуемым в рамках javascript. Для каждого конкретного случая, стоит выбирать адекватный подход.
Здравствуйте! Возможно ли использование названий на кириллице в массиве
Да, можно, в любой кодировке, в том числе UTF-8.
Добрый день. А можно ли опубликовать исходник или хотя бы javascript, где прайс объявлен? Спасибо.
А так вот он — shra.ru/widgets/select/sample.js
Добрый день, Юрий. Как можно добавить вывод картинки после выбора селекта и добавить кнопку вывода результата подбора.
Спасибо.
Нужны следующие правки:
1) добавляем в массив priceData поле с url картинки, например, так:
2) Картинку нужно где то показывать, для этого в html, к примеру, можно добавить какой то контейнер. Туда же будем выводить цену, пусть это будет элемент DIV#output
3) Опции (option) селекта должны теперь указывать на индекс массива, т.к. нам нужна не только информация о цене, но и адрес картинки. Меняем кусочек функции onchangeFirmSelect
вот этот:
на вот такой:
4) Теперь при выборе модели нужно показать картинку и цену в контейнере DIV#output. Меняем код функции onchangeModelSelect.
Юрий спасибо большое за помощь. У Вас все круто получилось, но я тупанул! Как добавить к этим селектам еще один для выбора третьего параметра? То есть мне необходимо: марка, модель и тип, а далее вывод соответствующей информации.
Спасибо!
В данном скрипте — никак, слишком много нужно переписывать. Попробуйте посмотреть скрипт для произвольной глубины выбора параметров — http://shra.ru/2016/12/formirovanie-svyazannykh-spiskov-select-proizvolnojj-vlozhennosti/
devElm.remove(i); devElm не объявили? что это?
И Андрей получает супер приз! Вы верно заметили, это ошибка.
Конечно же нужно использовать переменную modelSelObj, объявленную перед циклом. Я поправил это в статье.
что такое devElm? должно быть — это объекты второго списка, которые при повторном изменении выбора в первом списке заново наполняют второй?
И еще одно: У нас в списках обязательно должен быть как минимум 1 пункт . у меня, например, «Выберите округ». Но, очищая список инструкцией modelSelObj.remove(i); мы полностью удаляем все элементы в селекте, следовательно, и опция «Выберите округ» слетает, а вместо неё сразу же добавляется первый элемент из массива…как сделать, чтобы «Выберите округ» всегда был первым элементом, даже при повторном изменении предыдущего списка?
А там обратите внимание даже в комментариях к коду написано, что
//очистим список моделей — все, кроме первого элемента
Что за priceData?
Почему бы не показать код полностью чтобы все было понятней?
Попробуйте начать читать статью с начала.