Выпадающий список с полем для вывода пояснений на HTML, CSS и jQuery

Понадобился мне для взаимодействия с пользователем в браузере интерактивный элемент — выпадающий список с полем для вывода пояснений к пунктам списка, а как известно среди стандартных элементов HTML такого нет. Сказано — сделано, привожу код самописного аналога списка select с информационной областью под кодовым названием ВСИ (выпадающий список информационный). Элемент ВСИ является строчным, также как и стандартный select, то есть два элемента расположенных рядом встанут в одну строку.

Слева стандартный select, а справа три элемента ВСИ: в один столбец, в один столбец с полосой прокрутки и в два столбца.

select:  ВСИ: 
Лошадь
Синица
Карп
Люцерна
Смородина
Тик
Сосна4
Сосна5
Сосна6
Сосна7
Сосна8
Сосна9
Сосна10
Сосна11
Сосна12
Сосна13
Сосна14
Сосна15
Сосна16
Сосна17
Сосна18
Сосна19
Люцерна
Смородина
Тик
Сосна4
Сосна5
Сосна6
Сосна7
Сосна8
Сосна9
Сосна10
Сосна11
Сосна12
Сосна13
Сосна14
Сосна15
Сосна16
Сосна17
Сосна18
Сосна19
 

В своём селекте я постарался сохранить внешний вид стандартного элемента, хотя конечно его можно настроить под себя как угодно с помощью 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: "&#9660;"}).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"}); // подсветить пункт списка, над которым курсор мыши был последний раз
});

Поделиться статьей:  

Поделитесь своим мнением

Правила сообщений

Для оформления сообщений Вы можете использовать следующие тэги:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Копирование материалов разрешено только с указанием источника и прямой индексируемой ссылкой на оригинал.
Яндекс.Метрика
© 2018 Хороший отзыв · Искусство выбирать · Лично · Разумно · Честно