Сегодня сделаем функциональный combobox с умным поиском. На выходе получим нечто такое

Умный combobox делаем на jquery

Одной из главных фич, будет то, что поиск будет вестись не исключением, а сортировкой. При этом, сортировать будем по умному, а не простым сравнением. 

Обычно, при создании подобных плагинов, делают проверку на совпадение подстрок и тогда возвращают результат. У нас будет плагин, который на слово kazam, вернет в первой строчке KAZAN. 

Приступим. 

Для начала, настроим внешний вид. Создадим структуру

<div class="combobox">
	<input autocomplete="off" id="search" placeholder="Быстрый поиск" type="text">
	<div class="cities">
		<div class="wrapper">
		 <div class="cell">
			<a href="#moscow">Moscow</a>
			<!--список городов-->
		 </div>
		 <div class="cell"><!--список городов--></div>
		 <div class="cell"><!--список городов--></div>
		 <div class="clearfix"></div>
		</div>
	</div>
	<div class="helper"></div>
</div>

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

<script>
var regions = {
 moscow:'Moscow',
 spb:'Sankt-Petersburg',
 ...
};
</script>

Плагин без стилей выглядит ужасно. Даже не стоит на него смотреть. Лучше сразу добавим нужный css

.combobox{
	border:5px solid #a8a7a7;
	border-radius:5px;
	background: rgb(253,253,253); /* Old browsers */
	/* IE9 SVG, needs conditional override of 'filter' to 'none' */
	background: -moz-linear-gradient(top, rgba(253,253,253,0.7) 0%, rgba(252,250,230,0.7) 100%); /* FF3.6+ */
	background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(253,253,253,0.7)), 
color-stop(100%,rgba(252,250,230,0.7))); /* Chrome,Safari4+ */
	background: -webkit-linear-gradient(top, rgba(253,253,253,0.7) 0%,rgba(252,250,230,0.7) 100%); /* Chrome10+,Safari5.1+ */
	background: -o-linear-gradient(top, rgba(253,253,253,0.7) 0%,rgba(252,250,230,0.7) 100%); /* Opera 11.10+ */
	background: -ms-linear-gradient(top, rgba(253,253,253,0.7) 0%,rgba(252,250,230,0.7) 100%); /* IE10+ */
	background: linear-gradient(to bottom, rgba(253,253,253,0.7) 0%,rgba(252,250,230,0.7) 100%); /* W3C */
	filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fdfdfd', endColorstr='#fcfae6',GradientType=0 ); /* IE6-8 */
	position:relative;
	padding:15px;
	margin-top:63px;
	max-height:357px;
	
}
.combobox .cities{
	overflow:hidden;
	max-height:280px;
	position:relative;
	margin:10px 0px;
}
.combobox a{
font-size:13.5pt;
color:#3c3b36;
text-decoration:none;
}
.combobox >div>div>div:first-child>a:first-child{
	font-size:15pt;
	font-family:Helvetica, Arial, sans-serif;
	text-transform:uppercase;
}
.combobox a:hover{
text-decoration:underline;
}
.combobox input{
display: block;
width: 100%;
padding: 8px 10px 8px 40px;
border-radius: 6px;
border: 1px solid #d8d8d8;
font-size: 13.5pt;
background:url(images/search.png) no-repeat 10px center;
background-color:#fff;
-webkit-box-shadow: inset 1px 0px 12px -5px rgba(50, 50, 50, 0.78);
-moz-box-shadow:    inset 1px 0px 12px -5px rgba(50, 50, 50, 0.78);
box-shadow:         inset 1px 0px 12px -5px rgba(50, 50, 50, 0.78);
outline:0 !important;
-webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
-o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
}
.combobox input:focus{
 border-color: #ccc;
  outline: 0;
  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),inset 0 0 8px rgba(102, 175, 233, .6);
  box-shadow: inset 0 1px 1px rgba(0,0,0,.075),inset 0 0 8px rgba(102, 175, 233, .6);
}

.combobox .helper{
	position:absolute;
	bottom:0px;
	left:0px;
	right:0px;
	height:50px;
	background: rgb(252,250,229);
	background: -moz-linear-gradient(top,  rgba(252,250,229,0.1) 0%, rgba(252,250,230,1) 100%);
	background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(252,250,229,0.1)), 
color-stop(100%,rgba(252,250,230,1)));
	background: -webkit-linear-gradient(top,  rgba(252,250,229,0.1) 0%,rgba(252,250,230,1) 100%);
	background: -o-linear-gradient(top,  rgba(252,250,229,0.1) 0%,rgba(252,250,230,1) 100%); 
	background: -ms-linear-gradient(top,  rgba(252,250,229,0.1) 0%,rgba(252,250,230,1) 100%); 
	background: linear-gradient(to bottom,  rgba(252,250,229,0.1) 0%,rgba(252,250,230,1) 100%);
	filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fcfae5', endColorstr='#fcfae6',GradientType=0 );
}
.combobox .cell{
width:33%;
padding-bottom:50px;
}
.clearfix{
clear:both;
float:none;
}
.combobox .cell > a{
	display:block;
	margin-bottom:10px;
}

