Как улучшить мой живой поиск в AJAX, выполняя меньшее количество запросов - PullRequest
0 голосов
/ 03 декабря 2018

Я создаю страницу живого поиска AJAX. Пока что все работает, как задумано, но я заметил, что я делаю тонны вызовов AJAX.

Я знаю, где и почему это происходит, но я не могу найти способ остановить эти вызовы AJAX.

Я постараюсь дать быстрое объяснение и вставить нижеприведенный код.

В левой части страницы у меня есть несколько фильтров (например, местоположение, радиус, интересы, обязательства, диапазон дат, ...).У каждого из них есть прослушиватель событий (например, измененный).Когда срабатывает один из этих слушателей, я обновляю параметры запроса и запрашиваю результаты через AJAX.

Теперь проблема возникает, когда, например, кто-то выбрал 10 интересов (их 36), а затем он разделяет URL, это будет выглядеть так:

http://localhost/codeigniter/nl-be/sociale-teambuildings/zoeken?locations=&distance=0&minemployees=0&maxemployees=1000&minprice=0&maxprice=50000&interests=1%3B2%3B3%3B4%3B5%3B6%3B7%3B8%3B9%3B10&sdgs=&startdate=2018-12-03&enddate=2019-12-03&engagements=

Теперь, во-первых, я скажу, что я использую iCheck.js для своих флажков.Это означает, что есть проверка на событие ifChecked.Поскольку список содержит 10 записей о процентах, событие ifChecked будет вызвано 10 раз, что приведет к 10 запросам AJAX.Теперь рассмотрим это в сочетании с 5 SDG, 2 engagaments, 3 местоположения, ... и я получаю тонну запросов AJAX.Кроме того, при одновременном удалении всех моих интересов (есть ссылка «удалить»), он вызовет событие ifUnchecked 10 раз и, таким образом, снова выполнит 10 запросов AJAX.

Это мой полный JS-код, я пытался создать jsfiddle с HTML и всем, но код немного смешан с каркасом CodeIgniter и его трудно разместить там.Но JS-кода достаточно, чтобы получить картину.

//Set vars available to entire scope for filtering
var searchLocation = null;
var searchRadius = 0;
var searchMin = 0;
var searchMax = 1000;
var searchMinPrice = 0;
var searchMaxPrice = 50000;
var searchSelectedInterests = [];
var searchSelectedSdgs = [];
var searchStartDate = moment().format('YYYY-MM-DD');
var searchEndDate = moment().add(1, 'years').format('YYYY-MM-DD');
var searchSelectedEngagements = [];

var getUrl = window.location;
var baseUrl = getUrl .protocol + "//" + getUrl.host + "/" + getUrl.pathname.split('/')[1];

