AJAX: реализация трех связанных списков
Пусть у нас есть Интернет-магазин и мы хотим предоставить покупателю удобный интерфейс выбора товара. Для этого мы формируем три выпадающих списка для выбора:
- Категории (группы) товаров, например “Мониторы”, “Ноутбуки” и т.п.
- Производителя, например, “Samsung”, “Acer” и т.п.
- Товара
Вопрос в том, что эти выпадающие списки зависят друг от друга. Если пользователь выбрал категорию “Мониторы”, то мы должны сформировать второй список таким образом, чтобы в нем были представлены только производители мониторов. Соответственно, третий список формируется на основе выбранных значений первого и второго списков.
Для начала рассмотрим структуру базы данных нашего магазина.
Таблица categories:
Таблица makers:
Таблица products:
Имея перед глазами эти три таблицы, нетрудно прикинуть, как будут выглядеть наши выпадающие списки:
1001=>Samsung
1=>Монитор Samsung 740N
2=>Монитор Samsung 943N
3=>Монитор Samsung 2043NW
4=>Монитор Samsung SM2232BW
1002=>Acer
5=>Монитор Acer AL1716FS
6=>Монитор Acer AL1916CS
7=>Монитор Acer AL2216WSD
8=>Монитор Acer AL2416WBSD
1004=>BenQ
15=>Монитор BenQ G900W
16=>Монитор BenQ G700
2=>Ноутбуки
1001=>Samsung
11=>Ноутбук Samsung R20
12=>Ноутбук Samsung R60
1002=>Acer
9=>Ноутбук Acer Aspire 5315
10=>Ноутбук Acer Extensa 5220
1003=>Toshiba
13=>Ноутбук Toshiba A210-19A
14=>Ноутбук Toshiba A210-199
Ну а дальше - реализация нашей идеи:
Файл index.php
// Соединяемся с сервером базы данных
require 'connect.php';
header("Content-Type: text/html; charset=utf-8");
?>
<html>
<head>
<title>Динамический select</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="ajax.js"> </script>
</head>
<body>
<?php
echo '<form action="'.$_SERVER['PHP_SELF'].'" method="post">'."\n";
// Получаем из БД список категорий
$query = 'SELECT category_id, title FROM categories WHERE 1 ORDER BY category_id';
$res = mysql_query( $query );
echo 'Категории: <select name="category" id="category" onchange="getList(this.value, \'\');">'."\n";
echo '<option value="0">Выберите</option>'."\n";
while ( $ctg = mysql_fetch_array( $res ) ) {
echo '<option value="'.$ctg['category_id'].'">'.$ctg['title'].'</option>'."\n";
}
echo '</select><br/>'."\n";
?>
Производители:
<select name="maker" id="maker" onchange="getList(this.form.elements['category'].value, this.value);">
<option value="0">Выберите</option>
</select><br/>
Товары: <select name="product" id="product"><option value="0">Выберите</option></select>
</form>
</body>
</html>
Файл getList.php
// Соединяемся с сервером базы данных
require 'connect.php';
// Если выбрано значение первого списка - формируем второй список
if ( !isset($_GET['maker']) ) {
// Получаем из БД список производителей
$query = 'SELECT DISTINCT a.maker_id AS m_id, a.title AS m_title
FROM makers a INNER JOIN products b
ON a.maker_id=b.maker_id
WHERE b.category_id='.$_GET['category'].'
ORDER BY a.maker_id';
$res = mysql_query( $query );
$makerOptions = '<option value="0">Выберите</option>';
while ( $mkr = mysql_fetch_array( $res ) ) {
$makerOptions = $makerOptions.'<option value="'.$mkr['m_id'].'">'.$mkr['m_title'].'</option>';
}
$response = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'.
'<response>'.
'<action>'.
'makeMakerList'.
'</action>'.
'<options>'.
$makerOptions.
'</options>'.
'</response>';
} else { // Если выбрано значение из списка производителей - формируем список товаров
$query = 'SELECT product_id, title
FROM products
WHERE category_id='.$_GET['category'].'
AND maker_id='.$_GET['maker'].'
ORDER BY product_id';
$res = mysql_query( $query );
$productOptions = '<option value="0">Выберите</option>';
while( $prd = mysql_fetch_array( $res ) ) {
$productOptions = $productOptions.'<option value="'.$prd['product_id'].'">'.$prd['title'].'</option>';
}
$response = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'.
'<response>'.
'<action>'.
'makeProductList'.
'</action>'.
'<options>'.
$productOptions.
'</options>'.
'</response>';
}
header('Content-Type: text/xml');
echo $response;
?>
Файл ajax.js
function createRequest() {
try {
request = new XMLHttpRequest();
} catch (trymicrosoft) {
try {
request = new ActiveXObject("Msxml2.XMLHTTP");
} catch (othermicrosoft) {
try {
request = new ActiveXObject("Microsoft.XMLHTTP");
} catch (failed) {
request = null;
}
}
}
if (request == null) alert("Ошибка при создании объекта XMLHttpRequest!");
}
function getList(ctg, mkr) {
document.getElementById("product").innerHTML = '<option value="0">Выберите</option>';
if ( mkr == "" )
url = "getList.php?category=" + ctg;
else
url = "getList.php?category=" + ctg + "&maker=" + mkr;
createRequest();
request.open("GET", url, true);
request.onreadystatechange = makeList;
request.send(null);
}
function makeList() {
// только при состоянии "complete"
if (request.readyState == 4) {
// для статуса "OK"
if (request.status == 200) {
// здесь идут построение списков заново
responseXml = request.responseXML;
xmlDoc = responseXml.documentElement;
action = xmlDoc.getElementsByTagName("action")[0].firstChild.data;
options = xmlDoc.getElementsByTagName("options")[0].firstChild.data;
if ( action == "makeMakerList" )
document.getElementById("maker").innerHTML = options;
else
document.getElementById("product").innerHTML = options;
} else {
alert("Не удалось получить данные от сервера:\n" + request.statusText);
}
}
}
var request = null;
function createRequest() {
try {
request = new XMLHttpRequest();
} catch (trymicrosoft) {
try {
request = new ActiveXObject("Msxml2.XMLHTTP");
} catch (othermicrosoft) {
try {
request = new ActiveXObject("Microsoft.XMLHTTP");
} catch (failed) {
request = null;
}
}
}
if (request == null) alert("Ошибка при создании объекта XMLHttpRequest!");
}
function getList(ctg, mkr) {
var _select = document.getElementById("product");
_select.innerHTML = ""; // Удаляем всех потомков
var option = document.createElement("option");
var optionText = document.createTextNode("Выберите");
option.appendChild(optionText);
option.setAttribute("value", "0");
_select.appendChild(option);
if ( mkr == "" )
url = "getList.php?category=" + ctg;
else
url = "getList.php?category=" + ctg + "&maker=" + mkr;
createRequest();
request.open("GET", url, true);
request.onreadystatechange = makeList;
request.send(null);
}
function makeList() {
// только при состоянии "complete"
if (request.readyState == 4) {
// для статуса "OK"
if (request.status == 200) {
// здесь идет построение списков заново
var responseXml = request.responseXML;
var xmlDoc = responseXml.documentElement;
var action = xmlDoc.getElementsByTagName("action")[0].firstChild.data;
if ( action == "makeMakerList" ) {
_select = document.getElementById("maker");
} else {
_select = document.getElementById("product");
}
_select.innerHTML = ""; // Удаляем всех потомков
options = xmlDoc.getElementsByTagName("option");
for (var i=0; i<options.length; i++) {
// Извлекаем значение атрибута value и текст
var value = options[i].getAttribute("value");
var text = options[i].firstChild.data;
// Формируем очередной элемент option
var option = document.createElement("option");
var optionText = document.createTextNode(text);
option.appendChild(optionText);
option.setAttribute("value", value);
_select.appendChild(option);
}
} else {
alert("Не удалось получить данные от сервера:\n" + request.statusText);
}
}
}
Дамп базы данных:
`product_id` INT(11) NOT NULL AUTO_INCREMENT,
`category_id` INT(11) NOT NULL DEFAULT '0',
`maker_id` INT(11) NOT NULL DEFAULT '0',
`title` VARCHAR(255) NOT NULL DEFAULT '',
`price` FLOAT NOT NULL DEFAULT '0',
PRIMARY KEY (`product_id`)
) ENGINE=INNODB DEFAULT CHARSET=cp1251;
INSERT INTO `products` VALUES (1, 1, 1001, 'Монитор Samsung 740N', 5700);
INSERT INTO `products` VALUES (2, 1, 1001, 'Монитор Samsung 943N', 6430);
INSERT INTO `products` VALUES (3, 1, 1001, 'Монитор Samsung 2043NW', 7000);
INSERT INTO `products` VALUES (4, 1, 1001, 'Монитор Samsung SM2232BW', 11500);
INSERT INTO `products` VALUES (6, 1, 1002, 'Монитор Acer AL1916CS', 6000);
INSERT INTO `products` VALUES (7, 1, 1002, 'Монитор Acer AL2216WSD', 8900);
INSERT INTO `products` VALUES (8, 1, 1002, 'Монитор Acer AL2416WBSD', 15000);
INSERT INTO `products` VALUES (9, 2, 1002, 'Ноутбук Acer Aspire 5315', 14500);
INSERT INTO `products` VALUES (10, 2, 1002, 'Ноутбук Acer Extensa 5220', 15500);
INSERT INTO `products` VALUES (11, 2, 1001, 'Ноутбук Samsung R20', 15800);
INSERT INTO `products` VALUES (12, 2, 1001, 'Ноутбук Samsung R60', 20100);
INSERT INTO `products` VALUES (13, 2, 1003, 'Ноутбук Toshiba A210-19A', 24700);
INSERT INTO `products` VALUES (14, 2, 1003, 'Ноутбук Toshiba A210-199', 17000);
INSERT INTO `products` VALUES (15, 1, 1004, 'Монитор BenQ G900W', 5000);
INSERT INTO `products` VALUES (16, 1, 1004, 'Монитор BenQ G700', 4800);
CREATE TABLE `categories` (
`category_id` INT(11) NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY (`category_id`)
) ENGINE=INNODB DEFAULT CHARSET=cp1251;
INSERT INTO `categories` VALUES (1, 'Мониторы');
INSERT INTO `categories` VALUES (2, 'Ноутбуки');
CREATE TABLE `makers` (
`maker_id` INT(11) NOT NULL AUTO_INCREMENT,
`title` VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY (`maker_id`)
) ENGINE=INNODB DEFAULT CHARSET=cp1251;
INSERT INTO `makers` VALUES (1001, 'Samsung');
INSERT INTO `makers` VALUES (1002, 'Acer');
INSERT INTO `makers` VALUES (1003, 'Toshiba');
INSERT INTO `makers` VALUES (1004, 'BenQ');
Дмитрий:
Очень полезное решение для таких как я (не знающих JC и Ajax). Спасибо Вам за это, очень во время. Вот только работает в Firefox, а в IE и Opera нет.
21 Сентябрь 2008, 21:29Подскажите как решить эту проблемку
Роман:
Спасибо автору. В Ajax вообще не разбираюсь, но многое что подчеркнул для себя.
24 Сентябрь 2008, 4:19Не знаю как в Firefox, но в IE и Opera у меня тоже не работает
admin:
Я исправил ошибку - теперь все работает и в MS IE и в Opera. Как оказалось, эти браузеры не полностью поддерживают innerHTML. Впрочем, поскольку свойство innerHTML не включено в стандарт DOM, винить их в этом трудно. Попытка вставить внутрь тега select элементы option с помощью innerHTML, вызывало ошибку.
24 Сентябрь 2008, 11:57Роман:
Не помогло
Сделал точную копию таких же таблиц и этот же код, но всеравно ни чё не работает 
24 Сентябрь 2008, 16:23admin:
Внес изменения - попробуй еще раз: должно работать.
24 Сентябрь 2008, 18:06Роман:
Заработало!
Огромное спасибо автору за столь прекрасный скрипт и его описание!
27 Сентябрь 2008, 0:07P.S.: Так же огромное спасибо за то что автор не оставляет своих посетителей без своего внимания и быстро реагирует на отзывы
Владимир:
Подскажите, а как передать данные из последнего списка для дальнейшей обработки.
7 Октябрь 2008, 16:29admin:
Владимир, я не понял вопрос. Куда передать? Что за обработка?
8 Октябрь 2008, 10:33Владимир:
Вы писали: “Владимир, я не понял вопрос. Куда передать? Что за обработка?”
Надо внести выбранные категорию, производителя и товар в другую таблицу базы данных.
8 Октябрь 2008, 19:58admin:
Владимир, так в чем проблема? Надо по событию onchange для третьего списка отправить на сервер с помощью объекта XMLHttpRequest() выбранные значения в первом, втором и третьем списках. Или, если планируется выбрать несколько разных товаров, создать кнопку
Каждый раз, когда пользователь выбирает очередной товар с помощью выпадающих списков, он может щелкнуть по кнопке “Добавить в корзину”, а мы по этому событию вызываем функцию addToCart(), которая с помощью XMLHttpRequest() отправит запрос на сервер. Серверный скрипт, в свою очередь, выполнит запрос к базе данных на добавление данных в таблицу.
8 Октябрь 2008, 22:39Владимир:
Спасибо, понял, получилось.
9 Октябрь 2008, 13:57ArtRich:
Отличный пример! Очень помогло!!!
21 Март 2009, 14:22ArtRich:
Здравствуйте!
23 Март 2009, 20:32данный вопрос подымался выше, а именно, необходимо добавить четвертый выпадающий список, зависящий от третьего, подскажите плииз как реализовать это. Дело в том, что я плохо знаком с синтаксисом JAVA. На вид все понятно, а вот на деле никак не получается. Думаю, ни я один столкнулся с этой трудностью.
Спасибо!
ArtRich:
Спасибо! Вопрос снят, удалось сделать при помощи копирования функции onchange=”getList … Хотя не совсем правильно, зато работает)
23 Март 2009, 22:59admin:
ArtRich, давайте обсудим это на форуме. Хотелось бы услышать формулировку задачи - что за списки, как формируются, как зависят друг от друга.
24 Март 2009, 10:40Ленар:
В опере и мозиле работает, в эксплоере не хочет, в чём может быть причина?
10 Апрель 2009, 10:44Тимур:
Здравствуйте! Спасибо за Ваше доброе дело. Я столкнулся с некоторой проблемой. сделал все точно как у Вас. но списки не реагируют друг на друга. Может где-то не подключена поддержка Java? Сборка стандартная=сервер апач, мускл, php..
26 Июнь 2009, 23:13admin:
сделал все точно как у Вас. но списки не реагируют друг на друга
30 Июнь 2009, 9:21Значит, не все. давайте обсудим это на форуме.
тобик:
Спасибо, материал помог сориентироваться.
5 Июль 2009, 18:53Лео:
Почему в файлах кодировка utf8 а в БД 1251?
1 Сентябрь 2009, 1:01admin:
Почему в файлах кодировка utf8 а в БД 1251?
1 Сентябрь 2009, 9:08А почему нет?
Виталий:
В чем может быть причина в ошибке - “Не удалось получить данные от сервера:Not Found”. Дома на локальной машыне все работает.
26 Ноябрь 2009, 19:09admin:
Ошибка “Not Found” - “Не найден (документ, скрипт)”. Скорее всего, неправильно указан путь к серверному скрипту:
26 Ноябрь 2009, 22:24Различие между абсолютными и относительными путями. В файловой системе и на сайте
Виталий:
Спасибо большое. Оказывается просто напросто пропустил то что в файле getList.php есть большая буква “L”. Вот и мучился пока ненашол что у меня он с маленькой буквой.
28 Ноябрь 2009, 14:49TYUS:
Спасибо большое за пример.
Из вашего примера сделал себе 4 связанных списка для сервиса автомобильной тематики.
ЗЫ блог в ридер
15 Декабрь 2009, 15:23retuam:
Спасибо. Работает. Никак только не могу вывести данные в тертьем селекте в зависимости от первого без выбора значения во втором. Собственно при выборе “Монитор” хотелось бы видеть в третьем селекте все “мониторы” таблицы ‘products’ без ноутбуков?
8 Январь 2010, 4:16андрей:
Спасибо за пример. Очень хороший. Но у меня такой вопрос. Все сделал как у Вас - работает. Начал делать со своей базой работать перестало. Долго искал где ошибка. Но в отличие от Вашего примера у меня идентификаторы (category_id, marker_id и produkt_id) определяются не числами, а словами из латинских символов. Когда я добавил в свою таблицу столбец с идентификатором по id то все пошло. Так что этот скрипт обрабатывает в $_GET['category'] и $_GET['maker'] только числа?
26 Февраль 2010, 14:30admin:
этот скрипт обрабатывает в $_GET['category'] и $_GET['maker'] только числа
26 Февраль 2010, 14:43Чтобы скрипт работал со строками, надо в SQL-запросе строку заключить в кавычки:
… WHERE maker_id=’Samsung’
андрей:
Спасибо большое, теперь все отлично.
26 Февраль 2010, 14:53Ольга:
Отличная статья! Все бы хорошо, вот только у меня с кодировкой виимо какая-то проблема, то что берет из базы нечитабельно
27 Октябрь 2010, 23:23Dima:
Блин гениально! огромное спасибо!
16 Май 2011, 17:55Андрей:
Ольга:
Отличная статья! Все бы хорошо, вот только у меня с кодировкой виимо какая-то проблема, то что берет из базы нечитабельно
Меняете кодировку во всех файлах с UTF-8 на Windows-1251 - все работает.
19 Май 2011, 16:38Евгений:
Скопировал полностью всё… Списки не реагируют друг на друга.
13 Июль 2011, 15:08Пробовал глобальные переменные присваивать в значения обычных переменных в getList.php, ну и подправлял код. Все равно… списки молчат.
Юрий:
спасибо, статья ушла в печать
26 Июль 2011, 19:41Одного никак в толк не возьму - зачем в ajax.js дублируются все функции, а реально работают только последние?
Юрий:
И ещё вопрос, с вашего позволения, а зачем использовать responseXML, почему не просто responseText?
26 Июль 2011, 20:37admin:
И ещё вопрос, с вашего позволения, а зачем использовать responseXML, почему не просто responseText?
26 Июль 2011, 21:26Вам выбирать – text, XML, JSON…
Михаил:
получилось добавить и 4-й список. спасибо большое за скрипт. если нужно, могу выложить код.
22 Август 2011, 19:59Влад:
Спасибо! Все прекрасно работает, но только возник вопрос - почему в ajax.js повторяются функции.
27 Ноябрь 2011, 14:59Вопрос уже задавался, но остался открытым…
Алексей:
БОЛЬШОЕ СПАСИБО Автору за приведенный пример!
29 Ноябрь 2011, 21:22Таких как вы не очень много, которые на поставленную задачу приводят для общего доступа её решение. А много тех, кто сидит на форумах и если им такой вопрос задать, они мозг вынесут просто по полной программе и не факт, что человек задавший вопрос, получит на него ответ)
Еще раз ОГРОМНОЕ вам спасибо!
Антон:
Есть хороший пример динамических списков http://webersoft.ru/select-ajax-mysql/
2 Ноябрь 2012, 21:06