@dmitriu256

Какой алгоритм действий лучше? Как отправить POST запрос через request модуль? Как сохранять массив данных на сервере и обновлять раз в сутки?

Как поступить правильно?
Задача получить список городов компании от стороннего API (NovaPoshtaAPI) на основе полученных данных хочу сделать калькулятор с выпадающими списками - выбор городов.

Немного об API
1) API использует POST запросы от клиента
2) Выгрузка городов происходит всем справочником, нет возможности выдергивать города по одному.

Подход 1 (через AJAX запрос на клиенте без сервера)
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);

                }
                console.log(city); //получаем массив из 4000+ населенных пунктов

                //Далее вся логика(живой поиск, формирование выпадающего списка, другие действия)

            } else {
                console.log('Что то пошло не так');
            }
        });

Итог работы получаем массив городов (их более 4000 записей)
Далее организовать живой поиск на основе данного массива.
Вся дальнейшая логика (состоит из трех больших функций) будет описана внутри этого запроса.
При перезагрузке страницы опять будет загружаться сначала весь массив городов и далее логика.
Как этого избежать?
Как думаю решить:
- когда пользователь первый раз заходит на сайт проверяется наличие куки (например test),
если false - выполняется AJAX запрос, данные заносятся в localStorage и дальше живой поиск осуществляется из хранилища. (один раз загрузили данные и работаем)
- на основании данных в localStorage формируем выпадающие списки (город Отправки - город Получения)
----------------------------------------------------
Вариант2 (используя серверную часть на express js)
Используя модуль request сделать post запрос к api - получить справочник
И далее через собственный post запрос через ajax ссылаться на клиенте

app.get('/city', function(req,res){
    if (!req.body) return res.sendStatus(400);

    let cityObj = {
        "modelName": "Address",
        "calledMethod": "getCities",
        "methodProperties": {},
        "apiKey": 'apiKey'
    };
    request.post('https://api.novaposhta.ua/v2.0/json/', {form: cityObj}, function (error, response, body) {
        let data = body; //массив с городами
        res.send(data);
    });
});

1)Проблема если отсылаю таким образом - результат запроса "Data is invalid" - тк не корректно составлен запрос {form: cityObj} - как правильно отправлять post запросы в request модуле, что сделал неверно? - строку взял из документации

2) Как передать данные из этого get запроса в другой ? - если всегда при поиске выполнять этот запрос - постоянное обращение к api - выгрузка 4000 записей.

3) Как заставить такой запрос выполниться автоматически при запуске сервера - только один раз - ведь сейчас я его запускаю как бы в "ручном режиме" перейдя по адресу из запроса app.get('/city')?

Буду благодарен за помощь.
P.S. Может я все усложняю и можно намного проще, рад выслушать.

Полностью готовый вариант выбора данных из справочникам тип груза
(там всего несколько записей)
сделал вот так
let cargoType = calcForm.elements['cargo-type'];
    const cargoTypeObj = {
        "modelName": "Common",
        "calledMethod": "getCargoTypes",
        "methodProperties": {},
        "apiKey": apiKey
    };

    sendAjax(cargoType, cargoTypeObj);

function sendAjax(elem, reqObj) {
    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(reqObj));

    //проверяем состояние запроса
    xhr.addEventListener('readystatechange', function () {
        if (xhr.readyState < 4) {

        } else if (xhr.readyState === 4 && xhr.status === 200) {

            let type = JSON.parse(xhr.response);

            for(let i = 0; i < type.data.length; i++) {

                createOptions(type.data[i]);

                elem.append(createOptions(type.data[i]));
            }

            //Формируем данные в декоративном селекте
            if(cargoType.previousElementSibling.hasAttribute('data-select')){
                let select = elem.previousElementSibling.querySelector('.form-select__dropdown');

                for(let i = 0; i < type.data.length; i++) {
                    createSelectItem(type.data[i]);
                    select.append(createSelectItem(type.data[i]));
                }
            }

        } else {
            console.log('Что то пошло не так');
        }

    });
}

//Создаем пункты реального селекта ++
function createOptions(data){
    //Формируем реальный селект
    let opt = document.createElement('option');
    opt.value = data.Ref;
    opt.textContent = data.Description;

    return opt;
}

//Создаем пункты декоративного селекта ++
function createSelectItem(data) {
    let selectItem = document.createElement('div');
    selectItem.classList.add('form-select__item');
    selectItem.setAttribute('data-select-item', data.Ref);
    selectItem.textContent = data.Description;
    return selectItem;
}
  • Вопрос задан
  • 192 просмотра
Решения вопроса 1
@dmitriu256 Автор вопроса
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 городов, которые не подходят под условия поиска.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы