Draw is Redrawing Within Itself

Draw is Redrawing Within Itself

wjmccormickwjmccormick Posts: 10Questions: 2Answers: 0

Description of problem

In some instances, the Draw on Search causes the Datatables wrapper and table to be re-drawn (after init) within itself. On our development server, all of the code runs as expected. However, when it is pushed to our staging and production servers, the issue is observed.

The dropdown and text field search controls that are within the Datatables wrapper work as expected on all of our servers. I have one search control that is outside of the Datatables wrapper and it is this one, that when triggered, causes the Datatables wrapper and table to be re-drawn within itself.
.
.
.

Screenshots

Datatable initialized

.
.
Filter outside of the Datatable wrapper

.
.
✔ Datatable filtered on dev server

.
.
❌ Datatable filtered on staging and production servers

.
.
.

Code

The code below is reduced to just that which is pertinent to the issue.

        //first add an event listener for page load
        document.addEventListener("DOMContentLoaded", init, false); //init is the function name that will fire on page load

        function init() {
            //Datatables textfield filter within wrapper
            document.getElementById("global_filterCourses").addEventListener("keyup", filterGlobalCourses);
            //Datatables dropdown filter outside of wrapper
            document.getElementById("selIndvTrngFltr").addEventListener("change", filterJobCourses);

            initIndvTrngTable();
        }

        function initIndvTrngTable() {
            var arrTrgtAud = [];
            var j = 0;

            //Init the individual training datatable
            $("#coursesTable").DataTable({
                responsive: true,
                ajax: "data/IndividualTrainingData.txt",
                columns: [
                    { "data": "crsTitle" },
                    { "data": "trgtAud" },
                    { "data": "trngType" },
                    { "data": "crsType" },
                    { "data": "atrrsSchlCode" },
                    { "data": "crsLen" },
                    { "data": "enrollmentInst" },
                    { "data": "crsScope" },
                    { "data": "specInfo" }
                ],
                columnDefs: [
                    {"className": "tblcellNoWrap", "targets": 0},
                    {"className": "none", "targets": [1,4,5,6,7,8]},
                    {"responsivePriority": 1, "targets": 0}
                ],
                order: [[1, "asc"]],
                lengthMenu: [[10, 25, 50, -1], [10, 25, 50, "All"]],
                initComplete: function () {
                    //Populate the select input field with unique occurences of column data
                    this.api().columns([2,3]).every( function () {
                        var column = this;
                        var select = $("<select id='selIndvTrngFltr-" + column.index() + "'><option value=>Select All</option></select>")
                            .appendTo( $("#indvTrngFltr-" + column.index()) )
                            .on( "change", function () {
                                var val = $.fn.dataTable.util.escapeRegex(
                                    $(this).val()
                                );
                                var colid = this.parentElement.id.split("-")[1];
                                if( colid == 1) {
                                    column
                                        .search( this.value )
                                        .draw();
                                    var searchTermJob = this.value;
                                    $(".linkJobSearch").attr("href", function(i, val) {return val.substr(0, val.lastIndexOf("=") + 1) + searchTermJob;});
                                }
                                else {
                                    column
                                        .search( val ? "^" + val + "$" : "", true, false )
                                        .draw();
                                }
                            });

                        column.data().unique().sort().each( function ( val, j ) {
                            var dispVal = "";
                            //If the value has an `;`, it is from the Target Audience column and needs to be split
                            if(val.indexOf(";") > 0) {
                                //Split on the semicolon to get all of the items
                                var arrItems = val.split(";");
                                
                                for (var i = 0;i < arrItems.length - 1;i++) {
                                    if(arrItems[i]) {
                                        dispVal = arrItems[i];
                                        if(arrTrgtAud) {
                                            var a = arrTrgtAud.indexOf(dispVal);
                                            if(a == -1) {
                                                select.append("<option value=" + dispVal + ">" + dispVal + "</option>");
                                                arrTrgtAud[j] = dispVal;
                                                j = j++;
                                            }
                                        } else {
                                            select.append("<option value=" + dispVal + ">" + dispVal + "</option>");
                                            arrTrgtAud[j] = dispVal;
                                            j = j++;
                                        }
                                    }
                                }
                            } else {
                                if(val.indexOf("<") == 0){
                                    val = val.substring(val.indexOf(">") + 1, val.lastIndexOf("<"));
                                }
                                
                                if(val.length > 30){a
                                    dispVal = val.substring(0,30) + "...";
                                } else {
                                    dispVal = val;
                                }
                                
                                if(dispVal) {
                                    select.append("<option value=" + val + ">" + dispVal + "</option>");
                                }
                            }
                        });
                    });
                }
            });
        }

        function filterGlobalCourses() {
            //This Datatables Draw works as expected in all instances
            $("#coursesTable").DataTable().search( $("#global_filterCourses").val() ).draw();
        }

        function filterJobCourses() {
            var searchTermJob = this.value;
            var elems = document.getElementsByClassName("linkJobSearch");
            for (var i = 0;i < elems.length; i++) {
                var hrefVal = elems[i].getAttribute("href");
                hrefVal = hrefVal.substr(0, hrefVal.lastIndexOf("=") + 1);
                elems[i].setAttribute("href", hrefVal + searchTermJob);
            }

            //THIS IS WHERE THE REDRAW IS HAPPENING WITHIN ITSELF
            $("#coursesTable").DataTable().column(1).search( $("#selIndvTrngFltr").val() ).draw();
        }