$(document).ready(function(){
    'use strict';

    // Square Checkbox & Radio
    $('.skin-square input').iCheck({
        checkboxClass: 'icheckbox_square-blue'
    });
    $('.searchinterests input').on('ifChecked', function(event) {
        var interestid = event.target.value;
        searchSelectedInterests.push(interestid);
        updateURLParameters();
        performSearch();
    });
    $('.searchinterests input').on('ifUnchecked', function(event) {
        var interestid = event.target.value;
        var arrayPos = $.inArray(interestid, searchSelectedInterests);
        if (arrayPos > -1) {
            searchSelectedInterests.splice(arrayPos, 1);
        }
        performSearch();
    });
    $('.searchsdgs input').on('ifChecked', function(event) {
        var sdgid = event.target.value;
        searchSelectedSdgs.push(sdgid);
        updateURLParameters();
        performSearch();
    });
    $('.searchsdgs input').on('ifUnchecked', function(event) {
        var sdgid = event.target.value;
        var arrayPos = $.inArray(sdgid, searchSelectedSdgs);
        if (arrayPos > -1) {
            searchSelectedSdgs.splice(arrayPos, 1);
        }
        performSearch();
    });
    $('.searchengagements input').on('ifChecked', function(event) {
        var social_engagement_id = event.target.value;
        searchSelectedEngagements.push(social_engagement_id);
        updateURLParameters();
        performSearch();
    });
    $('.searchengagements input').on('ifUnchecked', function(event) {
        var social_engagement_id = event.target.value;
        var arrayPos = $.inArray(social_engagement_id, searchSelectedEngagements);
        if (arrayPos > -1) {
            searchSelectedEngagements.splice(arrayPos, 1);
        }
        performSearch();
    });

    var searchLocationSelect = $('.stb-search-location');
    var radiusSlider = document.getElementById('radius-slider');
    var minMaxSlider = document.getElementById('min-max-slider');
    var priceSlider = document.getElementById('price-slider');
    var daterangepicker = $('#searchdaterange');
    var curr_lang = $('#curr_lang').val();

    switch(curr_lang) {
        case 'nl':
            moment.locale('nl');
            daterangepicker.daterangepicker({
                minDate: moment(),
                startDate:  moment(),
                endDate: moment().add(1, 'years'),
                ranges: {
                    'Volgende week': [moment(), moment().add(1, 'weeks')],
                    'Volgende maand': [moment(), moment().add(1, 'months')],
                    'Volgende 3 maanden': [moment(), moment().add(3, 'months')],
                    'Volgende 6 maanden': [moment(), moment().add(6, 'months')],
                    'Volgend jaar': [moment(), moment().add(1, 'years')]
                },
                alwaysShowCalendars: true,
                locale: {
                    "customRangeLabel": "Vrije keuze",
                    "format": "DD-MM-YYYY"
                }
            });
            break;
        case 'en':
            moment.locale('en');
            daterangepicker.daterangepicker({
                minDate: moment(),
                startDate:  moment(),
                endDate: moment().add(1, 'years'),
                ranges: {
                    'Next week': [moment(), moment().add(1, 'weeks')],
                    'Next month': [moment(), moment().add(1, 'months')],
                    'Next 3 months': [moment(), moment().add(3, 'months')],
                    'Next 6 months': [moment(), moment().add(6, 'months')],
                    'Next year': [moment(), moment().add(1, 'years')]
                },
                alwaysShowCalendars: true,
                locale: {
                    "customRangeLabel": "Free choice",
                    "format": "DD-MM-YYYY"
                }
            });
            break;
        case 'fr':
            moment.locale('fr');
            daterangepicker.daterangepicker({
                minDate: moment(),
                startDate:  moment(),
                endDate: moment().add(1, 'years'),
                ranges: {
                    'Semaine prochaine': [moment(), moment().add(1, 'weeks')],
                    'Mois prochain': [moment(), moment().add(1, 'months')],
                    '3 mois prochains': [moment(), moment().add(3, 'months')],
                    '6 mois prochains': [moment(), moment().add(6, 'months')],
                    'L\'année prochaine': [moment(), moment().add(1, 'years')]
                },
                alwaysShowCalendars: true,
                locale: {
                    "customRangeLabel": "Libre choix",
                    "format": "DD-MM-YYYY"
                }
            });
            break;
    }
    daterangepicker.on('hide.daterangepicker', function (ev, picker) {
        var startdate = picker.startDate.format('YYYY-MM-DD');
        var enddate = picker.endDate.format('YYYY-MM-DD');
        setStartDate(startdate);
        setEndDate(enddate);
        updateURLParameters();
        performSearch();
    });

    if (searchLocationSelect.length) {
        searchLocationSelect.selectize({
            create: false,
            sortField: {
                field: 'text',
                direction: 'asc'
            },
            dropdownParent: 'body',
            plugins: ['remove_button'],
            onChange: function(value) {
                setLocation(value);
                var size = value.length;
                if (size == 1) {
                    enableRadius(radiusSlider);
                } else {
                    disableAndResetRadius(radiusSlider);
                }
                updateURLParameters();
                performSearch();
            }
        });
    }

    noUiSlider.create(radiusSlider, {
        start: [0],
        step: 5,
        range: {
            'min': 0,
            'max': 100
        }
    });
    var radiusNodes = [
        document.getElementById('radius-value')
    ];
    // Display the slider value and how far the handle moved
    // from the left edge of the slider.
    radiusSlider.noUiSlider.on('update', function (values, handle, unencoded, isTap, positions) {
        var value = values[handle];
        radiusNodes[handle].innerHTML = Math.round(value);
    });
    radiusSlider.noUiSlider.on('set', function (value) {
        setRadius(value);
        updateURLParameters();
        performSearch();
    });

    var minmax_slider_options = {
        start: [0,1000],
        behaviour: 'drag',
        connect: true,
        tooltips: [wNumb({
            decimals: 0
        }), wNumb({
            decimals: 0
        })],
        range: {
            'min': 0,
            'max': 1000
        },
        step: 5
    };
    noUiSlider.create(minMaxSlider, minmax_slider_options);
    var minMaxNodes = [
        document.getElementById('minmax-lower-value'),
        document.getElementById('minmax-upper-value')
    ];
    // Display the slider value and how far the handle moved
    // from the left edge of the slider.
    minMaxSlider.noUiSlider.on('update', function (values, handle, unencoded, isTap, positions) {
        var value = values[handle];
        minMaxNodes[handle].innerHTML = Math.round(value);
    });
    minMaxSlider.noUiSlider.on('set', function (values) {
        setMin(values[0]);
        setMax(values[1]);
        updateURLParameters();
        performSearch();
    });

    var price_slider_options = {
        start: [0,50000],
        behaviour: 'drag',
        connect: true,
        tooltips: [wNumb({
            decimals: 0
        }), wNumb({
            decimals: 0
        })],
        range: {
            'min': 0,
            'max': 50000
        },
        step: 250
    };
    noUiSlider.create(priceSlider, price_slider_options);
    var priceNodes = [
        document.getElementById('price-lower-value'), // 1000
        document.getElementById('price-upper-value')  // 3500
    ];
    // Display the slider value and how far the handle moved
    // from the left edge of the slider.
    priceSlider.noUiSlider.on('update', function (values, handle, unencoded, isTap, positions) {
        var value = values[handle];
        priceNodes[handle].innerHTML = '€'+Math.round(value);
    });
    priceSlider.noUiSlider.on('set', function (values) {
        setMinPrice(values[0]);
        setMaxPrice(values[1]);
        updateURLParameters();
        performSearch();
    });

    /** Reset methods **/
    $('#resetLocations').on('click', function(e) {
        e.preventDefault();
        var locationInputField = $('.stb-search-location');
        var control = locationInputField[0].selectize;
        control.clear();
    });

    $('#resetRadius').on('click', function(e) {
        e.preventDefault();
        document.getElementById('radius-slider').noUiSlider.set(0);
    });

    $('#resetMinMax').on('click', function(e) {
        e.preventDefault();
        document.getElementById('min-max-slider').noUiSlider.set([0,1000]);
    });

    $('#resetPrice').on('click', function(e) {
        e.preventDefault();
        document.getElementById('price-slider').noUiSlider.set([0,50000]);
    });

    $('#resetInterests').on('click', function(e) {
        e.preventDefault();
        searchSelectedInterests = [];
        $("input[name='interests[]']").iCheck('uncheck');
    });

    $('#resetSdgs').on('click', function(e) {
        e.preventDefault();
        searchSelectedSdgs = [];
        $("input[name='sdgs[]']").iCheck('uncheck');
    });

    $('#resetDate').on('click', function(e) {
        e.preventDefault();
        $('#searchdaterange').data('daterangepicker').setStartDate(moment());
        $('#searchdaterange').data('daterangepicker').setEndDate(moment().add(1, 'years'));

        var startdate = $('#searchdaterange').data('daterangepicker').startDate.format('YYYY-MM-DD');
        var enddate = $('#searchdaterange').data('daterangepicker').endDate.format('YYYY-MM-DD');
        setStartDate(startdate);
        setEndDate(enddate);
        performSearch();
    });

    $('#resetEngagement').on('click', function(e) {
        e.preventDefault();
        searchSelectedEngagements = [];
        $("input[name='engagement[]']").iCheck('uncheck');
    });

    // Set initial parameters (and pre-fill the filters based on query params) 
    setupConfig(radiusSlider);
});


function getQueryStringValue(){
    var currentURL = new URI();

    var queryParams = currentURL.query(true);
    if ($.isEmptyObject(queryParams) === false) {
        return queryParams;
    } else {
        return undefined;
    }
}

//In here we read the query parameters from the URL and set the filters to the query parameters (+ do initial filtering)
function setupConfig(radiusSlider) {
    var queryParams = getQueryStringValue();

    if (queryParams !== undefined) {
        var locations = queryParams.locations;
        if (locations !== undefined) {
            var locationsArray = locations.split(";");
            fillLocations(locationsArray);
            if (locationsArray.length != 1) {
                disableAndResetRadius(radiusSlider);
            }
        } else {
            disableAndResetRadius(radiusSlider);
        }

        var distance = queryParams.distance;
        if (distance !== undefined) {
            if (locationsArray.length != 1) {
                disableAndResetRadius(radiusSlider);
            } else {
                document.getElementById('radius-slider').noUiSlider.set(distance);
            }
        }

        var minEmployees = queryParams.minemployees;
        var maxEmployees = queryParams.maxemployees;
        if ((minEmployees !== undefined) && (maxEmployees !== undefined)) {
            document.getElementById('min-max-slider').noUiSlider.set([minEmployees,maxEmployees]);
        }

        var minPrice = queryParams.minprice;
        var maxPrice = queryParams.maxprice;
        if ((minPrice !== undefined) && (maxPrice !== undefined)) {
            document.getElementById('price-slider').noUiSlider.set([minPrice,maxPrice]);
        }

        var interests = queryParams.interests;
        if (interests !== undefined) {
            var interestsArray = interests.split(";");
            fillInterests(interestsArray);
        }

        var sdgs = queryParams.sdgs;
        if (sdgs !== undefined) {
            var sdgsArray = sdgs.split(";");
            fillSdgs(sdgsArray);
        }

        var startdate = queryParams.startdate;
        var enddate = queryParams.enddate;
        if ((startdate !== undefined) && (enddate !== undefined)) {
            $('#searchdaterange').data('daterangepicker').setStartDate(moment(startdate));
            $('#searchdaterange').data('daterangepicker').setEndDate(moment(enddate));

            var startdate = $('#searchdaterange').data('daterangepicker').startDate.format('YYYY-MM-DD');
            var enddate = $('#searchdaterange').data('daterangepicker').endDate.format('YYYY-MM-DD');
            setStartDate(startdate);
            setEndDate(enddate);
        }

        var engagements = queryParams.engagements;
        if (engagements !== undefined) {
            var engagementsArray = engagements.split(";");
            fillEngagements(engagementsArray);
        }
    } else {
        disableAndResetRadius(radiusSlider);
        performSearch();
    }
}

function fillLocations(locations) {
    var selectize = $('.stb-search-location');
    selectize[0].selectize.setValue(locations);
}

function fillInterests(interests) {
    for (var i = 0; i < interests.length; i++) {
        var checkboxId = "interest-"+interests[i];
        var checkbox = $('#'+checkboxId);
        checkbox.iCheck('check');
    }
}

function fillSdgs(sdgs) {
    for (var i = 0; i < sdgs.length; i++) {
        var checkboxId = "sdg-"+sdgs[i];
        var checkbox = $('#'+checkboxId);
        checkbox.iCheck('check');
    }
}

function fillEngagements(engagements) {
    for (var i = 0; i < engagements.length; i++) {
        var checkboxId = "eng-"+engagements[i];
        var checkbox = $('#'+checkboxId);
        checkbox.iCheck('check');
    }
}

function getCurrLang() {
    return $('#curr_lang').val();
}

function getCurrLangSegment() {
    return $('#curr_abbr').val();
}

function getLocation() {
    return $('#location').val();
}

function setLocation(value) {
    searchLocation = value;
}

function setRadius(value) {
    searchRadius = value;
}

function disableAndResetRadius(radiusSlider) {
    radiusSlider.noUiSlider.set(0);
    radiusSlider.setAttribute('disabled', true);
}

function enableRadius(radiusSlider) {
    radiusSlider.removeAttribute('disabled');
}

function setMin(value) {
    searchMin = value;
}

function setMax(value) {
    searchMax = value;
}

function setMinPrice(value) {
    searchMinPrice = value;
}

function setMaxPrice(value) {
    searchMaxPrice = value;
}

function setStartDate(value) {
    searchStartDate = value;
}

function setEndDate(value) {
    searchEndDate = value;
}

function performSearch() {
    $('#stb-items-placeholder').html('<div id="loading"><span>'+res.StbSearchPlaceholder+'</span></div>');

    var searchOptions = {
        type: 'POST',
        url: baseUrl + '/dashboard/socialteambuildings/search/getFilteredStbs',
        dataType: 'json'
    };

    var filterdata = {
        "curr_lang" : getCurrLang(),
        "curr_abbr" : getCurrLangSegment(),
        "location" : getLocation(),
        "interests": searchSelectedInterests,
        "sdgs": searchSelectedSdgs
    };

    var options = $.extend({}, searchOptions, {
        data: filterdata
    });

    // ajax done & fail
    $.ajax(options).done(function (data) {
        var mustacheJsonData = data.result;

        var html = Mustache.render( $('#stbItemGen').html(), mustacheJsonData );
        $('#stb-items-placeholder').html( html );
    });
}

function updateURLParameters() {
    if (searchLocation != null) {
        var locationsUrlString = searchLocation.join(";");
    }

    var distanceUrlString = Math.round(searchRadius[0]);
    var minEmployeesUrlString = Math.round(searchMin);
    var maxEmployeesUrlString = Math.round(searchMax);
    var minPriceUrlString = Math.round(searchMinPrice);
    var maxPriceUrlString = Math.round(searchMaxPrice);
    var interestUrlString = searchSelectedInterests.join(";");
    var sdgUrlString = searchSelectedSdgs.join(";");
    var startDateUrlString = searchStartDate;
    var endDateUrlString = searchEndDate;
    var engagementUrlString = searchSelectedEngagements.join(";");

    var params = {locations: locationsUrlString, distance: distanceUrlString, minemployees: minEmployeesUrlString, maxemployees: maxEmployeesUrlString, minprice: minPriceUrlString, maxprice: maxPriceUrlString, interests: interestUrlString, sdgs: sdgUrlString, startdate: startDateUrlString, enddate: endDateUrlString, engagements: engagementUrlString};
    var query = $.param(params);
    addURLParameter(query);
}

//This function removes all the query parameters and adds the completely newly built query param string again
function addURLParameter(queryString){
    var currentUrl = window.location.href;
    var urlNoQueryParams = currentUrl.split("?");
    var baseUrl = urlNoQueryParams[0];
    var result = baseUrl + "?" + queryString;
    window.history.replaceState('', '', result);
}

Я пытался использовать e.stopPropagation () и e.stopImmediatePropagation (), например, для опции удаления.Это не останавливает события, возвращающиеся в библиотеку iCheck.

1 Ответ

0 голосов
/ 03 декабря 2018

Debounce не будет работать здесь, так как проблема не в одном слушателе событий, имеющем слишком много запросов за короткий промежуток времени, а в том, что многие независимые слушатели событий запускают один за другим.

Возможные решения:

  1. Добавьте кнопку, например SEARCH, которая будет фактически выполнять поиск, вместо того, чтобы вызываться отдельными обновлениями.Это хорошее и простое решение проблемы многих независимых слушателей.

  2. Если вы не хотите добавлять новую кнопку, выполните следующее:Добавьте интервал времени с setInterval, чтобы выполнить поиск с помощью AJAX.И иметь флаг, должен ли быть выполнен поиск.Затем, когда любой из прослушивателей при смене чекбокса просто установит флаг true.Также, если запрос уже выполняется, НЕ делайте еще один AJAX-запрос, пока не завершится текущий.

следующий псевдокод:

var do_search = false, timer = null, doing_ajax = false, TROTTLE = 500;
timer = setTimeout(performSearch, TROTTLE);

function performSearch()
{
    if ( !do_search || doing_ajax )
    {
        timer = setTimeout(performSearch, TROTTLE);
        return;
    }
     doing_ajax = true;
     do_search = false;
     // do the ajax request here
     // ...
     // NOTE: on ajax complete reset the timer, eg timer = setTimeout(performSearch, TROTTLE);
     // and set doing_ajax = false;
}

// then your checkboxes listeners will simply update the do-search flag eg:
    $('.searchsdgs input').on('ifChecked', function(event) {
        var sdgid = event.target.value;
        searchSelectedSdgs.push(sdgid);
        updateURLParameters();
        //performSearch();
        do_search = true;
    });
    $('.searchsdgs input').on('ifUnchecked', function(event) {
        var sdgid = event.target.value;
        var arrayPos = $.inArray(sdgid, searchSelectedSdgs);
        if (arrayPos > -1) {
            searchSelectedSdgs.splice(arrayPos, 1);
        }
        //performSearch();
        do_search = true;
    });
    $('.searchengagements input').on('ifChecked', function(event) {
        var social_engagement_id = event.target.value;
        searchSelectedEngagements.push(social_engagement_id);
        updateURLParameters();
        //performSearch();
        do_search = true;
    });
    $('.searchengagements input').on('ifUnchecked', function(event) {
        var social_engagement_id = event.target.value;
        var arrayPos = $.inArray(social_engagement_id, searchSelectedEngagements);
        if (arrayPos > -1) {
            searchSelectedEngagements.splice(arrayPos, 1);
        }
        //performSearch();
        do_search = true;
    });

NOTE Вы можете настроить интервал TROTTLE, чтобы сбалансировать между более немедленной интерактивностью и меньшим количеством запросов AJAX.Поэкспериментируйте с различными значениями, чтобы почувствовать его для своего приложения.

NOTE2 Вы можете использовать этот пример и сделать его, например, функцией multi-debounce очистив тайм-аут и сбросив его в каждом отдельном слушателе (вместо простой установки do_search=true вы можете установить do_search=true, очистить предыдущий интервал и сбросить его снова).Это обеспечит выполнение только последнего обновления.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...