0

Sorry for repeating this question, and Ajax integration since its very broad subject, but I just can't figure it out.

I have a html table with 4k+ rows of data, and each of rows has some basic information like name, address, last name, phone, and a button which triggers a bootstrap modal which contains additional data like phone2, address 2, etc.

My problem is, since my modal is located inside a for loop like this -

<tbody>
    {% for opp in optika %}
        <tr>
            <td>{{ opp.example }}</td>
            <td>{{ opp.some_field }}</td>
            <td>            
                <button id="testbtn" type="button" class="btn btn-success btn-sm btn-detail"
                    data-toggle="modal"
                    data-target="#detalji_modal{{ opp.pk }}"
                    Detalji
                </button>
            </td>
...
        </tr>
    {% include "opportunity/partials/_detalji_modal.html" %}
    {% endfor %}
</tbody>

And this is modal detail -


<div class="modal fade bd-example-modal-lg" tabindex="-1" role="dialog"
     id="detalji_modal{{ opp.pk }}"
     aria-labelledby="detalji_modalLabel">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title" id="detalji_modalLabel">Detalji</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body" id="">
                <div class="container-fluid">
                    <h5>{{ opp.... }}</h5>
                    <p>{{ opp.....title }}</p>
                    ...
                </div>
            </div>
            <div class="modal-footer">
                <p style="margin-right: auto">Zahtev uneo/la : {{ opp.... }}</p>
                ...
                ...
                <button type="button" class="btn btn-outline-primary" data-dismiss="modal">
                    Close
                </button>
            </div>
        </div>
    </div>
</div>

I'll pass the view also :

@login_required(login_url='registration/login')
def OptikaList(request):
    user_groups = request.user.groups.all()
    optika_svi = OpportunityList.objects.all().defer(
        ...
    ).filter(opp_type__exact='optika').exclude(opp_status='zakazan', is_locked=True).order_by('eluid',
                                                                                              '-opp_status').exclude(
        opp_status='odustao')
    optika = [o for o in list(optika_svi)]

    counter = 1000

    context = {
        'optika': optika[:counter],
        'broj_poslova': counter,
        'ukupno_poslova': optika_svi.count(),
        'user_groups': user_groups,
    }

    #Ajax is not used for now, since idk how to implement it right.
    if request.is_ajax():
        data = {
            'rendered_table': render_to_string('opportunity/get_more_tables.html', context=context,
                                               request=request)}

        return JsonResponse(data)

    return render(request, 'opportunity/opp_optika.html', context)

And this is my ajax call which is a mess -

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script>
    $(document).ready(function () {

        // Our ajax function. Specifically declared as we are
        // using it in multiple places
        let ajax_function = function () {
            fetch_modal();
            page_changing = false;
            $.ajax({
                url: '{% url 'opportunity:optika' %}',
                type: "get",
                cache: true,
                data_type: 'html',

                success: function (data) {
                    console.log("Ajaxed");
                    var page = dt.page(); // save page we are on
                    var order = dt.order(); // save ordering
                    var search = dt.search();
                    var page_length = dt.page.len();
                    dt.destroy(); // put table back to new

                    $('#dataTable').find('tbody').empty().append(data.rendered_table);
                    dt = $('#dataTable').DataTable(table_options); // Re-init datatable
                    dt.order(order); // Put back our ordering
                    dt.search(search);
                    dt.page.len(page_length);
                    dt.page(page).draw('page'); // Draw the page
                    dt.draw(false); // Put in our ordering an all the stuff

                    if (focused_search) {
                        $('input[type=search]').first().focus();
                        console.log('focused');
                    }
                    focus_set();
                    focus_lost();
                    page_changer();
                    set_detail_btn();
                },
                error: function (data) {
                    console.log("Ajax stopped!");
                }
            });
        };

        // Variables to hold our urls to the body and footer views
        let body_update_link = '{% url 'opportunity:fetch_body' %}';
        let footer_update_link = '{% url 'opportunity:fetch_footer' %}';

        // id of opportunity currently in our modal
        let modal_opp = false;

        // ajax function for getting data for modal
        let fetch_modal = function () {
            if (modal_opp) {
                $('#detalji_modal .modal-body').load(body_update_link, modal_opp.toString());
                $('#detalji_modal .modal-footer').load(footer_update_link, modal_opp.toString());
            }
        };

        // function for activating getting modal data on click on details
        let set_detail_btn = function () {
            $('.btn-detail').click(function () {
                modal_opp = $(this).data('opp');
                fetch_modal();
            });
        };

        // Set modal_opp to false after our modal has disappeared to avoid
        // unnecessary updates of an invisible modal
        $('#detalji_modal').on('hidden.bs.modal', function (e) {
            modal_opp = false;
        });

        // A central variable for putting in table options
        let table_options = {
            // searching: false,
            // paging: false
            order: [[0, "asc"]]
        };

        // variable holding our setInterval function so we can disable
        // it if we want to
        let my_timer;

        // variable set to true if we are currently in the filter
        let focused_search = false;

        // Set our focused_search variable if our filter has focus
        let focus_set = function () {
            $('input[type=search]').focus(function () {
                console.log('HAS FOCUS');
                focused_search = true;
                clearInterval(my_timer);
                my_timer = false;
            });
        };

        // Reset our focused_search variable if our filter loose focus
        const focus_lost = function () {
            $('input[type=search]').blur(function () {
                    console.log('LOST_FOCUS');
                    focused_search = false;
                },
                function () {
                    console.log('LOST_FOCUS');
                    conditional_timer_restart();
                    focused_search = false;
                }
            );
        };

        // Restart our timer
        const conditional_timer_restart = function () {
            if (!my_timer) {
                my_timer = setInterval(ajax_function, 2000);
            }
        };

        // variable set to true if we have changed paging
        // needed when we click on our paging select so
        // our timer restart raliably
        let page_changing = false;

        // Function to add some behaviour to our pager
        // Will stop reloading page if we click on that select
        // Will restart the reloading cycle automatically after 5
        // seconds
        const page_changer = function () {
            $('select[name=dataTable_length]').click(function () {
                console.log('Page changer init');
                if (page_changing) {
                    page_changing = false;
                } else {
                    clearInterval(my_timer);
                    my_timer = false;
                    setTimeout(conditional_timer_restart, 5000);
                }
            });
            $('select[name=dataTable_length]').change(function () {
                console.log('Page changer done');
                page_changing = true;
                my_timer = setInterval(ajax_function, 5000);
            });
            $('select[name=dataTable_length]').blur(function () {
                console.log('Page changer lost focus');
                if (!my_timer) {
                    my_timer = setInterval(ajax_function, 5000);
                }

            });

        };
        set_detail_btn = function () {
            $('.btn-detail').click(function () {
                modal_opp = $(this).data('opp');
                fetch_modal();
            });
            $('.btn-detail').hover(
                function () {
                    console.log('hovered');
                    clearInterval(my_timer);
                    my_timer = false;
                },
                function () {
                    console.log('hoveroff ');
                    conditional_timer_restart();
                }
            );
        };

        // Our intial init of the table
        let dt = $('#dataTable').DataTable(table_options);
        focus_set();
        focus_lost();
        page_changer();
        set_detail_btn();
        // my_timer = setInterval(ajax_function, 1000);

    });

