Динамический select, динамические списки, выпадающие списки, зависимые списки, зависимые select-ы.

Создание нескольких html-списков, состав внутренних элементов некоторых из которых зависит от выбранного элемента в других списках

Дата публикации: 17.12.2013

Как обычно проходит свой путь развития веб-разработчик-самоучка? Он занимается тем, что ему кажется более простым. Считает, что ему вполне достаточно и этого для решения тех задач, которые стоят перед ним на начальном этапе. Но рано или поздно любой веб-разработчик приходит к пониманию того, что его знаний ему катастрофически не достаточно. Человек, мало-мальски знающий PHP , наверное, что-то понимающий в HTML и CSS), до последнего будет отрицать необходимость изучения JavaScript. Будет сам себя убеждать в том, что ему это сейчас не нужно, что хватит и того багажа знаний и навыков, которые имеются.

Но приходит он… и тут капитулируют все… Он заставляет неистово гуглить, усердно писать на форумы, искать, копать, латать дыры в знаниях. Он коварен. Он — динамический список, динамический select.

Почему? Что к этому приводит? Ну, так случается, что ВНЕЗАПНО нужно сделать так, чтобы при выборе одного параметра в раскрывающемся списке, что-то происходило. Например, при выборе Ростовской области из списка регионов нужно отфильтровывать список населенных пунктов, чтобы для Ростовcкой области нельзя было выбрать города и села Еврейской автономной области. Или при выборе автопроизводителя происходила фильтрация по маркам машин. Примеров можно привести чуть больше 9000.

Подробнее изучим задачу. Допустим, у нас есть три поля раскрывающихся списков: Тип транспорта, Вид транспорта, Категории транспорта. В конечном итоге нам необходимо, чтобы пользователь указал нужную ему категорию транспорта. Но мы хотим упростить жизнь своему пользователю, и не желаем, чтобы он утруждал себя поиском нужной категории среди совсем ненужных ему данных. Ведь категорий транспорта может быть великое множество, но подразделяются они на разные виды и типы.

Мы решили, что нам хватит трех списков. Делая выбор в первом, пользователь получает отфильтрованный второй список. В него попадут только те значения, которые относятся к выбранной категории в первом списке. Далее, делая выбор во втором списке, пользователь получает третий, сформированный по описанному выше принципу.

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

И ещё одно уточнение. Если пользователь захочет изменить выбранные данные, зависимые от него поля должны быть сброшены в исходное состояние. Это должно происходить для того, чтобы в списках всегда были только значения, которые имеют отношение к выбранным категориям и типам.

Вам важно знать вот что! Информация в этой статье для вас, скорее всего, сегодня уже является устаревшей. Вряд ли вы пытаетесь что-то «писать» для PHP версией старше чем 5.5. А если и пытаетесь… зачем оно вам? Среди материалов я уже разместил статью с построением динамических списков (динамических select-ов), работающих в трио с MySQL и PDO. То есть там информация для select-ов берётся из базы данных MySQL, а не просто хранится в массивах. Там работоспособный код. Смело переходите в ту статью.

Итак, создаем форму с тремя списками.


	<!-- Пишем в соответствии со стандартом HTML5 →
	<!DOCTYPE html>
	<html>
	<head>
		<!-- Пишем заголовок документа →
		<title>Динамические списки</title>
	</head>
	<body>
		<!-- Форма для динамических списков →
		<form action=»» method=  «post» id=  «dynamic_selects»>
			<!-- Создаем заголовок для списка выбора типов транспорта →
			<!-- Поле формы помещаем в контейнер с классом row →
			<div class=  «row»>
				<label for=  «type»>Тип транспорта:</label>
				<!-- Создаем поле со списком →
				<select id=  «type»>
					<!-- В список сразу внесем значение по умолчанию, а также
						несколько значений видов транспорта. Предположим, что они нам известны заранее, и хранятся, допустим, в базе данных →
					<option value=  «0»>Выберите из списка</option>
					<option value=  «1»>Наземный транспорт</option>
					<option value=  «2»>Водный транспорт</option>
					<option value=  «3»>Воздушный транспорт</option>
				</select>
			</div>
			<!-- Для списков видов и категорий транспорта мы создадим только значения по умолчанию, 
				остальные значения мы будем подгружать в них тогда, когда будет сделан выбор
				в первом списке →
			<div class=  «row»>
				<label for=  «kind»>Вид транспорта:</label>
				<!-- Так как это список зависит от выбора в первом списке, отключаем его, добавив
					к тегу select атрибут disabled→
				<select id=  «kind» disabled>
					<option value=  «0»>Выберите из списка</option>
				</select>
			</div>
			<div class=  «row»>
				<label for=  «category»>Категория транспорта:</label>
				<!-- Так как это список зависит от выбора во втором списке, отключаем его, добавив
					к тегу select атрибут disabled→
				<select id=  «category» disabled>
					<option value=  «0»>Выберите из списка</option>
				</select>
			</div>
		</form>
	</body>
	</html>

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