.
.
.

Link to test case

On our development server the code runs as expected. However, when it is pushed to our staging and production servers, the issue is observed. Here is the link to the page where the code is run on our public facing page. When a user clicks on the Select your job dropdown, a search of column(1) is done on the $("#coursesTable").DataTable() - LINE 121 above. To recreate the issue, any of the dropdown selections can be used, but in testing and to return a smaller search result, I typically use Enlisted Corps - CMF68 - 68D - Operating Room.
https://medcoe.army.mil/dotd#btn-a1
.
.
.

Error messages shown

There are no error messages prompted or in the console.

Replies

  • colincolin Posts: 14,526Questions: 1Answers: 2,482

    Thanks for the link, but I'm not seeing a "Select your job" dropdown. Please can you provide steps on where to go to see the issue,

    Colin

  • wjmccormickwjmccormick Posts: 10Questions: 2Answers: 0

    When you click on the link in the original post, you will come to a bookmark within the page, that is an accordion.

    1. Click on the first accordion item How do I train for my job? [Individual Training]

    2. Once the accordion element is expanded, you should see the Select your job dropdown control, click on this link to filter the datatable. The datatable itself is located further down the page. So you will need to scroll beyond the card items, to get to the the Distributed Learning Courses datatable.

  • kthorngrenkthorngren Posts: 17,238Questions: 25Answers: 4,091

    Interesting issue. I copied some of your code here:
    http://live.datatables.net/mameyuci/1/edit

    It works properly with column().search(). Did some debugging and found that when clicking on How do I train for my job? [Individual Training] some code in react-dom.production.min.js executes. I'm not familiar with React but I wonder if its doing something to duplicate the coursesTable_wrapper -tag div. The interesting thing is the duplicated-tag div` only has 10 rows:

    This leads me to believe something is copying the original table, which Datatables has one 10 of the 203 rows in the DOM and creating a new table and initializing Datatables using the new 10 row table. The code in the initIndvTrngTable() function is not executed a second time.

    Take a look at the React code to see if there is something it is doing with the table. Maybe someone more familiar with React can help.

    Kevin

  • wjmccormickwjmccormick Posts: 10Questions: 2Answers: 0

    @kthorngren Thanks for the feedback. Unfortunately, the React code is part of the CMS that is being used and I don't have access to that code.
    .
    .
    I did notice that our dev server loads content and files differently than our staging and production servers, which are the same.
    .
    .
    Content loaded on dev server
    This page is executed correctly.
    Page code is only loaded once, as the Preview.aspx?id=xxxxx file.

    .
    .
    Content loaded on production and staging servers
    This page is where the error is observed.
    Page code seems to be loaded twice, as the (index) and dotd files. However, only the (index) is ever hit when breakpoints are added.

    .
    .
    I am having our server admins look into why the servers are publishing the code differently.

  • wjmccormickwjmccormick Posts: 10Questions: 2Answers: 0

    A correction to my previous post, about the dotd and (index) pages that are shown on the production server developer tools Sources tab, they are not pages (files) that are loaded, rather they are content as rendered with and without the closing "/".
    .
    dotd
    medcoe.army.mil/dotd
    .
    (index)
    medcoe.army.mil/dotd/
    .
    .
    As a work around, I could try adding a filter control within the Datatables wrapper, that will filter the same .column(1). I could then have the first dropdown control set the value of the filter control within the wrapper and trigger the onchange() event. This because of the scroll distance between the first dropdown and the datatable. Definitely not ideal, but it would give added control over the Datatable data, without the user having to scroll back up to the other dropdown control, which filters other data on the page as well.
    .
    .
    Hopefully, the cause and a solution to this issue can be found.

  • allanallan Posts: 57,236Questions: 1Answers: 9,113 Site admin

    I believe Kevin, as usual, is right on the money. You have both React and DataTables trying to control the same DOM elements here, which is causing the problem.

    The sequence of events is:

    1. DataTables is initialised
    2. React is doing something with its virtual DOM which causes the live DOM to be cloned and updated
    3. You trigger a $().DataTable() on the new table element. DataTables sees that it is a new table element (rather than the already initialised one) and thus initialises itself again - the boom you have the problem.

    So the key here is to tell React not to do anything with the table. Leave that part to DataTables. Unfortunately, I'm not well enough versed in React to know how to do that. In Vue there is a v-once attribute which tells Vue to display the element and then leave it alone (more or less). Is there something like that in React?

    Allan

  • wjmccormickwjmccormick Posts: 10Questions: 2Answers: 0

    @allan Thanks for the feedback. Unfortunately, the React code is part of the CMS that is being used and I don't have access to that code.

  • wjmccormickwjmccormick Posts: 10Questions: 2Answers: 0
    edited March 7

    I just made some changes to the code to see if this resolves the issue. I have moved the Datatable filter event listeners, that were in the main init function, to within their respective Datatables initialization functions. Once the changes are pushed to the staging server, I'll be able to see if the changes fixed the issue.
    .
    .
    Event Listeners in the init function

    .
    .
    FAQ listeners now in the faqsTable Datatable initialization

    .
    .
    Individual Training event listeners now in coursesTable Datatables initialization

  • wjmccormickwjmccormick Posts: 10Questions: 2Answers: 0

    I finally got it fixed!
    .
    In my first try at this, as described in the preceding post, I moved the Datatable Draw functions from the init function, but opting to continue to use the jQuery selector to target the Datatable table. This did NOT work, as it still resulted in the table being written again within its own wrapper.
    .
    So what was it that worked?
    I abandoned the use of the jQuery (or JavaScript) selectors and instead, being within intiComplete: function (), I referenced this.api() when calling search() or columns().
    .
    To see the fix in action, refer to the link in the Link to test case in the original post and the navigation instructions in the 2nd reply.
    .
    .
    Event Listeners in the init function

    .
    .
    FAQ listeners now in the faqsTable Datatable initialization

    .
    .
    Individual Training event listeners now in coursesTable Datatables initialization

  • colincolin Posts: 14,526Questions: 1Answers: 2,482

    Nice, thanks for posting back,

    Colin

Sign In or Register to comment.