«Узерлист»

3 февраля 2008 года, 14:56
К данной статье привязаны следующие примеры:

Все мы прекрасно знаем как выглядят списки в (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

Мнения (1)

Все эти хорошие люди уже прокомментировали запись. Поделитесь собственным мнением, расскажите, что вы думаете о поставленной проблеме, задаче, озвученных мыслях.

  • clparenb

    25 апреля 2009 г.23:27

    Для меня оказалась очень полезная статья, в который раз СПАСИБО. Интересно было бы посмотреть реализацию списка с функцией изменения структуры на лету пользователем:перемещение на нижний/верхний уровень, вверх/вниз по структуре

Я тоже знаю!

Для обращения к человеку используйте символ @, после которого следует имя того, к кому обращаетесь (пробелы заменяются на знак подчёркивания). Если вам интересно, можете подписаться на комментарии по RSS или по эл. почте. Ведите себя достойно, вы же не роботы, правда?

Вы можете использовать следующие XHTML-элементы в разметке комментария: strong, em, span[class=crossline], a[href=uri], code[type=язык], blockquote, ul и ol. В качестве языка кода может быть указан, например, javascript или css.