I have a Django template html page that includes a form for users to upload a data file in JSON format. This data file is then processed, and the data is sent to my PostgreSQL database. The page displays a DataTables table populated by data from the database and three D3 charts.
Currently, when I upload a file, I can see the datatable in its raw form for several seconds before the table is fully created. As the table is large, it obscures my D3 chart’s initial animations. Ideally, I would like the initial datatable creation to be obscured by a ‘loading.gif’. When the datatable creation is complete, I want the user to see the fully formed datatable, and only then do I want the D3 charts to be created/rendered on the page.
I have included a loading.gif in my static folder, which correctly displays after file upload. However, after several seconds, the loading.gif disappears, and then, as before, I can briefly see the data in its raw form before the full datatable displays. I am unsure how to completely obscure the datatable creation (with the loading.gif) until the table is fully formed.
Here is my DataTables script (below). As you can see, I have a ‘loading’ indicator at the top of the script set to ‘block,’ and I set it to ‘none’ when the datatable is complete. I thought this would obscure the table creation until it was complete, but this is not the case.
<!-- DATATABLES -->
<script type="text/javascript">
$(document).ready(function () {
$('form').submit(function () {
// Show the loading indicator
$('#loading-indicator').css('display', 'block');
});
var table = $('#example').DataTable({
colReorder: true,
fixedColumns: false,
responsive: false,
paging: true,
scrollY: '40vh',
scrollX: true,
autoWidth: true,
pageLength: 15,
dom: 'Bfrtip',
buttons: [
{
extend: 'colvis',
postfixButtons: ['colvisRestore'],
text: 'Column Selection'
},
{
text: 'Download CSV',
extend: 'csv',
exportOptions: {
columns: ':visible'
}
}
],
columnDefs: [
{ width: '60px', targets: 0, className: 'text-left' },
{ width: '200px', targets: -1, className: 'text-left' }
],
initComplete: function () {
$('#loading-indicator').css('display', 'none');
$('#example_wrapper').css('visibility', 'visible');
}
});
});
</script>
Here is my full html template page:
{% block content %}
{% load static %}
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<link rel="shortcut icon" type="image/x-icon" href="{% static 'favicon.ico' %}">
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.2.0/css/bootstrap.min.css" rel="stylesheet"/>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.5.1.js"></script>
<link href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css" rel="stylesheet"/>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<link href="https://cdn.datatables.net/v/bs5/dt-1.13.4/b-2.3.6/b-colvis-2.3.6/b-html5-2.3.6/cr-1.6.2/r-2.4.1/sb-1.4.1/sp-2.1.2/sl-1.6.2/datatables.min.css" rel="stylesheet"/>
<script src="https://cdn.datatables.net/v/bs5/dt-1.13.4/b-2.3.6/b-colvis-2.3.6/b-html5-2.3.6/cr-1.6.2/r-2.4.1/sb-1.4.1/sp-2.1.2/sl-1.6.2/datatables.min.js"></script>
<script src="{% static 'd3_dots.js' %}"></script>
<script src="https://d3js.org/d3.v7.js"></script>
<script src="{% static 'd3_bar.js' %}"></script>
<style>
#example_wrapper {
visibility: hidden;
}
table {
font-size: 12px;
}
.dataTables_wrapper .dataTables_paginate .paginate_button {
padding: 1px;
background: white;
color: white;
}
.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
background-color: red;
}
.dataTables_wrapper .dt-button-collection .buttons-columnVisibility .dt-button.active {
background-color: red;
}
.text-left {
text-align: left !important;
white-space: nowrap;
}
.buttons-collection .dt-button:first-child {
background-color: red !important;
color: white !important;
}
.buttons-csv {
background-color: red !important;
color: white !important;
}
.upload-form button {
border-radius: 5px;
background: #E9ECEF;
border: 1px solid #CED4DA;
}
.bar {
fill: #0c6cfc;
stroke: #FFA500;
stroke-width: 1px;
}
.bar:hover {
fill: #FFA500;
stroke: #000000;
stroke-width: 2px;
}
#chart-wrapper {
display: flex;
flex-direction: row;
}
#chart-level-assign,
#chart-edu-setting
#chart-smd-scatter {
width: 33.33%;
}
#loading-indicator {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.9);
z-index: 9999;
text-align: center;
padding-top: 20%;
background-image: url('/static/loading.gif');
background-repeat: no-repeat;
background-position: center;
}
#loading-indicator img {
width: 100px;
height: 100px;
}
</style>
</head>
<body>
<div id="loading-indicator">
</div>
<div class="container mt-5">
<div class="row">
<div class="col-md-12">
<form method="post" enctype="multipart/form-data" class="upload-form">
{% csrf_token %}
<div class="row mb-3">
<div class="col-md-6">
{{ form.json_file }}
</div>
<div class="col-md-6 d-flex align-items-left justify-content-end">
<button type="submit" name="upload_button" class="btn-primary">Upload</button>
</div>
</div>
</form>
{% if has_data %}
<br>
<table id="example" class="display hover">
<thead>
<tr>
<th>#</th>
<th>EPPI ID</th>
<th>Admin Strand</th>
<th>Educational Setting</th>
</tr>
</thead>
<tbody>
{% for data_object in data %}
<tr>
<th>{{ forloop.counter }}</th>
<td>{{ data_object.eppi_id }}</td>
<td>{{ data_object.admin_strand_data }}</td>
<td>{{ data_object.edu_setting_data }}</td>
</tr>
{% empty %}
<tr>
<td colspan="3">No data to display.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
<br>
<br>
<div id="chart-wrapper">
<div id="chart-level-assign"></div>
<div id="chart-edu-setting"></div>
<div id="chart-smd-scatter"></div>
</div>
<!-- DATATABLES -->
<script type="text/javascript">
$(document).ready(function () {
$('form').submit(function () {
// Show the loading indicator
$('#loading-indicator').css('display', 'block');
});
var table = $('#example').DataTable({
colReorder: true,
fixedColumns: false,
responsive: false,
paging: true,
scrollY: '40vh',
scrollX: true,
autoWidth: true,
pageLength: 15,
dom: 'Bfrtip',
buttons: [
{
extend: 'colvis',
postfixButtons: ['colvisRestore'],
text: 'Column Selection'
},
{
text: 'Download CSV',
extend: 'csv',
exportOptions: {
columns: ':visible'
}
}
],
columnDefs: [
{ width: '60px', targets: 0, className: 'text-left' },
{ width: '200px', targets: -1, className: 'text-left' }
],
initComplete: function () {
$('#loading-indicator').css('display', 'none');
$('#example_wrapper').css('visibility', 'visible');
}
});
});
</script>
<!-- D3 CHARTS -->
<script type="text/javascript">
// First chart
const assign_level_rawData = JSON.parse('{{ assign_levels|safe }}');
console.log(assign_level_rawData)
for (let i = 0; i < assign_level_rawData.length; i++) {
if (assign_level_rawData[i][0] === "N") {
assign_level_rawData[i][0] = "NA";
}
}
const data1 = assign_level_rawData.map(d => ({level_assign: d[0], count: d[1]}));
createChart(data1, "chart-level-assign", "Level of Assignment", "level_assign");
// Second chart
const edu_setting_rawData = JSON.parse('{{ edu_setting_levels|safe }}');
for (let i = 0; i < edu_setting_rawData.length; i++) {
if (edu_setting_rawData[i][0] === "N") {
edu_setting_rawData[i][0] = "NA";
}
}
const data2 = edu_setting_rawData.map(d => ({edu_setting: d[0], count: d[1]}));
createChart(data2, "chart-edu-setting", "Educational Setting", "edu_setting");
// Third chart
const data3 = "{{ smd_combined_csv|escapejs }}";
const smd = d3.csvParse(data3).filter(d => d.smd_value !== "NA");
drawDotChart(smd);
</script>
</body>
</html>
{% endblock %}
2
Answers
You can use “draw” event delegate instead of “initComplete”.
While the DataTable is loading, you can also use the
processing
option to display a loading message or animation instead of initComplete and draw.Here we define processing as true and display a loading message using the language option. The custom message will be automatically deleted after the DataTable is loaded.
You can personalize the loading message. In this snippet, a spinner animation is displayed using an icon from the Font Awesome icon set.