</script>


fetch_body and fetch_footer functions from views.py -

def fetch_body(request):
    id = list(request.GET.keys())[0]
    opp = OpportunityList.objects.get(pk=id)

    return HttpResponse(
        render_to_string('opportunity/partials/_modal_body.html', context={'opp': opp}, request=request))


def fetch_footer(request):
    id = list(request.GET.keys())[0]
    opp = OpportunityList.objects.get(pk=id)

    return HttpResponse(
        render_to_string('opportunity/partials/_modal_footer.html', context={'opp': opp}, request=request))

The page itself needs 5+ seconds to load, and it has more than 20mb of data which is a huge overkill.

Basically I need the modal body(data) to be fetched on modal opening/on button(testbtn) click.

Or maybe to load the details(modal) only for showing objects with pagination.

And since im not so good with Ajax(its still confusing thing for me) and jQuery, can someone please explain me the best approach for this(example will be great!).

Thank you!

dev.ink
  • 380
  • 5
  • 21
  • there are many examples out there, just choose one. https://stackoverflow.com/questions/20306981/how-do-i-integrate-ajax-with-django-applications for example. Your time problem is probably because you fetch objects multiple times/your template for loop keeps on doing look ups which costs time. https://docs.djangoproject.com/en/3.0/ref/models/querysets/#prefetch-related look at that and `values/values_list`. Last but not least I would suggest to use English for everything in your code (attributes, variables, names etc). You steal your project the opportunity to grow bigger... – hansTheFranz Apr 13 '20 at 13:35
  • I don't find this link useful, and I've already seen it. I can't find good example for my problem there. Prefetch related is good, but im pulling the data from db view, which is not managed, and has only CharFields, so prefetch related is no go here. I prefer English also, but this one is understandable since I removed all the unnecessary code. – dev.ink Apr 13 '20 at 13:46
  • please post your Ajax call (the js). maybe it's easier to create a separate url and function to understand the concept. The idea would be to load some data in the first view and then with the Ajax call fetch more data. One way would be trigger an Ajax call each time a user opens a modal and in the modal have more data which get loaded via Ajax. another way would be only loading 10 objects and on scroll load next 10 and so on. btw `'optika': optika[:counter]` makes no sense, if you want to limit the query do it when you fetch the objects. the counter in your example is always 1000? why int()? – hansTheFranz Apr 13 '20 at 19:10
  • I added my current ajax call which isn't used. Yeah I understand the logic in theory, just don't know how to implement it into my code. – dev.ink Apr 13 '20 at 19:38

1 Answers1

0

It turns out that Ajax is not so hard to figure.

SO basically there's no correct answer for this, you should choose the options which suits you best.

As mentioned before this link How do I integrate Ajax with Django applications? has nice detailed explanation for beginners.

dev.ink
  • 380
  • 5
  • 21