Доброго времени, заранее извиняюсь за многословность.
Не так давно попалось тестовое задание реализовать автокомплит списка городов.
Из функционала:
- При вводе первого символа список фильтруется. Первый пункт в списке подсвечивается. Оптимальное количество строк в списке — 5. Если после фильтрации список может быть очень большим, это число можно увеличивать до 20.
- Если обработка запроса для построения списка занимает дольше 0,5 секунды, показываем лоадер минимум 1 секунду. Если список уже открыт, и при получении ответа возникла задержка — показываем лоадер, список до обновления остается в прежнем состоянии.
- Если происходит ошибка сервера, показываем сообщение об ошибке и кнопку обновить.
- Если для введенного нет совпадений, показываем соответствующий тест.
- Потеря фокуса без выбора пункта из списка
Если ничего не найдено, показываем ошибку
- Если список не успел загрузиться, показываем ошибку
- Если введенное значение посимвольно совпадает со значением из справочника, при потере фокуса выбираем его
- При получении фокуса введенное значение выделяется
Так же некоторые вещи не совсем так или вообще не сделал и мне теперь стало интересно как можно было бы это реализовать.
Во 2 пункте прелоадер показывается только до полной загрузки данных и убирается после загрузки данных.
В 7 пункте я не могу понять как сравнить сразу данные из массива и полное слово.
И интересно как бы сделать активный класс при нажатии на результат т.к очищать класс до добавления класса мне не удалось.
Хотелось бы услышать советы более опытных программистов по поводу улучшения кода.
codepen
let homeworkContainer = document.querySelector('#homework-container'),
filterBlock = document.getElementById('filter-block'),
filterInput = homeworkContainer.querySelector('#filter-input'),
filterResult = homeworkContainer.querySelector('#filter-result'),
filterList = homeworkContainer.querySelector('.filter-list'),
info = homeworkContainer.querySelector('.info-block'),
reoladBtn = document.createElement('div'),
filtrFocusOut;
function createCORSRequest(method, url) {
let xhr = new XMLHttpRequest();
if ('withCredentials' in xhr) {
// XHR for Chrome/Firefox/Opera/Safari.
xhr.open(method, url, true);
} else if (typeof XDomainRequest != 'undefined') {
// XDomainRequest for IE.
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
// CORS not supported.
xhr = null;
}
return xhr;
}
function loadAndSortTowns() {
return new Promise(function(resolve) {
let url = './data/kladr.json',
preloader = document.createElement('div'),
xhr = createCORSRequest('GET', url);
preloader.className = 'input__preloader';
if (!xhr) {
alert('CORS not supported');
return;
}
xhr.responseType = 'json';
filtrFocusOut = true;
filterBlock.appendChild(preloader);
xhr.onload = function() {
let resp = xhr.response;
preloader.remove();
filtrFocusOut = false;
if (xhr.status === 200 && xhr.readyState === 4) {
resp.sort(function(a, b) {
if (a.name > b.name) {
return 1
}
return -1
});
resolve(resp);
} else {
reoladBtn.innerHTML = 'Обновить';
reoladBtn.className += 'reolad-btn';
filterList.appendChild(reoladBtn);
filterResult.innerHTML = 'Что то пошло не так. Проверьте соединение с интернетом и попробуйте еще раз'
}
};
xhr.send();
})
}
function checkFilterStatus (array, count) {
if (array.length === 0) {
info.innerHTML = 'Не чего не найдено';
filtrFocusOut = true;
} else {
filtrFocusOut = false;
filterInput.parentNode.classList.remove('filter-erore');
if (count > 1) {
info.innerHTML = 'Показано ' + count + ' из ' + array.length + ' найденных городов. Уточните запрос, чтобы увидеть остальные'
} else {
info.innerHTML = ''
}
}
}
function isMatching(full, chunk) {
full = full.toLowerCase();
chunk = chunk.toLowerCase();
if (full.substr(0, chunk.length) === chunk) {
return true;
}
return false;
}
filterInput.oninput = function(e) {
e.target.parentNode.classList.remove('filter-erore');
loadAndSortTowns().then(function (data) {
let value = e.target.value.trim(),
htmlText = '',
checkArray = [],
count = 0;
if (value.length > 0) {
filterList.classList.add('is--show');
for (let i= 0; i<data.length; i++) {
if (isMatching(data[i].City, value)) {
checkArray.push(data[i].City);
if (checkArray.length <= 5) {
count++;
htmlText += '<div id=' + data[i].Id + ' ' + 'class="result__item">' + data[i].City + '</div>';
}
} else {
checkFilterStatus(checkArray, count, htmlText)
}
}
} else {
filterList.classList.remove('is--show');
info.innerHTML = '';
}
filterResult.innerHTML = htmlText;
});
};
filterList.onclick = function (e) {
if ( e.target.className.indexOf('result__item') === 0) {
filterList.classList.remove('is--show');
filterInput.value = e.target.innerHTML;
}
};
filterInput.onchange = function (e) {
if (filtrFocusOut) {
e.target.parentNode.classList.add('filter-erore');
info.innerHTML = 'Выберите значение из списка';
}
};
filterInput.onfocus = function(e) {
e.target.setSelectionRange(0, this.value.length);
};
reoladBtn.onclick = function () {
loadAndSortTowns();
};