$(document).ready(function () { $("#save-and-continue").click(function () { var form = $(this).parents("form"); var hidden_field = $("<input type='hidden' name='_continue' value='1' />"); form.append(hidden_field); form.submit(); }); $("[data-hide]").on("click", function () { $("." + $(this).attr("data-hide")).hide(); }); var formDataChanged = false; $(":input", this).change(function () { if (this.form !== undefined && this.form !== null) { formDataChanged = true; } }); $('.main-sidebar a').click(function () { if (formDataChanged) { return confirm("Data changed. Are you sure you want to leave?"); } else { return true; } }); //disable all dropdown HTML select elements that should be readonly but aren't $("select[readonly] option:not(:selected)").attr('disabled', 'disabled'); }); $.ajaxSetup({ beforeSend: function (xhr, settings) { function getCookie(name) { var cookieValue = null; if (document.cookie && document.cookie != '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = jQuery.trim(cookies[i]); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) == (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) { // Only send the token to relative URLs i.e. locally. xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); } } }); function showErrorInfo(content) { var errorDialogDiv = document.createElement("div"); document.body.appendChild(errorDialogDiv); errorDialogDiv.innerHTML = '<div class="modal-dialog" role="document">' + '<div class="modal-content">' + '<div class="modal-header">ERROR' + '<button type="button" class="close" data-dismiss="modal" aria-label="Close">' + '<span aria-hidden="true">×</span></button>' + '</div>' + '<div name="error-content" class="modal-body">' + content + '</div>' + '<div class="modal-footer">' + '<button type="button" class="btn btn-outline pull-right" data-dismiss="modal">Close</button>' + '</div>' + '</div>' + '</div>'; $(errorDialogDiv).attr("role", "dialog"); $(errorDialogDiv).addClass("modal modal-danger fade"); $(errorDialogDiv).modal("show"); } //------------------------------------------------------ // common code for dynamic Data Tables //------------------------------------------------------ if (!String.prototype.startsWith) { String.prototype.startsWith = function (searchString, position) { position = position || 0; return this.indexOf(searchString, position) === position; }; } function createColumn(dataType, name, filter, visible, sortable, renderFunction) { if (renderFunction === undefined) { renderFunction = function (data, type, row, meta) { return row[dataType]; } } return { "type": dataType, "name": name, "filter": filter, "visible": visible, "render": renderFunction, "sortable": sortable }; } /* We use an auxiliary function to create the function due to the lack of block scope in JS. https://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example */ function createRenderFunction(columnRow){ return function(data, type, row, meta){ /* Fancy highlighting for the column matches. */ if(columnRow.filter == 'string_filter'){ //obtain the input element by its placeholdername which matches the column name: e.g.: <input type="text" style="width:80px" placeholder="First name"> filter_value = $(`input[placeholder="${columnRow.name}"]`).val(); //if there is any filter, we highlight the matching part if(filter_value != undefined && filter_value.length > 0){ //global and case insensitive replacement return data.replace(RegExp(filter_value, 'gi'), `<span class="highlight_match">${filter_value}</span>`); } } return data; }; } function getColumns(columns, getSubjectEditUrl) { var result = []; for (var i = 0; i < columns.length; i++) { var columnRow = columns[i]; if (columnRow.type === "address"){ var renderFunction = (function () { return function (data, type, row) { return data; }; })(); var columnDef = createColumn(columnRow.type, columnRow.name, columnRow.filter, columnRow.visible, columnRow.sortable, renderFunction); columnDef.className = 'subject-address'; columnDef.width = '150px'; result.push(columnDef); } else if (columnRow.type === "edit") { result.push(createColumn("id", columnRow.name, columnRow.filter, columnRow.visible, columnRow.sortable, function (data, type, row) { var url = getSubjectEditUrl(row.id.toString()); return '<a href="' + url + '" type="button" class="btn btn-block btn-default">Edit</a>'; })); } else if (/^visit_[0-9]+$/.test(columnRow.type)) { var renderFunction = (function () { var x = columnRow.type.replace("visit_", ""); return function (data, type, row) { return create_visit_row(row.visits[x - 1]); }; })(); result.push(createColumn(columnRow.type, columnRow.name, columnRow.filter, columnRow.visible, columnRow.sortable, renderFunction)); } else { result.push(createColumn(columnRow.type, columnRow.name, columnRow.filter, columnRow.visible, columnRow.sortable, createRenderFunction(columnRow))); } } return result; } function createHeader(columnsDefinition) { var header = document.createElement("thead"); var headerRow = document.createElement("tr"); header.appendChild(headerRow); for (var i = 0; i < columnsDefinition.length; i++) { var column = columnsDefinition[i]; var element = document.createElement("th"); element.innerHTML = column.name; headerRow.appendChild(element); } return header; } function createFilter(columnsDefinition) { var footer = document.createElement("tfoot"); footer.style.display = "table-header-group"; var footerRow = document.createElement("tr"); footer.appendChild(footerRow); for (var i = 0; i < columnsDefinition.length; i++) { var column = columnsDefinition[i]; var element = document.createElement("th"); if (column.filter !== null) { element.innerHTML = "<div name='" + column.filter + "'>" + column.name + "</div>"; } footerRow.appendChild(element); } return footer; } function create_visit_row(visit) { var color = "white"; var text = "---"; if (visit !== undefined && visit !== null) { if (visit.status === "DONE") { color = "#6bff5a"; text = `<span title="Visit is finished, all appointments done.">OK</span>`; } else if (visit.status === "MISSED") { color = "pink"; text = `<span title="Visit is finished, some appointments were not carried out.">MISSED</span>`; } else if (visit.status === "UPCOMING") { color = "#00ffff"; text = `<span title="Visit has not started yet.">UPCOMING</span>`; } else if (visit.status === "EXCEEDED") { color = "orange"; text = `<span title="Visit is over and no appointments were set.">EXCEEDED</span>`; } else if (visit.status === "SHOULD_BE_IN_PROGRESS") { color = "orange"; text = `<span title="Visit has started but no appointments have been set yet.">IN PROGRESS (NO APPOINTMENTS)</span>`; } else if (visit.status === "IN_PROGRESS") { color = "lightgreen"; text = `<span title="Appointments are taking place.">IN PROGRESS</span>`; } var start_date = moment(visit.datetime_start); var end_date = moment(visit.datetime_end); text += ` <br/> <span data-html="true" title="From: ${start_date.format('ddd Do MMMM YYYY')} </br> To: ${end_date.format('ddd Do MMMM YYYY')}"> From: ${start_date.format('D MMM. YYYY')} </span> <br/> <span data-html="true" title="From: ${start_date.format('ddd Do MMMM YYYY')} </br> To: ${end_date.format('ddd Do MMMM YYYY')}"> To: ${end_date.format('D MMM. YYYY')} </span>` text += `<br/><span data-html="true" title="Visit details<br/>Appointment Types:<br/><div class='appointment_type_list'>${visit.appointment_types.join('<br/>')}</div>"> <a href="${visit.edit_visit_url}"><i class="fa fa-list" aria-hidden="true"></i></a> </span>`; if(!visit.is_finished){ text += `<span title="Add new appointment to visit"><a href="${visit.add_appointment_url}"><i class="fa fa-plus-square-o" aria-hidden="true"></i></a></span>`; }else{ text += `<span title="Visit is marked as finished" ><i class="fa fa-check-circle" aria-hidden="true"></i></span>`; } } return "<div class='visit_row' style='background-color:" + color + "';width:100%;height:100%>" + text + "</div>"; } function createVisibilityCheckboxes(checkboxesElement, columns) { var row = null; for (var i = 0; i < columns.length; i++) { if (i % 10 === 0) { row = document.createElement("div"); row.style.display = "table-row"; checkboxesElement.appendChild(row); } var column = columns[i]; var element = document.createElement("div"); element.style.display = "table-cell"; var checked = ""; if (column.visible) { checked = "checked"; } element.innerHTML = "<input type='checkbox' " + checked + " data-column='" + i + "' name='" + column.type + "'/>" + column.name; row.appendChild(element); } } function createTable(params) { var tableElement = params.tableElement; var worker_locations = params.worker_locations; if (worker_locations === undefined) { worker_locations = []; } var subject_types_url = params.subject_types_url; var locations_url = params.locations_url; var flying_teams_url = params.flying_teams_url; var appointment_types_url = params.appointment_types_url; var subjects_url = params.subjects_url; var voucher_types_url = params.voucher_types_url; var voucher_partner_url = params.voucher_partner_url; var columnsDefinition = params.columns; var dom_settings = params.dom_settings || 'lrtip' // show table without search box; tableElement.appendChild(createHeader(columnsDefinition)); tableElement.appendChild(createFilter(columnsDefinition)); tableElement.appendChild(document.createElement("tbody")); createVisibilityCheckboxes(params.checkboxesElement, columnsDefinition); var table; $(tableElement).find('tfoot div[name="string_filter"]').each(function () { var title = $(this).text(); $(this).html('<input type="text" style="width:80px" placeholder="' + title + '" />'); }); $(tableElement).find('tfoot div[name="yes_no_filter"]').each(function () { $(this).html('<select style="width:60px" ><option value selected="selected">---</option><option value="true">YES</option><option value="false">NO</option></select>'); }); $(tableElement).find('tfoot div[name="yes_no_null_filter"]').each(function () { $(this).html('<select style="width:60px" ><option value selected="selected">---</option><option value="true">YES</option><option value="false">NO</option><option value="null">N/A</option></select>'); }); $(tableElement).find('tfoot div[name="yes_no_null_inconclusive_filter"]').each(function () { $(this).html('<select style="width:100px" ><option value selected="selected">---</option><option value="true">Positive</option><option value="false">Negative</option><option value="null">N/A</option><option value="inconclusive">Inconclusive</option></select>'); }); //make columns of virus test date wider $(tableElement).find('tfoot div[name="virus_test_date"]').each(function () { $(this).css('width', '100px'); $(this).text(''); }); //replace the hyphen with non breaking space hyphen to ensure the column names are more readable $(tableElement).find('th:contains("RT-PCR")').each(function(){ non_breaking_hyphen = $.parseHTML('‑'); var text = $(this).text().replace('-', $(non_breaking_hyphen).text()); $(this).text(text); }); $(tableElement).find('tfoot div[name="integer_filter"]').each(function () { var options = '<option value selected="selected">---</option>'; for (var i = 1; i <= 8; i++) { options += '<option value="' + i + '">' + i + '</option>'; } $(this).html('<select style="width:60px" >' + options + '</select>'); }); $(tableElement).find('tfoot div[name="from_zero_integer_filter"]').each(function () { var options = '<option value selected="selected">---</option>'; for (var i = 0; i < 8; i++) { options += '<option value="' + i + '">' + i + '</option>'; } $(this).html('<select style="width:60px" >' + options + '</select>'); }); $(tableElement).find('tfoot div[name="voucher_status_filter"]').each(function () { $(this).html('<select style="width:60px" ><option value selected="selected">---</option>' + '<option value="NEW">NEW</option>' + '<option value="IN_USE">IN USE</option>' + '<option value="USED">USED</option>' + '<option value="EXPIRED">EXPIRED</option>' + '<option value="REMOVED">REMOVED</option>' + '</select>'); }); $(tableElement).find('tfoot div[name="visit_filter"]').each(function () { $(this).html('<select style="width:60px" >' + '<option value selected="selected">---</option>' + '<option value="MISSED">MISSED</option>' + '<option value="DONE">DONE</option>' + '<option value="EXCEED">EXCEED</option>' + '<option value="IN_PROGRESS">IN PROGRESS</option>' + '<option value="SHOULD_BE_IN_PROGRESS">SHOULD BE IN PROGRESS</option>' + '<option value="UPCOMING">UPCOMING</option>' + '</select>'); }); $(tableElement).find('tfoot div[name="location_filter"]').each(function () { var obj = $(this); obj.html('<select style="width:80px"><option value selected="selected">---</option></select>'); var select = $('select', obj); $.get(locations_url, function (data) { $.each(data.locations, function (index, location) { select.append('<option value="' + location.id + '">' + location.name + '</option>'); }); if (worker_locations.length === 1) { select.val(worker_locations[0].id); } }); }); $(tableElement).find('tfoot div[name="voucher_type_filter"]').each(function () { var obj = $(this); obj.html('<select style="width:80px"><option value selected="selected">---</option></select>'); var select = $('select', obj); $.get(voucher_types_url, function (content) { $.each(content.data, function (index, voucher_type) { select.append('<option value="' + voucher_type.id + '">' + voucher_type.code + '</option>'); }); }); }); $(tableElement).find('tfoot div[name="voucher_partner_filter"]').each(function () { var obj = $(this); obj.html('<select style="width:80px"><option value selected="selected">---</option></select>'); var select = $('select', obj); $.get(voucher_partner_url, function (content) { $.each(content.data, function (index, voucher_partner) { select.append('<option value="' + voucher_partner.id + '">' + voucher_partner.name + '</option>'); }); }); }); $(tableElement).find('tfoot div[name="flying_team_filter"]').each(function () { var obj = $(this); obj.html('<select style="width:80px"><option value selected="selected">---</option></select>'); var select = $('select', obj); $.get(flying_teams_url, function (data) { $.each(data.flying_teams, function (index, flying_team) { select.append('<option value="' + flying_team.id + '">' + flying_team.name + '</option>'); }); if (worker_locations.length === 1) { select.val(worker_locations[0].id); } }); }); $(tableElement).find('tfoot div[name="appointment_type_filter"]').each(function () { var obj = $(this); obj.html('<select style="width:80px"><option value selected="selected">---</option></select>'); var select = $('select', obj); $.get(appointment_types_url, function (data) { $.each(data.appointment_types, function (index, appointment_type) { select.append('<option value="' + appointment_type.id + '">' + appointment_type.type + '</option>'); }); if (worker_locations.length === 1) { select.val(worker_locations[0].id); } }); }); $(tableElement).find('tfoot div[name="type_filter"]').each(function () { var obj = $(this); obj.html('<select style="width:80px"><option value selected="selected">---</option></select>'); var select = $('select', obj); $.get(subject_types_url, function (data) { $.each(data.types, function (index, type) { select.append('<option value="' + type.id + '">' + type.name + '</option>'); }); }); }); $(function () { var columns = []; var columnDefs = []; for (var i = 0; i < columnsDefinition.length; i++) { var column = columnsDefinition[i]; columns.push({"data": column.type}); columnDefs.push({ "targets": i, "render": column.render, visible: column.visible, className: column.className, width: column.width, orderable: column.sortable }); } table = $('#table').DataTable({ pageLength: 25, lengthMenu: [[25, 50, 100, -1], [25, 50, 100, "All"]], serverSide: true, processing: true, deferRender: false, responsive: true, ajax: subjects_url, columns: columns, columnDefs: columnDefs, order: [[0, 'desc']], buttons: [ { extend: 'excel', exportOptions: { columns: ':visible', modifier: { page: 'all', search: 'applied', order: 'applied' } }, text: `<span class='excel-export-button'><i class="fa fa-file-excel-o" aria-hidden="true"></i> Export selection to excel</span>` } ], dom: dom_settings, //see docs: https://datatables.net/reference/option/dom initComplete: function(settings, json) { $('.excel-export-button').parents('button').tooltip({container: 'body', title: `Show <b>All</b> entries to consider all rows instead of current page. Filters and sorting will prevail.`, html: true}); } }); // Apply the search table.columns().every(function () { var that = this; $('input', this.footer()).on('keyup change', function () { if (that.search() !== this.value) { that.search(this.value).draw(); } }); $('select', this.footer()).on('keyup change', function () { if (that.search() !== this.value) { that.search(this.value).draw(); } }); }); $('#table_filter').css("display", "null"); }); $(params.checkboxesElement).find('input').on('click', function (e) { var visible = $(this).is(":checked"); // Get the column API object var column = table.column($(this).attr('data-column')); // Toggle the visibility column.visible(visible); }); } //------------------------------------------------------ // END common code for dynamic Data Tables //------------------------------------------------------