Все мы прекрасно знаем как выглядят списки в (X)HTML по умолчанию. Сегодня мы попробуем представить их в совершенно ином свете. Для начала мы назовём наш кораблик: его будут звать «Узерлист». Теперь можно приступить к разработке.
Для начала мы определимся с тем, что хотим сделать. Как уже было сказано, мы сделаем пользовательские списки со своим собственным интерфейсом. Для разработки такого типа виджета нам понадобятся те самые три компонента: (X)HTML, CSS и JavaScript, причём последнего будет гораздо больше, чем двух первых.

Рисунок 1
Список — он и в Малайзии список, то есть для его создания логичнее всего будет использование ul в качестве основного элемента (и никаких глубоковложенных div). Помимо самой структуры, мы должны будем привязать наш список к определённой форме в документе, так как мы сделаем возможность отсылать данные, которые были выбраны в списке, через формы. Но об этом позднее. Сейчас же, вернёмся к структуре списков.
Размечать мы их будем подобным образом:
<ul id="идентификатор" >
<li value="значение">Элемент 1</li>
</ul>
По идентификатору мы сможем передать наш список в класс-обработчик. У каждого элемента li есть атрибут value, который отвечает за отсылаемое формой значение (по аналогии со стандартными списками select). Элементы могут встречаться неограниченное количество раз, то есть размер списка (количество элементов в нём) ничем не ограничен.
Список выглядит немного неопрятно? Согласен, поэтому сейчас мы ему припудрим носик с помощью CSS:
/* Индивидуальный класс для списка */
.userlist
{
list-style: none;
list-style-image: none;
padding: 0px;
margin: 0px;
width: 150px;
font-size: 0.7em;
}
/* Параметры каждого элемента списка */
.userlist li
{
padding: 4px;
border: dotted 1px #bbb;
border-bottom: none;
color: #666;
cursor: hand;
cursor: pointer;
width: 100%;
}
После всех приготовлений можно приступить к разработке ядра нашего списка на JavaScript.
Для начала мы подготовимся и создадим суплементарные функции для дальнейшей разработки. Это всем уже, наверное, знакомые замечательные простые фунцкии bind и listen, предназначенные для обработки событий на JavaScript.
function $(elementid)
{
return document.getElementById(elementid);
}
function bind(toObject, methodName)
{
return function(e){toObject[methodName](e)}
}
function listen(object, hevent, hfunc)
{
if (object.addEventListener)
object.addEventListener(hevent,hfunc,false);
else if (object.attachEvent)
object.attachEvent('on'+hevent,hfunc);
}
function listenex(object, hevent, lobject, lfunc)
{
listen(object, hevent, bind(lobject, lfunc));
}
Мы добавили функцию listenex, которая немного упростит совместное использование двух других функций.
Так как списков может быть много, чтобы каждый список можно было настраивать и перенастраивать многократно и отдельно от других списков на странице, мы будем использовать объектно-ориентированные возможности JavaScript. Для начала создадим каркас нашей функции.
function UserList(listid, assocform)
{
//Получаем список по идентификатору
this.ulist = $(listid);
//Создаём связанный со списком элемент
//Он необходим для отправки формы
this.usubm = document.createElement("input");
//Массив ответов
this.usubm_values = Array();
//Настройка списка, возможность мультивыбора
this.multi = true;
//Ассоциируемая со списком форма
this.uform = $(assocform);
}
Для работы списка мы его связываем с формой. Таким образом, наш список будет нести не только эстетическую функцию на себе, но и практическию.
Мы договорились, что предоставим пользователю возможность настраивать список, поэтому внутри нашего класса мы создадим функцию Setup, которая этим и будет заниматься.
this.Setup = function(ismulti, fixedheight, setwidth, setheight)
{
//Здесь совершенно скоро будет наша функция.
}
Пройдёмся по опциям новоявленной функции:
ismulti — логическое значение (true или false), определяющее возможность количества выбранных вариантов: один или несколько.

Рисунок 2
fixedheight — фиксировать высоту списка. При установке значения в true, появляется вертикальная полоса прокрутки.

Рисунок 3
setwidth и setheight — установка длины и высоты списка, соответственно.

