Из select’а в select. Работа с двойными списками
Июль 10, 2006
В этой заметке я опишу свою реализацию двойных списков - довольно полезного элемента управления при построении сложных HTML-форм (как обычно не без участия JavaScript). Особое внимание уделяется работе с событиями. В конце - работающий пример.
Итак как обычно сначала рассмотрим HTML-часть: очевидным решением было использование таблицы для layout’а элементов формы, но я решил от этого отказаться: во-первых, потому что таблицы сегодня не в моде, во-вторых, подобные решения с таблицами уже точно существуют, и потом без таблиц (используя список) можно отлично и элегантно обойтись:
<form action="" id="form_sel2sel" method="post">
<ul class="sel2sel">
<li>
<select size="10" id="sel1" name="sel1">
<option value="1">Item1</option>
<option value="2">Item2</option>
<option value="4">Item4</option>
</select>
</li>
<li>
<input type="button" id="btn_move12" value=">" />
<br />
<input type="button" id="btn_move21" value="<" />
</li>
<li>
<select size="10" id="sel2" name="sel2">
<option value="3">Item3</option>
<option value="5">Item5</option>
</select>
</li>
</ul>
<div class="clr"></div>
<p>
<input type="hidden" value="" name="sel2_values" id="sel2_values" />
<input type="submit" class="clr" value="Go" />
</p>
</form>
Как видим, используется два select’a: sel1 и sel2 (параметр size=10 делает их раскрытыми), две кнопки: btn_move12 и btn_move21 (для перебрасывания элементов из списка в список), hidden - о нем речь пойдет немного позже и submit. Конструкций типа onclick мы не видим (их и не будет).
Прежде чем подключать JavaScript, опишем CSS-стили. Их немного, потому приведу полностью:
* {
padding: 0;
margin: 0 auto;
}
body {
text-align: center;
}
.clr {
clear: both;
}
ul {
list-style: none;
width: 410px;
}
li {
float: left;
}
input{
width: 50px;
height: 30px;
font-size: 20px;
margin: 27px;
}
select {
width: 150px;
}
Результат можно посмотреть на скриншоте:

