Выпадающий список с полем для вывода пояснений на HTML, CSS и jQuery
Понадобился мне для взаимодействия с пользователем в браузере интерактивный элемент - выпадающий список с полем для вывода пояснений к пунктам списка, а как известно среди стандартных элементов HTML такого нет. Сказано - сделано, привожу код самописного аналога списка select с информационной областью под кодовым названием ВСИ (выпадающий список информационный). Элемент ВСИ является строчным, также как и стандартный select, то есть два элемента расположенных рядом встанут в одну строку.
Слева стандартный select, а справа три элемента ВСИ: в один столбец, в один столбец с полосой прокрутки и в два столбца.
select: ВСИ:В своём селекте я постарался сохранить внешний вид стандартного элемента, хотя конечно его можно настроить под себя как угодно с помощью css-стилей.
Для возможности отправки выбранного в списке значения методом POST, ВСИ динамически создаёт скрытое (hidden) поле (input), имя которого задано идентификатором, содержащимся в атрибуте data-n.
Пояснение к пункту списка хранится в его атрибуте data-o, в атрибуте data-v хранится значение, элемент с атрибутом data-s="select" будет выбран по умолчанию при загрузке, если data-s="select" не содержится ни в одном из пунктов списка, то по умолчанию будет выбран первый пункт. ВСИ умеет выводить пункты в несколько столбцов, задать количество столбцов можно атрибутом data-b. Элемент нормально отображается в браузере смартфона. Вот вроде бы и всё, далее сами коды:
HTML-код:
<div data-n="sel2" data-b="1" class="selcont"><div class="sel">
<div data-o="парнокопытное животное" data-v="1">Лошадь</div>
<div data-s="select" data-o="птица семейства синицевых" data-v="2">Синица</div>
<div data-o="промысловая рыба" data-v="3">Карп</div>
</div></div>
CSS-код:
.selcont {line-height:1; position:relative; display:inline-block; vertical-align:baseline; background:#fff; font-family:Arial; font-size:10pt; color:#000; text-shadow:none;}
.sel {position:relative; display:inline-block; padding:2px 4px; border:1px solid #aaa; cursor:default; user-select: none; margin:0;}
#stre {line-height:1; position:absolute; right:4px; top:4px; font-size:9px; margin:0px; padding:0px;}
.seld {position:absolute; left:0px; display:none; border:1px solid #7a9cd3; cursor:default; background:#fff; z-index:100;}
.seld div:hover {color:#fff; background:#1e90ff;}
.seld div {padding:2px 4px; user-select:none;}
.sinf {display:none; position:absolute; top:0px; width:150px; border:1px solid #7a9cd3; margin:0px; padding:3px 5px; background:#fff; z-index:100; color:#444; font-size:9pt; line-height:1.4;}
jQuery-код:
// Выпадающий список информационный (ВСИ)
// инициализировать все ВСИ на странице
$('.selcont').each(function() {
$("<p>", {id: "stre", html: "▼"}).prependTo($(this).children(".sel"));
$("<br>", {}).appendTo(this); // добавить br перенос строки
$("<div>", {class: "seld"}).appendTo(this); // добавить выпадающий список
$("<p>", {class: "sinf"}).appendTo(this); // добавить информационную область
dname = $(this).attr("data-n"); // определить имя для hidden поля
$("<input>", {type: "hidden", name: dname, value: ""}).appendTo(this); // добавить input для передачи данных POST-ом
mid = 'div[data-n="' + $(this).attr("data-n") + '"]'; // data-n текущего ВСИ
max = my_max(mid + ' .sel div')+9; // вернуть ширину самого широкого элемента в наборе + padding справа + padding слева + 1
ks = $(mid).attr("data-b"); // data-b текущего ВСИ (количество столбцов)
if (!!ks === false) {ks = 1;} // если не задан, то 1 столбец
nmax = max*ks; // размеры ВСИ в зависимости от заданного в нём кол-ва столбцов
kke = $(mid + ' .sel div').length; // общее количество пунктов в списке
if (kke/ks > 15) {ppr = 19;} else {ppr = 5;} // если пунктов больше 15 в столбце, то дать место для вертикальной полосы прокрутки
$(mid + ' .sel').css("width", max+ppr); // задать ширину всегда видимой части ВСИ
$(mid + ' .sinf').css("left", nmax+ppr+9); // задать левый отступ информационной области
sel_zn(mid); // показать только выбранный пункт в всегда видимой части ВСИ
$(mid + ' .seld').css({"width": nmax+ppr+8, "top": $(mid).height()}); // задать ширину и top выпадающего списка
});
// клик по всегда видимой части ВСИ
$("body").on("click", ".selcont .sel", function () {
mi = 'div[data-n="' + $(this).parent(".selcont").attr("data-n") + '"]'; // data-n текущего ВСИ
$('.sel').css({"box-shadow": "none", "border-color": "#aaa"}); // скрыть подсветку фокуса всегда видимой части всех ВСИ
if ($(mi + ' .seld').css("display")=="none") { // если выпадающий список не виден
$(mi + ' .sinf').html( $(mi + ' .sel div[data-v="' + $(mi + ' input').val() + '"]').attr("data-o") ); // вывод подсказки выбранного пункта в информационную область ВСИ
$(mi + ' .sel').css({"box-shadow": "0 0 1px #005fff", "border-color": "#6687be"}); // показать подсветку фокуса всегда видимой части ВСИ
$('.seld, .sinf').css("display","none"); // скрыть все выпадающие списки и инф области на странице
$(mi + ' .seld').html(""); // очистить выпадающий список от содержимого
$(mi + ' .sel div').clone().appendTo(".seld"); // клонировать пункты в выпадающий список
$(mi + ' .seld div').css("display","block"); // сделать пункты в выпадающем списке видимыми
$(mi + ' .seld').css("display","inline-block"); // сделать видимым сам выпадающий список
$(mi + ' .sinf').css("display","block"); // сделать видимой инф область
$(mi + ' .sinf').css("top", $(mi + ' .seld').position().top); // задать top инф области
ks = $(mi).attr("data-b"); // data-b текущего ВСИ (количество столбцов)
var shg = $(mi + ' .seld div').outerHeight(); // высота одного пункта выпадающего списка
if (!!ks === true) { // если data-b задан (несколько столбцов)
var kkol = Math.ceil($(mi + ' .sel div').length/ks); // вернёт количество пунктов для одной колонки
kke = $(mi + ' .sel div').length; // общее количество пунктов в списке
if (kke/ks > 15) {ppr = 19;} else {ppr = 0;} // если пунктов больше 15 в столбце, то дать место для вертикальной полосы прокрутки
var lf = Math.ceil(($(mi + ' .seld').outerWidth()-ppr)/ks); // отступ слева для поколоночного вывода пунктов выпадающего списка
var pad = parseInt($(mi + ' .seld div').css("padding-left")); // внутренний отступ пункта выпадающего списка
var tp = 0; // здесь хранится нарастающий top для пунктов выпадающего списка
nk = 1;
$(mi + ' .seld div').each(function(i) { // распределить пункты списка по колонкам
if (nk == 1) { // если это первая колонка
$(this).attr("style", ""); // удалит все css-свойства, содержащиеся в атрибуте style у элемента
$(this).css({"width":lf-pad*2});
} else { // если это не первая колонка
$(this).css({"position":"absolute", "top": tp + "px", "left": lft + "px", "width":lf-pad*2});
tp = tp + shg;
}
if ((i+1) % kkol == 0) {nk = nk + 1; tp = 0; lft = lf * (nk - 1);} // если это был последний элемент в текущем столбце
});
}
pdd = parseInt($(mi + ' .sinf').css("padding-top"))*2;
vseld = $(mi + ' .seld').height(); // высота выпадающего списка
if (shg*15 < vseld) { // если высота выпадающего списка больше высоты 15 пунктов
$(mi + ' .seld').css({"height":shg*15, "overflow":"auto"}); // поставить полосу прокрутки на выпадающий список
$(mi + ' .sinf').height(shg*15-pdd); // задать высоту инф области в 15 пунктов
} else {
$(mi + ' .sinf').height(vseld-pdd); // задать высоту инф области по высоте выпадающего списка
}
} else { // если выпадающий список виден
$(mi + ' .sel').css({"box-shadow": "none", "border-color": "#aaa"}); // скрыть подсветку фокуса всегда видимой части ВСИ
$('.seld, .sinf').css("display","none"); // скрыть все выпадающие списки и инф области на странице
}
$(mi + ' .seld div[data-v=' + $(mi + ' input').val() + ']').css({"color":"#fff", "background":"#1e90ff"}); // подсветить выбранный пункт в списке
});
// клик по месту вне любого из ВСИ на странице
$(document).click(function(event) {
if ($(event.target).closest('.selcont').length) return;
$('.sel').css({"box-shadow": "none", "border-color": "#aaa"}); // скрыть подсветку фокуса всегда видимой части всех ВСИ
$('.seld, .sinf').css("display","none"); // скрыть все выпадающие списки и инф области на странице
event.stopPropagation();
});
// возвращает ширину самого широкого элемента в наборе
function my_max(mm) {
var A = $(mm), max = 0, elem;
A.each(function () {
if (this.offsetWidth > max)
max = this.offsetWidth, elem = this;
});
return max;
}
// показать только выбранный пункт в всегда видимой части ВСИ
function sel_zn(mid) {
zzn = +$(mid + ' input').val();
sel1 = mid + ' .sel';
if (zzn == 0) { // если пункт еще не выбирался
vid = $(sel1 + " div[data-s='select']"); // пункт с data-s='select'
per = $(sel1 + " div:first"); // первый пункт в наборе
$(sel1 + " div[data-s!='select']").css("display","none"); // скрыть все пункты без data-s='select'
if (vid.length == 0) { // если в наборе нет пункта с data-s='select'
per.css("display","inline-block"); // показать первый пункт в всегда видимой части ВСИ
dv = per.attr("data-v"); // запомнить его значение id
} else { // если в наборе есть пункт с data-s='select'
vid.css("display","inline-block");// показать пункт с data-s='select' в всегда видимой части ВСИ
dv = vid.attr("data-v"); // запомнить его значение id
vid.removeAttr("data-s"); // удалить у элемента пункта атрибут data-s='select'
}
$(mid + ' input').val(dv); // запомнить id выбранного пункта в hidden
} else { // если пункт выбирался
$(sel1 + ' div').each(function() { // показать только выбранный элемент в всегда видимой части ВСИ
aa = $(this).attr("data-v");
if (aa == zzn) {$(this).css("display","inline-block");} // показать выбранный пункт
else {$(this).css("display","none");} // скрыть все невыбранные пункты
});
}
}
// клик по пункту выпадающего меню
$("body").on("click", ".selcont .seld div", function () {
mid = 'div[data-n="' + $(this).parents(".selcont").attr("data-n") + '"]'; // data-n текущего ВСИ
$(mid + ' input').val($(this).attr("data-v")); // запомнить id выбранного пункта в hidden
sel_zn(mid); // показать только выбранный пункт в всегда видимой части ВСИ
$('.seld, .sinf').css("display","none"); // скрыть все выпадающие списки и инф области на странице
});
// наведение мыши на пункт выпадающего меню
var he; // здесь хранится пункт списка, над которым находится курсор мыши
$("body").on("mouseover", ".selcont .seld div", function () {
$(mi + ' .seld div').css({"color":"", "background":""}); // убрать подсветку выбранного пункта в списке
mi = 'div[data-n="' + $(this).parents(".selcont").attr("data-n") + '"]'; // data-n текущего ВСИ
$(mi + ' .sinf').html($(this).attr("data-o")); // вывод подсказки к пункту в информационную область ВСИ
he = $(this); // запомним пункт списка, над которым находится курсор мыши
});
// выход мыши за пределы выпадающего меню
$("body").on("mouseleave", ".selcont .seld", function () {
he.css({"color":"#fff", "background":"#1e90ff"}); // подсветить пункт списка, над которым курсор мыши был последний раз
});
Поделиться статьей: