0

I have a table that loads empty (no rows) and then I run an Ajax call -inside a django for-loop- to populate the table with data from a python function that each time returns one row/item.

I also have a jquery function that when I right-click on a row, it produces a context menu for that specific row, populated with options dynamically through an AJAX script. The problem is that the context menu isnt populated promptly.

How it works is:

  • I right-click on a row and "grab" certain parameters of the object in said row.
  • These parameters then are passed to the getTablePerms() function, which runs an AJAX POST request to a Python function that -based on those parameters- returns the permissions of the item in the row and modifies the html of the context menu div, essentially showing the context menu options.
  • While the rows are added to the table, right-clicking on a row should produce the context menu immediately, but it does not. It appears as if its waiting to get response from most (if not all) of the GET requests.

To counter this I applied two setTimeouts to make the AJAX GET request wait a few seconds, but while it works on small amounts of rows (70 rows), at larger amounts (500+ rows) it does not show the context menu until it has finished receiving the STATUS-200 responses. The POST request does get sent, but it is its response that gets delayed.

Which is why I think that the sequential GET requests might block the POST request from receiving the response because it gets queued after the GET request responses.

My views.py

def myprotocol_item(request,pk):

    data = dict()
    protocoltype = request.GET.get('protocoltype')
    mytype = request.GET.get('type')
    queryset = Myprotocol.objects.filter(Q(pk=pk) & Q(del_f=0))
    context = { 'myprot':queryset[0]}  
    template = 'protocol/Myprotocol/list_table_body.html'
    data['html_form'] = render_to_string(template,context,request=request,)
    data['pk'] = pk
    return JsonResponse(data)

My ajax call:

<script>
{% if data %}
  {% for dataitem in data %}
  setTimeout(function(){
    $.ajax({
        headers: {'X-CSRFToken':getCookie('csrftoken')},
        url: "{% url 'protocol:myprotocol-item' dataitem.protocol_ptr.id %}",
        type: 'get',
        dataType: 'json',
        success: function(data) {
          var tablerows = $('#myTable1 #tbody tr').length;
         
          if (tablerows === 0){
            $("#myTable1 #tbody").append(data.html_form);  
          }
          else if (tablerows > 0){
           
            $("#myTable1 #tbody tr").last().after(data.html_form);

          }
          // let the plugin know that we made a update
          // the resort flag set to anything BUT false (no quotes) will trigger an automatic
          // table resort using the current sort          
          var resort = true;
          $("table").trigger("update", [resort]); 
          
   
        }, // end of success
        error : function(xhr,errmsg,err) {
          console.log(xhr.status + ": " + xhr.responseText); // provide a bit more info about the error to the console
        } // end of error   

    }); //end of ajax
  }
  ,3000);//end of SetTimeout
  {% endfor %}
{% endif %}
</script>

My rightclick.js

  $('body').contextmenu(function() {
    return false;
  });
  //==============contextMenu====================== //
  var $contextMenu = $("#contextMenu");
  var $id_page_content = $("#id_page_content");
  var $myTable1 = $("#myTable1");
  $('body').on("contextmenu", "#myTable1 tbody tr,#myTable2 tr",function(e) {

    var myid = $(this).attr('id').split('_')[1].replace('.', '');
    var mytype = $(this).attr('id').split('_')[2];
    var f = $(this).attr('id').split('_')[3];
    var mycontainerid = $(this).attr('id').split('_')[4];

    var obj_table = $(this).attr('data-obj-table').split('_')[1];
    var routeid = $(this).attr('data-obj-table').split('_')[2];
    

    console.log('myid '+ myid);
    console.log('folder ' + f);
    console.log('mytype ' + mytype);
    console.log('obj table ' + obj_table);
    console.log('obj route ' + routeid);
    console.log('mycontainerid ' + mycontainerid);

    getTablePerms(myid,mytype,obj_table,f,routeid,mycontainerid);
    
    if ($(window).scrollTop() < $(document).height() && e.pageY > $myTable1.height()-80 && e.pageY >= document.querySelector('[id^="myTable"]').offsetTop+200 && e.pageY >= $(window).height()-300){
    
       $contextMenu.css({
         display: "block",
         left: e.pageX,
         top: e.pageY-248,
    
       });
     }
     else {
      $contextMenu.css({
        display: "block",
        left: e.pageX,
        top: e.pageY,

      });
    }
    
  });
  $('#contextMenu').click( function(event){
    event.stopPropagation();
    $('#contextMenu').hide();
  });
  $('body').click( function(){
    
    $('#contextMenu').hide();
  });
  //==============End contextMenu====================== //

function getTablePerms(myid,mytype,obj_table,f,routeid,mycontainerid){
        $.ajax({
              type:"POST",
              dataType: "json",
              url: "/common/get/object/perms/",
              data:{'csrftoken':getCookie('csrftoken'),'obj':myid,'mytype':mytype,'obj_table':obj_table,'f':f,'routeid':routeid,'mycontainerid':mycontainerid},
              success: function(result)
              {
//========== JQUERY CODE TO MODIFY CONTEXT MENU ============//
              }// end of success
        });// end of ajax
}// end of function

Any thoughts on how to make the POST request reply be given priority in receiving it?

John Gardounis
  • 125
  • 1
  • 12
  • Maybe you should reconsider looping and creating AJAX calls, it seems like you could easily refactor your code and get all the data you need in one call. Note also that you are making a script write a script by having Django loop and write JavaScript, this again can be simplified for which the [json_script](https://docs.djangoproject.com/en/4.1/ref/templates/builtins/#json-script) template tag might be useful. – Abdul Aziz Barkat Oct 14 '22 at 04:51
  • @AbdulAzizBarkat yes you are right about the script tag, it might be confusing without it. Thank you for pointing it out. You mean to have the Ajax return the whole sum of the records and then use JQuery to put them out? But wont that create a loading time overhead when the amount of records is high? – John Gardounis Nov 01 '22 at 10:49
  • 1
    So you assume N API calls are faster than 1? If you consider it properly with N calls, you first call your server, the server makes a query to the database, and then returns the response to you. You might get part of the data quicker but the totality comes out slower plus you increase the traffic to the server and the number of queries made to the DB. The ordering of the elements in the DOM might also not be consistent given AJAX calls are asynchronous. – Abdul Aziz Barkat Nov 01 '22 at 11:08
  • @AbdulAzizBarkat may I send you a PM to discuss this further? I am really interested in any ideas :) – John Gardounis Nov 02 '22 at 15:03
  • @AbdulAzizBarkat I agree that the 1 call is better, I am just trying to see how I can add the table row, even if the call is not concluded due to large amount of data. The json_script is something I will try to see how it works. If I manage to make it work, I will change the answer. – John Gardounis Nov 02 '22 at 15:13

1 Answers1

0

The answer came after a bit of search in the form of AjaxQueue.

The problem was basically that while the data are called in one Ajax request, it doesnt allow any other calls made, including the Ajax POST that builds the context menu depending on each item permissions.

What I did instead is to make the Ajax request to bring the data of each table row into an AjaxQueue, by adding the ajaxQueue.js to the list template's header and modifying the ajax request as follows:

{% if data %}
  {% for dataitem in data %}
    var jqXHR = $.ajaxQueue({
        headers: {'X-CSRFToken':getCookie('csrftoken')},
        url: "{% url 'protocol:myprotocol-item' dataitem.protocol_ptr.id %}",
        type: 'get',
        dataType: 'json',
        success: function(data) {
          var tablerows = $('#myTable1 #tbody tr').length;
         
          if (tablerows === 0){
            $("#myTable1 #tbody").append(data.html_form);  
          }
          else if (tablerows > 0){
           
            $("#myTable1 #tbody tr").last().after(data.html_form);

          }
        
          var resort = true;
          $("table").trigger("update", [resort]); 
          return false;          
   
        }, // end of success
        error : function(xhr,errmsg,err) {
          console.log(xhr.status + ": " + xhr.responseText);
        } // end of error   

    }); //end of ajax
  }

  {% endfor %}
{% endif %}

The ajaxQueue calls each item separately making the process fast enough without binding the server resources and allows for irrelevant requests to be made, including the one for the context menu.

A big thank you to @gnarf and their ajaxQueue script!!!

John Gardounis
  • 125
  • 1
  • 12