/**
 * Особо внешний вид страницы  «причесывать» не будем
 * Просто оформим поля нашей формы
 */
/* Для контейнера, который содержит поля формы */
div.row {
	margin-bottom:15px;	/* Нижний отступ */
}
/* Заголовки полей форм будут на отдельной строке */
label {
	display:block;
	font-weight:bold;	/* Сделаем их жирными */
	margin-bottom:5px;	/* Нижний отступ от заголовка */
}

/* Наши раскрывающиеся списки тоже  «причешем» */
select {
	width:300px;		/* Ширина */
	padding:3px 5px;	/* Величина внутренних отступов: сверху/снизу, справа/слева */
}
	

Конечно же внешний вид формы у вас может быть своим: ) Что дальше? Давайте подумаем. При выборе значения в первом списке мы должны получить перечень значений для второго. Даже не хочу сейчас описывать решение этого процесса одним только PHP. Нужно помнить одно: если речь идёт о последствиях действий пользователя, значит это JavaScript.

Но писать пригодный код на чистом JavaScript довольно не просто. Язык и сам по себе довольно не прост в понимании для тех людей, которые только-только стали осваивать PHP, а вдобавок ещё и каждый производитель броузера по-своему реализовал его в своем продукте. Для того, чтобы не заботиться о кроссбраузерности, мы воспользуемся библиотекой jQuery. Вопросы, связанные с синтаксисом данной библиотеки, здесь мы не обсуждаем.

Заголовок нашего HTML-документа мы перепишем, добавив в него подключение файла CSS и подключение библиотеки jQuery, которую мы возьмём у Яндекса. Следом мы подключим JavaScript-файл, в котором будем писать собственный JavaScript-сценарий.


<!-- Пишем заголовок документа →
<title>Динамические списки</title>
<!-- Подключаем CSS-файл к документу →
<link rel=  «stylesheet» type=  «text/css» href=  «style.css» />
<!-- Подключаем библеотеку jQuery, которая хранится на сервере Яндекса →
<script src=  «http://yandex.st/jquery/1.10.2/jquery.min.js»></script>
<!-- Подключаем файл со своим собственным скриптом →
<script src=  «my.script.js»></script>
	

Что должно происходить? При возникновении события выбора значения в первом списке, выбранное значение должно быть отправлено обработчику, который в ответ отправит перечень значений для второго списка, непосредственно связанных с первым. Этот перечень мы будем получать от сервера, так как предполагаем, что нужная нам информация хранится в базе данных (MySQL, к примеру). Значит из JavaScript-сценария нам нужно будет выполнить AJAX-запрос к серверу. После того, как сервер пришлет ответ, мы помещаем его во второй список, и делаем его активным. Начнем писать наш JavaScript-сценарий.


/**
 * @author Evgeni Lezhenkin evgeni@lezhenkin.ru http://lezhenkin.ru 
 * 
 * В данном сценарии мы будем обрабатывать события выбора значений 
 * в списках тега select. Второе и третье поле списка являются зависимыми.
 * При выборе значения в первом поле select должен отправляться AJAX-запрос, 
 * который вернет перечень значений для второго списка. Далее эти значения будут
 * помещены во второй select, а сам список станет активным. 
 * После выбора значения во втором списке запускается тот же алгоритм, что описан
 * выше. 
 * 
 * Все AJAX-запросы к серверу будут выполняться через скрипт query.php методом POST
 *
 * Если пользователь меняет значение в первом списке, все поля второго 
 * и третьего удаляются, и второй и третий списки становятся неактивными. Если * Меняется значение второго списка, то удаляются все значения для третьего,
 * и он становится неактивным.
 *
 * Данный сценарий будет работать во всех броузерах благодаря библеотеке jQuery
 * 
 * Здесь не будет описываться синтаксис самого jQuery, синтаксис JavaScript. Также
 * здесь не будет подробно описываться инструментарий
 */
