1
votes

I am super puzzled about what is happening here. I start an AJAX request and display a loading animation. When the AJAX successfully returns a response I hide the loading animation and use the response to create some HTML elements.

For one of the HTML elements the client needs a few seconds until its created, but because of this none of the previously created html elements are displayed.

Here is the code:

function load_chatpics($page) {
    // Show loading animation
    $('#find-chatpics-c .loading-element').removeClass('display-none');

    $.ajax({
        url : '/home/get_chatpics/' + $page,
        type : "get",
        success : function(response) {
            console.log("get_chatpics", response);

            // Hide loading animation
            $('#find-chatpics-c .loading-element').addClass('display-none');

            if (response['success'] == true) {

                $('#find-chatpics-c .main-c-left').append('<div class="all-chat-pic-holders"></div>');

                $.each(response['result'], function(key, element) {

                    // Append IMG element
                    $('#find-chatpics-c .main-c-left .all-chat-pic-holders').append('<div class="chat-pic-holder" data-picid="' + element['id'] + '">\
                        <img data-sentby="' + element['from_user'] + '" src="' + $('#home-container').data('chatpath') + element['message'] + '">\
                    </div>');
                });

                // IMAGES SHOULD BE ALREADY VISIBLE, BUT THEY ARENT, BECAUSE OF WHAT COMES NOW

                // Pagination buttons           
                $('#find-chatpics-c .main-c-left').append('<div class="chat_pic_nav">\
                    <div class="buttons" data-currentpage="0">\
                        <button class="pics_back"><i class="fas fa-angle-left"></i></button>\
                        <select></select>\
                        <button class="pics_forward"><i class="fas fa-angle-right"></i></button>\
                    </div>\
                </div>');

                $('#find-chatpics-c .main-c-left').prepend('<div class="chat_pic_nav">\
                    <div class="buttons" data-currentpage="0">\
                        <button class="pics_back"><i class="fas fa-angle-left"></i></button>\
                        <select></select>\
                        <button class="pics_forward"><i class="fas fa-angle-right"></i></button>\
                    </div>\
                </div>');

                $('#find-chatpics-c .main-c-left .chat_pic_nav .buttons').data('currentpage', $page);

                // THE FUNCTION WHICH CAN TAKE LONGEr, A FEW SECONDS~
                // BECAUSE OF THIS NOTHING IS VISIBLE, EVEN THE LOADING ANIMATION IS NOT REMOVED

                populate_select(response['result_len'], $page, $('#find-chatpics-c .main-c-left .chat_pic_nav .buttons select'));


            } 
        },
        error : function(xhr) {
            console.log("get_chatpics", xhr);
            $('#find-chatpics-c .loading-element').addClass('display-none');
            spawn_flash('error', xhr['responseText']);
        }
    });
}

To sum is up:

When the function is called an animation is shown, but in the AJAX success nothing is executed/created until populate_select(response['result_len'], $page, $('#find-chatpics-c .main-c-left .chat_pic_nav .buttons select')) is not ready.

What I expect is:

In the success function the animation is removed, then the images are created and are seen at the moment when they have been created, then the navigation is created and at last the select field is populated with the pages (that is what populate_select() does), but none of this happens until populate_select() is ready

I even have a fix for this issue, which works great:

I simply put populate_select() into a timeout function

setTimeout(function(){
    populate_select(response['result_len'], $page, $('#find-chatpics-c .main-c-left .chat_pic_nav .buttons select'));
}, 1000) 

Why does this fix the issue?

EDIT:

The populate_select fucntion:

function populate_select(_len, _page, _selectObject) {
    var select_counter = 0;
    while (select_counter <= _len) {
        if (select_counter == _page) {
            _selectObject.append('<option selected val="' + select_counter +'"> Seite ' + (select_counter + 1) + '</option>');
        } else {
            _selectObject.append('<option val="' + select_counter +'"> Seite ' + (select_counter + 1) + '</option>');
        }
        select_counter += 1;
    }
}
1
Does populate_select() fire a synchronous AJAX request?Rory McCrossan
Sorry I should add the function aswell, give my a secondRoman
Sounds like populate select does something in a large loop or does some synchronous process that locks up rendering. Impossible to tell without looking at the method.epascarello
With the edit, if it is a large loop it will lock up the browser.epascarello
Agreed with @epascarello. What is the value of response['result_len']?Rory McCrossan

1 Answers

1
votes

The reason this is causing a freeze is because you're performing a massive update to the DOM in a loop when the AJAX response is received. As such this is not inherently an AJAX problem, but instead caused by the amount of processing being done after it.

You state in the comments that response['result_len'] returns 2400, hence you are looping this number of times and appending an <option> element in each iteration. This results in many DOM operations (create element, update properties of the element, append the element to the DOM, etc) and this is one of the slowest performing parts of JS. The UI appears to freeze in this instance because there is so much work being done to the DOM in the background, and it will only update the UI once all this processing has been completed.

What can you do to work around this?

One way would be to build all the HTML as a single string and append it once. This drastically reduces the number of DOM operations being performed.

function populate_select(_len, _page, _selectObject) {
  var select_counter = 0;
  var html = '';
  while (select_counter <= _len) {
    if (select_counter == _page) {
      html += '<option selected val="' + select_counter + '"> Seite ' + (select_counter + 1) + '</option>';
    } else {
      html += '<option val="' + select_counter + '"> Seite ' + (select_counter + 1) + '</option>';
    }
    select_counter += 1;
  }
  _selectObject.append(html);
}

populate_select(2400, null, $('select'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<select></select>

Another way would be to not create N number of incremental option elements in the first place. If all you need is for the user to enter a number between X and Y, use a number input set its min and max and then validate it when the user has entered a value. This saves the need for the loop at all and is probably also easier for the users, as scrolling through 2,000+ options will take a while.

<input type="number" min="0" max="2400" value="0" />