.xdsoft_scrollbar .xdsoft_scroller{
	background:#d8d8d8;
	border-radius:4px;
	cursor:pointer;
}
.xdsoft_scrollbar{
	width:8px;
	position:absolute;
	top:10px;
	right:2px;
	bottom:10px;
	background:#fff;
	border:1px solid #d8d8d8;
	border-radius:4px;
	z-index:6;
}

Изображение search.png, вы найдете в архиве, в конце статьи. Все градиенты сделаны с использованием онлайн css генераторов. Часть стилей просто взята с bootsrap. Этот фреймворк, просто кладезь css сниппетов. 

Пара последних селекторов стоит отдельного упоминания. Это красивый скролбар. Куда без него. Для его создания воспользуемся готовым плагином xdsoftScroller. Чтобы scrollbar, помимо указателя мыши, реагировал еще и на колесико, подключим плагин mousewheel не безызвестного Брендона Аарона.

Плагин принял свой окончательный облик, пришло время его оживить. 

Плагин уже позволяет найти нужный город с помощью прокрутки. Но для большего удобства у нас предусмотрен поисковый механизм. Напишем его.

$('#search').on('keydown',function(){
	var that = this;
	timer1 = clearTimeout(timer1);
	setTimeout(function(){
		updateCombobox(that.value)
	},10);
});

Такой прием используют повсеместно. Если пользователь вводит слово, нет смысла искать то что он вводит при каждой букве. Верным решением будет подождать пока он напечатает пару тройку символов, затем произвести свои манипуляции. В моих примерах плагин оперирует всего 50-тью элементами. А что если их будет 50000? Создание сайтов и плагинов должно подразумевать, что в какой-то момент количество пользователей или информации может вырасти. Если запускать фильтр при каждом нажатии клавиши, пользователь свое слово скорее всего уже не допишет. Все будет тормозить. Напишем функцию updateCombobox(), которая на вход получает строку из поля ввода.

function updateCombobox( value ){
	value = value.toLowerCase().replace(/[\s]/g,'');
	if( value!='' ){
		for(var i in regions){
			regions[i].similar = string_similarity(regions[i].toLowerCase(),value)
	}
	regions.sort(function(a,b){
		if( value=='' ){
			return a>b?1:-1;
		}else{
			return b.similar>a.similar?1:-1;
		}
	})
	var box = '',res ='',$region, cnt = Math.round(regions.length/3);
	for(var i in regions){
		$region = regions[i];
		box+='<a href="#'+i+'" title="'+$region+'">'+$region+'</a>';
		if( (parseInt(i)+1)%cnt==0 ){
			res+='<div class="cell">'+box+'</div>';
			box = '';
		}
	}
	if( box ){
		res+='<div class="cell">'+box+'</div>';
		box = '';
	}
	$('.combobox .cities').empty().removeClass('xdsoft_scroller_box');
	$('.combobox .cities').html('<div class="wrapper">'+res+'<div class="clearfix"></div></div>').xdsoftScroller(0);
}

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

Загадочная функция string_similarity найдена на просторах интернета

function get_bigrams(string) {
    // Takes a string and returns a list of bigrams
    var s = string.toLowerCase();
    var v = new Array(s.length-1);
    for (i = 0; i< v.length; i++){
        v[i] =s.slice(i,i+2);
    }
    return v;
}

function string_similarity(str1, str2){
    /*
    Perform bigram comparison between two strings
    and return a percentage match in decimal form
    */
    var pairs1 = get_bigrams(str1);
    var pairs2 = get_bigrams(str2);
    var union = pairs1.length + pairs2.length;
    var hit_count = 0;
    for (x in pairs1){
        for (y in pairs2){
            if (pairs1[x] == pairs2[y]){
                hit_count++;
            }
        }
    }
    return ((2.0 * hit_count) / union);
}

Для того, чтобы плагин сделать еще более удобным можно воспользоваться моим Punto Switcher на JS и тогда похожесть надо будет искать слегка иначе

var tvalue = switcher(value,1);
if( value!='' ){
	for(var i in regions){
		regions[i].similar = 
			Math.max(
				string_similarity(regions[i].toLowerCase(),value),
				string_similarity(regions[i].toLowerCase(),tvalue)
			);
	}
}

Желаю удачи, успехов вам

Оставлять комментарии могут только зарегистрированные пользователи

Комментарии  

Nemzida
# Nemzida 15.08.2014 11:16
Не работает
murzer
# murzer 23.09.2014 22:46
Спасибо за статью. У меня тоже стоит дома стиральная машина. Когда надо постирать вещи, например - рубашку, брюки, платок, носки, трусики, майка, часы, гвоздик, орех, стакан, арбуз и кокос, я открываю крышку машины и кладу все во внутрь. Через час примерно слышу сигнал о завершении процесса мойки.

Я доволен и все хорошо.

С добрым утром и с новым годом вас друзья!
Leroy
# Leroy 24.09.2014 11:55
что?