Вот теперь сделаем, чтобы картинка ожила. Основная функция - move, ее переметры - первый и второй select’ы. Заметим что IE как всегда отличился, и для него добавление элемента в select выглядит немого по-другому чем для остальных.
function move(select1, select2)
{
var selInd = select1.selectedIndex;
if(selInd != -1)
{
var opt = document.createElement('OPTION');
opt.text = select1.options[selInd].text;
opt.value = select1.options[selInd].value;
try {
select2.add(opt,null);
}
catch(ex) {
select2.add(opt); // IE only
}
select1.remove(selInd);
}
}
Все, что осталось - привязать выполнение функции move к определенным событиям. В моем случае - это нажатия на кнопки ">", "<" а также doubleclick’и по элементах select’ов.
Самым простым способом, на первый взгляд кажется добавление прямо в HTML типа:
<input type="button" id="btn_move21" value="<" oncklick="move(…)"/>
Однако по моему опыту и мнению многих вебдевелоперов с именем так делать не стоит. Гораздо лучшим вариантом будет оставить HTML в покое и все события обрабатывать в JS-файле. Я использую для этих целей общеизвестную функцию addEvent By Scott Andrew, а также свою вспомогательную на ее основе (для нее нужно указывать не элемент, а id элемента):
function addEventById(Id, evType, fn, useCapture)
{
addEvent(document.getElementById(Id), evType, fn, useCapture);
}
Однако не все так просто и гладко как хотелось. Если нужно к событию привязать функцию с параметрами, возникают сложности. Следующая функция позволяет обойти эту проблему (_params - массив параметров, а результат - тоже функция, но уже с параметрами):
function bindParameter(_func, _params)
{
return function()
{
_func.apply(this, _params)
};
}
Но IE5 не поддерживает apply и тогда приходится дописывать:
if (!Function.prototype.apply)
{
Function.prototype.apply = function(oScope, args)
{
var sarg = [];
var rtrn, call;
if (!oScope)
oScope = window;
if (!args)
args = [];
for (var i = 0; i < args.length; i++)
sarg[i] = "args["+i+"]";
call = "oScope.__applyTemp__(" + sarg.join(",") + ");";
oScope.__applyTemp__ = this;
rtrn = eval(call);
try {
delete oScope.__applyTemp__;
}
catch(ex){}
return rtrn;
}
}
Как видим, приятного мало. Но зато все вроде работает, и мы можем спокойно добавлять события:
var d;
var sel1;
var sel2;
var sel2_values;
function init()
{
d = document;
sel1 = d.getElementById("sel1");
sel2 = d.getElementById("sel2");
sel2_values = d.getElementById("sel2_values");
sel2_values.value = "";
addEventById("btn_move12", "click", bindParameter(move,Array(sel1, sel2)), false);
addEventById("btn_move21", "click", bindParameter(move,Array(sel2, sel1)), false);
addEventById("form_sel2sel", "submit", go, false);
addEventById("sel1", "dblclick", bindParameter(move,Array(sel1, sel2)), false);
addEventById("sel2", "dblclick", bindParameter(move,Array(sel2, sel1)), false);
}
addEvent(window, "load", init, false);
После этого программа справляется со своими визуальными заданиями: элементы перебрасываются. Но скорее всего требовательному пользователю этого будет мало. Он захочет передавать информацию о состояниях selecto’в на сервер. Как жто сделать. Мне известно то что при передачи данных формы select’ы передают одно выбранное значение (или несколько в случае multiple). Нам же нужно передавать все значения. Для этого перед отправкой данных на сервер, запишем множество значений в hidden - элемент (значения разделяются символом |):
function go()
{
var s = '';
sel2_values.value = '';
for(i=0; i<=sel2.options.length-1; i++)
s += sel2.options[i].value + "|";
sel2_values.value = s;
}
После отправки данных формы, сервер получает sel2_values, которые несложно обработать, например, PHP-функцией explode.
Под конец - небольшой "Список литературы" - те ресурсы без которых эта заметка не состоялась бы:
- http://www.mredkj.com/tutorials/tutorial005.html
- http://anchor.siter.com.au/dmitry/doublelisting.html
- http://youngpup.net/2002/oldblog123
Вот и все. Работающий пример ждет своего просмотра.
Июль 10th, 2006 в 3:28 pm
Интересный пример, спасибо
Июль 19th, 2006 в 3:28 pm
V Opera 9 poslednii’ element posle perenosa odnogo iz elementov vpravo nivkakuyu ne hochet perehodit’ tuda zhe. Naprimer esli pervym peredvinut’ Item4, to posle etogo Item2 ostanetsya poslednim i ne perei’det vpravo. Tak zhe v obratnuyu storonu.
Июль 28th, 2006 в 8:58 am
В опере однозначно есть баг.
Август 8th, 2006 в 9:44 pm
Хм. В моей Опере 8.51 вроде все хорошо. Буду искать девятую и искать глюки.
Август 9th, 2006 в 11:26 am
Кажется, мне кое-как удалось решить проблему с Opera 9. Для этого нужно заменить
try { select2.add(opt,null); }на
try { select2.add(opt,null); if(select1.options.length>1) { if(selInd>0) select1.selectedIndex = selInd-1; else select1.selectedIndex = selInd+1; } }А вообще, работа с select’ами довольно грязная, так как выползает масса глюков со стороны браузеров. Кстати, ходят слухи, в ИЕ 7 этот елемент переписали заново, поскольку в прежних версиях там баги лезли отовсюду
Август 19th, 2006 в 5:13 am
Переделанная функция для поддержки select multiple
function move_select(select1, select2) { var remove_list = new Array(); for (i=0; i < select1.length; i++) { if (select1.options[i].selected == true) { var opt = document.createElement('option'); opt.text = select1.options[i].text; opt.value = select1.options[i].value; try { select2.add(opt,null); } catch(ex) { select2.add(opt); // IE only } remove_list.push(i); } } for (i=0; i < remove_list.length; i++) { if (i == 0) { select1.remove(remove_list[i]); } else { select1.remove(remove_list[i]-i); } } }Август 19th, 2006 в 9:51 am
2mor: С комментерия видно только что переделанная. НО:
Зачем переделанная. Что там нового добавляется? Что было не так? Хочется комментариев + не хочется разбирать код.
Август 20th, 2006 в 8:28 pm
2svoloshyn Добавлена возможность перемещения сразу нескольких выбранных позиций, в твоём примере перемещать можно было только по одному.
Август 20th, 2006 в 9:17 pm
2mor: ясно. Извини, что не понял сам. Но и тебе стояло бы об этом написать перед своим кодом (select multiple тоже информативная фраза, но лично до меня почему-то сразу не дошло
).
Я же написал для перемещения по одному для упрощения. Ведь главное - идея. К тому же если выбрать несколько позиций, теряется смысл перемещения по даблклику…
Если же твой код рабочий и кроссбраузерный, я думаю многие тебе скажет спасибо.
Сентябрь 16th, 2006 в 6:39 pm
Super
Август 2nd, 2007 в 9:47 am
Сайт о регулярных выражениях Regexp
Март 3rd, 2008 в 1:32 pm
Спасибо, полезная статья!