http://www.exspressinform.ru/


/* 18.05.2006 */

Javascript SELECT - динамические списки

Автор: Цыгырлаш Игорь (18.05.2006)

"Как реализовать следующее: есть два HTML списка select, нужно динамически с помощью Javascript заполнить второй список по значению, выбранному в первом? И если я таких селектов связанных захочу много, а не два?" - эти вопросы были вчера заданы мне по аське и подвигли меня к написанию этой статьи...

Как это выглядит снаружи

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

Работаем с SELECT из Javascript (что и как)

Для решения поставленной задачи необходимо иметь понятие о том как воздействовать на элемент SELECT из javascript скрипта.

Пусть у нас на странице есть элемент:

<FORM name="myForm">

  <select id="mySelectId" name="mySelect">
    <option value="str0"> Строка списка 0 </option>
    <option value="str1"> Строка списка 1 </option>
    <option value="str2"> Строка списка 2 </option>
  </select>

</FORM>

Как видно, SELECT состоит из элементов OPTION, которые представляют собой элементы списка. Сами элементы OPTION состоят из текстовой метки заключенной между тегами option и атрибута value в котором содержится значение соответствующее текстовой метке. Текстовая метка видна пользователю в списке, тогда как значение value не отображается на экране, а передается на сервер при отправке формы (на сервер передаются только значения выделенных элементов списка). Таким образом, для изменения элемента SELECT, мы должны воздействовать на элементы OPTION.

Для обращения к нашему элементу списка будем использовать DOM метод getElementById объекта document:

var objSel = document.getElementById("mySelectId"); 

Если список находится внутри формы, то к нему можно обратиться и через форму:

var objSel = document.myForm.mySelect;

Где, myForm имя формы заданное через атрибут name в теге FORM, а mySelect имя элемента формы, заданное через атрибут name в теге SELECT.

Как добавить новый элемент в список SELECT ?

Все HTML элементы OPTION доступны в javascript-скрипте через свойство options объекта Select, которое является коллекцией элементов (упрощенно говоря массивом) элементов Option.

var objSel = document.getElementById("mySelect");

//Создаем новый объект Option и заносим его в коллекцию options
objSel.options[0] = new Option("Строка списка 0", "str0");
objSel.options[1] = new Option("Строка списка 1", "str1");
objSel.options[2] = new Option("Строка списка 2", "str2");

Добавлять элементы в конец списка удобно так:

objSel.options[objSel.options.length] = new Option("текст", "значение");

Свойство length объекта options содержит количество элементов в списке. Данное свойство доступно как для чтения, так и для записи. Если установить свойству length значение больше чем текущее количество элементов, то в список будут добавлены пустые элементы. Если же установленное значение меньше текущего количества элементов списка, то список будет усечен до указанного количества элементов (лишние элементы будут удалены). Таким образом, добавить новый элемент в список можно и так (допустим у нас есть пустой список, добавим первый элемент):

objSel.options.length=1; //добавляем в конец списка пустой элемент
objSel.options[0].text = "Строка списка 0";
objSel.options[0].value = "str0";

Вернемся к функции-конструктору Option(). Помимо указания текста и значения можно указать является ли элемент выделенным по умолчанию (defaultSelected), т.е. будет ли элемент выделенным при сбросе формы методом reset() и является ли элемент выделенным в данный момент (selected). Полный синтаксис функции конструктора объекта Option имеет следующий вид:

var newOpt = new Option("text", "value", isDefaultSelected, isSelected);

Где, первые два аргумента - это строки, а третий и червертый булевы аргументы и принимают значения true или false.

До сих пор для добавления элемента мы использовали BOM, однако лучше, во всех отношениях, использовать методы DOM, и реализовывать добавление элемента так:

function addOption (oListbox, text, value, isDefaultSelected, isSelected)
{
  var oOption = document.createElement("option");
  oOption.appendChild(document.createTextNode(text));
  oOption.setAttribute("value", value);

  if (isDefaultSelected) oOption.defaultSelected = true;
  else if (isSelected) oOption.selected = true;

  oListbox.appendChild(oOption);
}

Пример использования:

var objSel = document.getElementById("mySelect");
addOption(objSel, "текст", "значение", true);

И на последок о некоторых особенностях IE :-) В IE 5+ исключена (ввиду нестабильной работы) возможность добавления элемента списка через new Option(), когда сам список SELECT находится в одном документе (окне, фрейме), а элементы добавляются из другого документа (окна, фрейма), например, если SELECT в родительском окне, а добавить элементы нужно из дочернего окна, открытого через window.open(). Используя DOM-методы вы сможете обойти данную проблему. Более подробно о данной проблеме...

Доступ к элементам списка

var text  = objSel.options[2].text;
var value = objSel.options[2].value;

Элементы списка в массиве options индексируются с нуля, поэтому первый элемент списка имеет индекс 0:

objSel.options[0] - первый элемент списка.
objSel.options[objSel.options.length-1] - последний элемент списка

Изменение элемента списка

objSel.options[1].text = "Новый текст";
objSel.options[1].value = "новое значение";

objSel.options[2] = new Option("Новый текст", "новое значение");//полная замена элемента на новый

Как узнать какой элемент выбран и как выбрать нужный элемент?

У объекта элемента SELECT существует свойство selectedIndex, которое содержит индекс выделенного элемента, или -1 если нет выделенных элементов.

if ( objSel.selectedIndex != -1)
{
  //Если есть выбранный элемент, отобразить его значение (свойство value)
  alert(objSel.options[objSel.selectedIndex].value);
}
Здесь хочу заметить, что возможен выбор нескольких значений в списке, для этого нужно указать свойство multiple в теге SELECT и указать количество видимых без прокрутки списка элементов через атрибут size (т.е. установить высоту списка измеряемую в видимых элементах), например, разметка <select size="3" multiple>...</select> создаст список высотой в 3 элемента, в котором разрешено выбирать несколько элементов сразу. Если вы указываете size больше 1, то список перестает быть выпадающим, а изменяет вид на обычный блок с полосой прокрутки (она доступка при необходимоти). Свойство size можно указывать и без multiple.

В случае когда доступен множественный выбор элементов, данное свойство содержит индекс первого выделенного в списке элемента (т.е. того элемента который в списке находиться выше). Чтобы в данном случае найти все выделенные элементы нужно перебрать в цикле все элементы массива options и проверить их свойство selected, которое равно true у выделенных элементов:

function getSelectedIndexes (oListbox)
{
  var arrIndexes = new Array;
  for (var i=0; i < oListbox.options.length; i++)
  {
      if (oListbox.options[i].selected) arrIndexes.push(i);
  }
  return arrIndexes;
};

Функция getSelectedIndexes принимает в качестве аргумента объект списка и возвращает массив индексов выделенных элементов.

Пример использования (отображение индексов выделеных элементов):

var objSel = document.getElementById("mySelect");
 
alert ( getSelectedIndexes(objSel) ); 

Описанные свойства selectedIndex и selected являются свойствами как для чтения так и для записи, поэтому присваивая им значения можно выделять нужные элементы.

//выбрать (выделить) второй элемент списка
objSel.selectedIndex = 1;
//или так
objSel.options[1].selected=true;

Для выбора нескольких элементов (при установленном свойстве multiple) нужно установить свойство selected=true для всех нужных элементов:

//выбрать (выделить) 1 и 3 элементы списка
objSel.options[0].selected=true;
objSel.options[2].selected=true;

Следует обратить внимание на баг в браузере Opera, проявляющийся при динамическом заполнении списка через скрипт и последующей установке выделенного пункта через свойство selectedIndex или selected. Баг проявляется в добавлении пустых элементов в список, доступ к которым из скрипта не возможен (скрипт их не видит, зато пользователь видит и может их выбрать). Эти пустые лже-элементы списка появляются после установки свойства selectedIndex или selected. Для обхода этой ошибки в Opera используйте установку этих свойств через setTimeout с задержкой в 1мс:

var objSel = document.getElementById("mySelect");

//Динамически создаем элементы списка
objSel.options[0] = new Option("Строка списка 0", "str0");
objSel.options[1] = new Option("Строка списка 1", "str1");

//Выделяем второй элемент списка
setTimeout( function(){objSel.options[1].selected=true;}, 1 );

Удаление i-го элемента списка

objSel.options[1] = null; //удалить элемент списка с индексом 1 (второй элемент списка)

Или используя DOM метод remove:

oListbox.remove(0); //удалить первый элемент списка

Как очистить список SELECT (как удалить все элементы списка)?

Для этого можно установить свойство length в ноль:

objSel.options.length = 0;

Либо вызвать DOM метод remove для каждого элемента списка:

function clearSelect(oListbox)
{
  for (var i=oListbox.options.length-1; i >= 0; i--)
  {
      oListbox.remove(i);
  }
};

Как сдвинуть элемент списка вверх или вниз

function shiftUpOption (oListbox, iIndex)
{
  if (iIndex > 0) 
  {
    var oOption = oListbox.options[iIndex];
    var oPrevOption = oListbox.options[iIndex-1];
    oListbox.insertBefore(oOption, oPrevOption);
  }
};

function shiftDownOption (oListbox, iIndex)
{
  if (iIndex < oListbox.options.length - 1)
  {
    var oOption = oListbox.options[iIndex];
    var oNextOption = oListbox.options[iIndex+1];
    oListbox.insertBefore(oNextOption, oOption);
  }
};

Примеры:

var objSel = document.getElementById("mySelect");
shiftDownOption (objSel, 1); //переместить второй элемент списка (с индектом 1) на первую позицию (с индексом 0)

Динамические связанные списки Select - взгляд изнутри

Ну что же, теперь вы знаете достаточно, чтобы выполнить практически любую задачу связанную с элементом SELECT и Javascript, а так же понять пример реализации задачи, поставленной в самом начале статьи. Как выглядит готовое, работающее решение показано в самом верху, а как это выглядит изнутри смотрите...

<html>
  <head>
  <title>Связанные селекты</title>
  
  <!-- Подключаем javascript-библиотеку функций -->
  <script type="text/javascript" src="linkedselect.js"></script>
</head>

<body>

<!-- Первый (главный) список (изначально заполнен вручную) -->
  <select size="4" id="List1">
    <option value="ie"> Internet Explorer </option>
    <option value="safari"> Safari </option>
  </select>
  
<!-- Подчиненный список 1 (изначально пуст) -->
  <select size="4" id="List2"></select>
  
<!-- Подчиненный список 2 (изначально пуст) -->
  <select size="4" id="List3"></select>
  
<script type="text/javascript">
// Создаем новый объект связанных списков
var syncList1 = new syncList;

// Определяем значения подчиненных списков (2 и 3 селектов)
syncList1.dataList = {

/* Определяем элементы второго списка в зависимости 
от выбранного значения в первом списке */

  'ie':{
      'ie_win':'Windows',
      'ie_mac':'Mac'
  },
  
  'safari':{
      'safari_mac':'Mac'
  },

/* Определяем элементы третьего списка в зависимости 
от выбранного значения во втором списке */

  'ie_win':{
      'ie_win_5':'версия 5',
      'ie_win_6':'версия 6'
  },
  
  'ie_mac':{
      'ie_mac_5':'версия 5'
  },
  
  'safari_mac':{
      'safari_mac_1':'версия 1',
      'safari_mac_2':'версия 2'
  }
};

// Включаем синхронизацию связанных списков
syncList1.sync("List1","List2","List3");
</script>

</body>
</html>

Давайте разберем страницу. Первым делом в разделе head подключаем библиотеку linkedselect.js, которая определяет класс объектов syncList и собственно выполняет синхронизацию. Далее в теле документа (внутри тэга body) определяем HTML списки SELECT. Причем первый список (List1) уже содержит нужные значения (тэги option) , а два других (List2, List3), которые должны заполняться динамически, изначально пусты. Затем создаем объект связанного списка: var syncList1 = new syncList, и заполняем его свойство syncList1.dataList данными для пустых списков List2 и List3 (надеюсь, что разукрашенный код и комментарии позволят понять, как dataList-объект формируется). Ну и последний шаг, это запуск синхронизации - вызов метода syncList1.sync(). Данный метод принимает в качестве аргументов id селектов, которые нужно синхронизировать между собой, их может быть 2 и более (в нашем случае 3). Имейте в виду, что порядок следования аргументов важен и определяет подчиненность селектов (главный вначале, подчиненные за ним). Так же замечу, что создавать объект syncList и заполнять его данными можно и до определения самих селектов тэгами HTML (например, в разделе head документа), главное чтобы селекты уже были в документе на момент вызова метода syncList.sync().

P.S.: Не забудьте скачать и сохранить у себя библиотеку linkedselect.js

-> Обсудить статью в форуме
http://www.exspressinform.ru/
<- Назад к списку статей
•> Ссылки на символы в HTML4
<• Подсвечивание строк таблицы