// Используем немедленно вызываемую функцию
  (function  () {
	// Будем работать в соответствии с требованиями современного стандарта
	// ECMAScript. Включим строгий режим.
  «use strict»;
	
	// Весь наш основной сценарий будет работать уже после загрузки документа
	jQuery  (function  () {
		// Пишем обработчик события для выбора значения в первом списке
		// Нас интересует событие изменения значения поля
		$  ('#type'). change  (function  () {
			// При изменении значения первого списка мы должны удалить
			// все имеющиеся значения во втором и третьем, а также
			// сделать их неактивными
			$  ('#kind, #category'). find  ('option:not  (:first)') 	// Ищем все теги option, не являющиеся тегом по умолчанию
				.remove  ()	// Удаляем эти теги
				// Чтобы сделать поля неактивными, неправильно менять значение атрибута disabled
				// Теперь нам нужно изменять значение свойства disabled объектов полей списка, 
				// так как мы работаем с ними через библиотеку jQuery
				.end  ()		// Возвращаемся к исходному объекту
				.prop  ('disabled',true) ;		// Делаем поля неактивными
			// Сохраним выбранное значение списка в переменную
			var type_id = $  (this). val  ();
			// Если выбрано значение по умолчанию, ничего не делаем
			if  (type_id == 0) { return; }
			// В ином случае нам необходимо отправить запрос на сервер
			// AJAX-запрос к серверу мы выполним, используя метод jQuery ajax  ()
			$.ajax  ({
				type:  «POST»,	// Тип запроса
				url:  «query.php»,	// Путь к сценарию, обработающему запрос
				dataType:  «json»,	// Тип данных, в которых сервер должен прислать ответ
				data:  «query=getKinds&type_id=» + type_id,	// Строка POST-запроса
				error: function  () {	// Обработчик, который будет запущен в случае неудачного запроса
					alert  (  «При выполнении запроса произошла ошибка:  (») ;	// Сообщение о неудачном запросе
				},
				success: function  (data) { // Обработчик, который будет запущен после успешного запроса
					// В ответ на наш запрос сервер должен прислать массив значений
					// Мы его вставим в поле второго списка с помощью цикла for
					for  (var i = 0; i < data.length; i++) {
						// Каждое полученное значение вставим в список видов транспорта
						$  ('#kind'). append  ('<option value=»' + data[i].kind_id + '">' + data[i].kind + '</option>') ;
					}
					// После того, как мы сформировали список, мы можем сделать его активным
					// обратившись к его свойству disabled
					$  ('#kind'). prop  ('disabled', false) ;	// Включаем поле
				}
			});
		});
		
		// Пишем обработчик события для выбора значения во втором списке
		// Нас интересует событие изменения значения поля
		$  ('#kind'). change  (function  () {
			// При изменении значения второго списка мы должны удалить
			// все имеющиеся значения в третьем, а также
			// сделать его неактивными
			$  ('#category'). find  ('option:not  (:first)') 	// Ищем все теги option, не являющиеся тегом по умолчанию
				.remove  ()	// Удаляем эти теги
				// Чтобы сделать поле неактивным, неправильно менять значение атрибута disabled
				// Теперь нам нужно изменять значение свойства disabled объекта поля списка, 
				// так как мы работаем с ним через библиотеку jQuery
				.end  ()		// Возвращаемся к исходному объекту
				.prop  ('disabled',true) ;		// Делаем поле неактивным
			// Сохраним выбранное значение списка в переменную
			var kind_id = $  (this). val  ();
			// Сохраним выбранное значение типа транспорта в переменную
			var type_id = $  ('#type'). val  ();
			// Если выбрано значение по умолчанию, ничего не делаем
			if  (type_id === 0) { return; }
			// В ином случае нам необходимо отправить запрос на сервер
			// AJAX-запрос к серверу мы выполним, используя метод jQuery ajax  ()
			$.ajax  ({
				type: "POST",	// Тип запроса
				url: "query.php",	// Путь к сценарию, обработающему запрос
				dataType: "json",	// Тип данных, в которых сервер должен прислать ответ
				data: "query=getCategories&type_id=" + type_id + "&kind_id=" + kind_id,	// Строка POST-запроса
				error: function  () {	// Обработчик, который будет запущен в случае неудачного запроса
					alert  ("При выполнении запроса произошла ошибка:  (") ;	// Сообщение о неудачном запросе
				},
				success: function  (data) { // Обработчик, который будет запущен после успешного запроса
					// В ответ на наш запрос сервер должен прислать массив значений
					// Мы его вставим в поле третьего списка с помощью цикла for
					for  (var i = 0; i < data.length; i++) {
						// Каждое полученное значение вставим в список категорий транспорта
						$  ('#category'). append  ('<option value="' + data[i].category_id + '">' + data[i].category + '</option>') ;
					}
					// После того, как мы сформировали список, мы можем сделать его активным
					// обратившись к его свойству disabled
					$  ('#category'). prop  ('disabled', false) ;	// Включаем поле
				}
			});
		});
		
		// Никакие обработчичик для поля третьего списка не нужны
	}); // Функция ожидания загрузки документа jQuery
})  (); // Немедленно вызываемая функция
/**
 * Вы должны понимать, что данный код далек от идеала и является лишь 
 * примером решения конкретной задачи — динамического пополнения полей
 * формы select. Здесь опущено очень много моментов, связанных с отладкой,
 * безопасностью и многим другим. И вы должны понимать это очень хорошо.
 * Данный пример просто показывает инструмент, с помощью которого решается
 * озвученная задача.
 */
	

В представленном коде для вас будет достаточно много нового. Я снабдил его обширными комментариями, которые помогут вам понять суть процесса. Теперь пришла очередь взглянуть на сценарий-обработчик AJAX-запросов. Он у нас находится в файлеquery.php.


<?php
/**
 * @author Evgeni Lezhenkin evgeni@lezhenkin.ru http://lezhenkin.ru
 * 
 * Скрипт, обрабатывающий POST-запросы от JavaScript-сценариев
 * Скрипт получает строку запроса, а в ответ отправляет массив с данными
 */
// Непосредственно для этого скрипта мы создадим здесь массивы, в которых будут храниться
// значения, запрашиваемые JavaScript-сценарием. В ваших сценариях этих массивов, скорее всего,
// не будет. Информация, подобная этой, будет в вашей базе данных, и вам её придется оттуда 
// извлечь. Как вы это сделаете, это уже ваши предпочтения
$types = array  (
	1 ≥ array  (
		// Наземный транспорт
		1 ≥ 'Железнодорожный транспорт',
		2 ≥ 'Автомобильный транспорт',
		3 ≥ 'Ручной транспорт'
	),
	2 ≥ array  (
		// Водный транспорт
		1 ≥ 'Речной транспорт',
		2 ≥ 'Морской транспорт',
		3 ≥ 'Подводный транспорт'
	),
	3 ≥ array  (
		// Воздушный транспорт
		1 ≥ 'Самолеты',
		2 ≥ 'Вертолеты',
		3 ≥ 'Ракета  (шаттл)'
	)
);
$kinds = array  (
	// Наземный транспорт
	1 ≥ array  (
		// Железнодорожный транспорт
		1 ≥ array  (
			1 ≥ 'Электропоезд',
			2 ≥ 'Дизельный поезд',
			3 ≥ 'Дрезина'
		),
		// Автомобильный транспорт
		2 ≥ array  (
			1 ≥ 'Легковой автомобиль',
			2 ≥ 'Грузовой автомобиль',
			3 ≥ 'Автобус'
		),
		// Ручной транспорт
		3 ≥ array  (
			1 ≥ 'Тачка',
			2 ≥ 'Тележка',
			3 ≥ 'Велосипед'
		)
	),
	// Водный транспорт
	2 ≥ array  (
		// Речной транспорт
		1 ≥ array  (
			1 ≥ 'Трамвай',
			2 ≥ 'Теплоход',
			3 ≥ 'Ракета'
		),
		// Морской транспорт
		2 ≥ array  (
			1 ≥ 'Крейсер',
			2 ≥ 'Круизный лайнер',
			3 ≥ 'Баржа'
		),
		// Подводный транспорт
		3 ≥ array  (
			1 ≥ 'Подводная лодка',
			2 ≥ 'Батискаф',
			3 ≥ 'Капсула смерти'
		)
	),
	// Воздушный транспорт
	3 ≥ array  (
		// Самолет
		1 ≥ array  (
			1 ≥ 'Боинг',
			2 ≥ 'Аэробус',
			3 ≥ 'Руслан'
		),
		// Вертолеты
		2 ≥ array  (
			1 ≥ 'МИ',
			2 ≥ 'Апач',
			3 ≥ 'Черная акула'
		),
		// Ракета  (шаттл)
		3 ≥ array  (
			1 ≥ 'Союз',
			2 ≥ 'Апполон',
			3 ≥ 'Дискавери',
			4 ≥ 'Буран'
		)
	)
);

// Проверяем наличие переменной, которая укажет данному сценарию какие именно данные нужны
if  (!isset  ($_POST['query']) ||! $_POST['query']) {
	exit  ("Нет данных определяющих тип запроса");
}
else {
	// Сохраняем строку запроса данных в отдельной переменной
	$query = trim  ($_POST['query']); // Очищаем от лишних пробелов
	
	// Определяем тип запроса
	switch  ($query) {
	case 'getKinds':	// Запрос на получение видов транспорта
		// Сохраним в переменную значение выбранного типа транспорта
		$type_id = trim  ($_POST['type_id']); // Очистим его от лишних пробелов
		// Формируем массив с ответом
		$result = NULL;
		$i = 0;
		foreach  ($types[$type_id] as $kind_id ≥ $kind) {
			$result[$i]['kind_id'] = $kind_id;
			$result[$i]['kind'] = $kind;
			$i++;
		}
	break;
	case 'getCategories':	// Запрос на получение категорий транспорта
		// Сохраним в переменные значения выбранных типа транспорта и вида транспорта
		$type_id = trim  ($_POST['type_id']); // Очистим их от лишних пробелов
		$kind_id = trim  ($_POST['kind_id']);
		// Формируем массив с ответом
		$result = NULL;
		$i = 0;
		foreach  ($kinds[$type_id][$kind_id] as $category_id ≥ $category) {
			$result[$i]['category_id'] = $category_id;
			$result[$i]['category'] = $category;
			$i++;
		}
	break;
	default:
		// Если данные не определены
		$result = NULL;
	break;
	}
}

// Преобразуем данные в формат json, чтобы их смог обработать JavaScript-сценарий, приславший запрос
echo json_encode  ($result);

/**
 * Данный код не идеален. Сама идея представления исходных данных о транспорте в виде массива очень
 * далека от идеала. И вы должны понимать почему. Данные должны храниться в реляционной базе данных, 
 * а представленный вариант написания сценария является лишь простейшим примером, который не стоит 
 * в таком виде применять на практике. Вы здесь должны лишь понять принципы работы языка и взаимодействия
 * между языками программирования
 */
?>
	

Можете проверять select-ы. Они отлично работают. Добавить ещё один select вы вполне сможете сами. Я помню, как много времени потратил когда-то на поиски нужной мне информации. Гуглил, искал, писал… что угодно делал, но только не читал книги и документацию. Поэтому, если вы пришли сюда и вам подошли описанные выше скрипты, не поленитесь — почитайте мануалы: )

P.S. Вам доступны описанные скрипты и в виде архива.

Вам важно знать вот что! Информация в этой статье для вас, скорее всего, сегодня уже является устаревшей. Вряд ли вы пытаетесь что-то «писать» для PHP версией старше чем 5.5. А если и пытаетесь… зачем оно вам? Среди материалов я уже разместил статью с построением динамических списков (динамических select-ов), работающих в трио с MySQL и PDO. То есть там информация для select-ов берётся из базы данных MySQL, а не просто хранится в массивах. Там работоспособный код. Смело переходите в ту статью.
  • Я опубликовал эту статью:17.12.2013
  • 75 683
  • Яндекс.Метрика

Меню сайта

Settings

Performance

CPU Load
60%
CPU Temparature
42°
RAM Usage
6,532 MB

Customer care

Reports

Projects

May 14, 2020

Upcoming events

12:00

Donec laoreet fringilla justo a pellentesque

13:20

Nunc quis massa nec enim

14:00

Praesent sit amet