Draw is Redrawing Within Itself
Draw is Redrawing Within Itself
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
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
When you click on the link in the original post, you will come to a bookmark within the page, that is an accordion.
Click on the first accordion item How do I train for my job? [Individual Training]
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.
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 onHow do I train for my job? [Individual Training]
some code inreact-dom.production.min.js
executes. I'm not familiar with React but I wonder if its doing something to duplicate thecoursesTable_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
@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.
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 theonchange()
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.
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:
$().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 av-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
@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.
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
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 referencedthis.api()
when callingsearch()
orcolumns()
..
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
Nice, thanks for posting back,
Colin