Рисунок 4
Теперь приступим к реализации самой функции.
//Устанавливаем имя класса CSS для нашего списка
this.ulist.className = "userlist";
//Устанавливаем размеры списка, если присутствуют соответствующие опции
if (setwidth != null)
this.ulist.style.width = setwidth + "px";
else
this.ulist.style.width = "150px";
if (setheight != null)
this.ulist.style.height = setheight + "px";
//Синхронизируем параметр типа списка
if (ismulti != null) this.multi = ismulti;
//Если список будет закреплённым по высоте, то добавляем новый класс CSS к списку.
if (fixedheight != null && fixedheight != false) this.ulist.className += " fixedsize";
//Устанавливаем настройки для возможности отсылки списка через GET/POST
this.usubm.type = "hidden";
this.usubm.name = this.ulist.id;
this.ulist.parentNode.appendChild(this.usubm);
CSS-класс fixedsize представит совершенно несложно.
.userlist.fixedsize
{
overflow: auto;
}
.userlist.fixedsize li
{
width: auto;
}
Тем самым мы обеспечим фиксированную высоту списка при любом количестве элементов в нём.
Теперь мы должны получить все элементы нашего списка и назначить им обработчики.
//Получаем все элементы списка
var ulistelements = this.ulist.getElementsByTagName("li");
//Устанавливаем обработчики на каждый элемент списка
for (var i = 0; i < ulistelements.length; i++)
{
//Обработчик мыши
listenex(ulistelements[i], "mouseover", this, "handler_mouseover");
listenex(ulistelements[i], "mouseout", this, "handler_mouseout");
//Обработчик действия
listenex(ulistelements[i], "click", this, "handler_click");
//Если мы имеем дело с последним элементом списка...
if (i+1 == ulistelements.length)
{
//...то применяем небольшой CSS-fix для нашего конкретного стиля
ulistelements[i].className += " lastelement";
}
}
Готово. Осталось только установить обработчик на саму форму, к который привязан список.
listenex(this.uform, "submit", this, "handler_submitting");
Вот теперь функция Select разработана полностью и мы можем приступать к разработке дополнительных функций управления элементами списка. Мы будем хранить состояние выбора каждого элемента списка в атрибуте этого элемента и нам нужен будет слой функций для прозрачного управления данными атрибутами.
SetSelect — устанавливает состояние выбора конкретного элемента списка. Через аргументы данной функции передаётся объект элемента и логическая переменная состояния выбора (true — выбран, false — не выбран).
IsSelected — проверка наличия выбора на текущем элементе.
UnselectAll — снятие флага выбора с каждого элемента списка.
Теперь реализуем эти функции.
//Установка флага выбора на элемент
this.SetSelect = function(element, state)
{
//Установка атрибута
element.setAttribute("uselist_is_selected", state.toString());
//Установка нового значения в массиве выбранных значений (для последующей отсылки в форме)
this.SetSubmitting(element, state);
}
//Проверка флага выбора на указанном элементе
this.IsSelected = function(element)
{
if (element.getAttribute("uselist_is_selected") == null ||
element.getAttribute("uselist_is_selected") == "false")
return false;
else
return true;
}
//Отмена выбора каждого элемента
this.UnselectAll = function()
{
//Получение элементов
var ulistelements = this.ulist.getElementsByTagName("li");
for (var i = 0; i < ulistelements.length; i++)
{
//Отмена выбора
this.SetSelect(ulistelements[i], false);
ulistelements[i].className = ulistelements[i].className.replace("selected", "").replace("active", "");
this.SetSubmitting(ulistelements[i], false);
}
}
Помимо этого, понадобятся следующие функции:
//Получение элемента через событие (X-bro-функция)
this.GetElementByEvent = function(handler_event)
{
if (!handler_event) handler_event = window.event;
return handler_event.srcElement ? handler_event.srcElement : handler_event.target;
}
//Установка флага выбранности в массив для последующей отсылки с помощью форм
this.SetSubmitting = function(element, state)
{
this.usubm_values[element.value] = state;
}
Все необходимые функции реализованы, теперь приступаем к реализации обработчиков событий элементов.
//Обработчик отсылки формы
this.handler_submitting = function()
{
var submit_string = "";
//Собираем строку отсылки (разделяем переменные пробелом)
for (entry in this.usubm_values)
{
if (this.usubm_values[entry] == true)
submit_string += entry + " ";
}
//Устанавливаем значение ассоциированного элемента формы
this.usubm.value = submit_string;
}
//Стилизация — обработчик наведения мыши на элемент
this.handler_mouseover = function(e)
{
//Получаем элемент
e = this.GetElementByEvent(e);
//Проверяем флаг выбора элемента и по его результату выделяем элемент
if (!this.IsSelected(e))
e.className += " active";
}
//Стилизация — обработчик события, при котором мышь покидает элемент
this.handler_mouseout = function(e)
{
//Получаем элемент
e = this.GetElementByEvent(e);
//Проверяем флаг выбора и по его результату выделяем элемент
if (!this.IsSelected(e))
e.className = e.className.replace("active", "");
}
//Обрабатываем клик по элементу
this.handler_click = function(e)
{
//Получаем элемент
e = this.GetElementByEvent(e);
//Если это список с возожностью выбора лишь одного элемента
//Убираем выбор со всех других
if (!this.multi) this.UnselectAll();
//Если элемент не выбран...
if (!this.IsSelected(e))
{
//...устанавливаем на него выбор
this.SetSelect(e, true);
e.className += " selected";
}
else
{
//...иначе снимаем выбор с него
this.SetSelect(e, false);
e.className = e.className.replace("selected", "");
}
}
Всё готово! Попробуем применить?
var ulist1 = new UserList("place", "listtest");
ulist1.Setup();
var ulist2 = new UserList("set", "listtest");
ulist2.Setup(false, true, 150, 100);
Данный список будет отсылаться на форме, образуя примерно такой результат:

Рисунок 5