Kovalsky, исходя из описанного алгоритма действий получилось следующее
Вариант1 (поиск городов осуществляется на сервере с отдачей результата клиенту)
Серверная часть (express js + nodejs)
//используем этот запрос для живого поиска
app.post('/city', function(req,res){
let text = req.body.text;
let val = text.trim().toLowerCase(); // приходит от пользователя
console.log(val);
let city = arr.filter(el => {
if(val){
return el.Description.toLowerCase().search(val) !== -1;
}
});
res.send(city); // возвращаем массив с данными иначе пустой массив
});
app.listen(PORT, function(){
console.log(`Прослушиваем порт по адресу ${PORT}`);
//Формируем справочник городов компании
let cityObj = {
"modelName": "Address",
"calledMethod": "getCities",
"methodProperties": {},
"apiKey": apiKey
};
request.post(
'https://api.novaposhta.ua/v2.0/json/',
{
json: cityObj,
headers: {
"Content-type": "application/json",
}
},
function (error, response, body) {
let data = body;
for(let i = 0; i < data.data.length; i++) {
arr.push(data.data[i]);
console.log(arr[i]);
}
});
});
Клиентская часть обработки
let city = calcForm.elements['cargo-city-to'];
if(city.previousElementSibling.hasAttribute('data-select')){
let el = city.previousElementSibling.querySelector('[data-select-title]');
el.setAttribute('contenteditable', true);
el.addEventListener('focus', function(){
this.textContent = '';
});
el.addEventListener('input', function(){
//Очистка декоративных пунктов
let selected = city.previousElementSibling.querySelectorAll('[data-select-item]');
selected.forEach(el => {
console.log(el.textContent);
el.remove();
});
//очистка стандартного селекта
for(let i = 0; i < city.options.length; i++){
city.remove(i);
}
//Запрос живого поиска
let xhr = new XMLHttpRequest();
xhr.open('POST', 'http://localhost:3010/city');
xhr.setRequestHeader('Content-type', 'application/json; charset = utf-8');
//Отправка данных на сервер
xhr.send(JSON.stringify({text: this.textContent}));
console.log(this.textContent);
//проверяем состояние запроса
xhr.addEventListener('readystatechange', function () {
if (xhr.readyState < 4) {
} else if (xhr.readyState === 4 && xhr.status === 200) {
let data = JSON.parse(xhr.response);
for(let i = 0; i < data.length; i++) {
//Формируем список опций у реального селекта
createOptions(data[i]);
city.append(createOptions(data[i]));
}
//Формируем данные в декоративном селекте
if(city.previousElementSibling.hasAttribute('data-select')){
let select = city.previousElementSibling.querySelector('.form-select__dropdown');
for(let i = 0; i < data.length; i++) {
createSelectItem(data[i]);
select.append(createSelectItem(data[i]));
}
}
} else {
console.log('Что то пошло не так');
}
});
});
}else{
console.log('---');
}
getCity(city, `http://localhost:3010/city`);
//Изначально загружаем с сервера 5 записей из массива городов (что бы декоративный селект имел некоторый список по умолчанию)
function getCity(elem, url){
let xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.setRequestHeader('Content-type', 'application/json; charset = utf-8');
//Отправка данных на сервер
xhr.send();
//проверяем состояние запроса
xhr.addEventListener('readystatechange', function () {
if (xhr.readyState < 4) {
} else if (xhr.readyState === 4 && xhr.status === 200) {
let data = JSON.parse(xhr.response);
for(let i = 0; i < 5; i++) {
createOptions(data[i]);
elem.append(createOptions(data[i]));
}
//Формируем данные в декоративном селекте
if(elem.previousElementSibling.hasAttribute('data-select')){
let select = elem.previousElementSibling.querySelector('.form-select__dropdown');
for(let i = 0; i < 5; i++) {
createSelectItem(data[i]);
select.append(createSelectItem(data[i]));
}
}
} else {
console.log('Что то пошло не так');
}
});
}
Вариант2 (когда вся логика происходит на клиенте, используем API напрямую, города храним в localStorage)
//Получение городов компании
let cityTo = calcForm.elements['cargo-city-to'];
let cityFrom = calcForm.elements['cargo-city'];
checkKeyCity('Mycity', cityTo);
checkKeyCity('Mycity', cityFrom);
//Получение куки
function getCookie(name) {
let matches = document.cookie.match(new RegExp(
"(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)"
));
return matches ? decodeURIComponent(matches[1]) : undefined;
}
//Загрузка городов в localStorage
function getCities(){
let date = new Date();
date = new Date(date.setDate(date.getDate() + 1));
let cityObj = {
"modelName": "Address",
"calledMethod": "getCities",
"methodProperties": {},
"apiKey": apiKey
};
let xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.novaposhta.ua/v2.0/json/');
xhr.setRequestHeader('Content-type', 'application/json; charset = utf-8');
//Отправка данных на сервер
xhr.send(JSON.stringify(cityObj));
//проверяем состояние запроса
xhr.addEventListener('readystatechange', function () {
if (xhr.readyState < 4) {
} else if (xhr.readyState === 4 && xhr.status === 200) {
let city = [];
let obj = {};
let cities = JSON.parse(xhr.response);
for (let i = 0; i < cities.data.length; i++) {
obj = {
Ref: cities.data[i].Ref,
Description: cities.data[i].Description
};
//Сформировали справочник населенных пунктов
city.push(obj);
}
localStorage.setItem('Mycity', JSON.stringify(city));
} else {
console.log('Что то пошло не так');
}
});
document.cookie = `${decodeURI('cities')} = ${decodeURI(true)}; expires = ${date}; path = /`;
}
//Проверка наличие куки
if(getCookie('cities') === undefined) {
getCities();
}
//Проверка наличия ключа в localStorage
function checkKeyCity(key, element){
if (localStorage.getItem(key) !== null){
let data = JSON.parse(localStorage.getItem(key));
//Формируем данные в селекте
for(let i = 0; i < data.length; i++) {
createOptions(data[i]);
element.append(createOptions(data[i]));
}
//Формируем данные в декоративном селекте
if(element.previousElementSibling.hasAttribute('data-select')){
let select = element.previousElementSibling.querySelector('.form-select__dropdown');
select.overflowY = 'scroll';
for(let i = 0; i < data.length; i++) {
createSelectItem(data[i]);
select.append(createSelectItem(data[i]));
}
}
let el = element.previousElementSibling.querySelector('[data-select-title]');
el.setAttribute('contenteditable', true);
el.addEventListener('focus', function(){
this.textContent = '';
});
//Живой поиск ++
el.addEventListener('input', function(){
element.previousElementSibling.querySelector('.form-select__dropdown').classList.remove('hidden');
let val = this.textContent.trim().toLowerCase();
let items = element.previousElementSibling.querySelectorAll('.form-select__item');
if(val != ''){
items.forEach(function(elem) {
if(elem.textContent.toLowerCase().search(val) == -1) {
elem.classList.add('hidden');
}else{
elem.classList.remove('hidden');
}
});
}else{
items.forEach(function(elem) {
elem.classList.remove('hidden');
});
}
});
}
}
Итог
Остановился на Варианте2 - скорость поиска быстрее, тк один раз загружаем города в хранилище в дальнейшем постоянно обращаемся к нему, без использования ресурсов сервера.
-Построение DOM дерева происходит один раз
- живой поиск осуществляется простым добавления класса hidden городов, которые не подходят под условия поиска.