Skip to content
Snippets Groups Projects
smash.js 20.9 KiB
Newer Older
Valentin Groues's avatar
Valentin Groues committed
$(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">&times;</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));
            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";
Carlos Vega's avatar
Carlos Vega committed
            text = `<span title="Visit is finished, some appointments were not carried out.">MISSED</span>`;
        } else if (visit.status === "UPCOMING") {
            color = "#00ffff";
Carlos Vega's avatar
Carlos Vega committed
            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);

Carlos Vega's avatar
Carlos Vega committed
        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 () {
Carlos Vega's avatar
Carlos Vega committed
        $(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>');
Carlos Vega's avatar
Carlos Vega committed
    //make columns of virus test date wider
    $(tableElement).find('tfoot div[name="virus_test_date"]').each(function () {
        $(this).css('width', '100px');
        $(this).text('');
    });

Carlos Vega's avatar
Carlos Vega committed
    //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('&#8209;');
        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>' +
    $(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"]],
            deferRender: false,
            responsive: true,
            ajax: subjects_url,
            columns: columns,
            columnDefs: columnDefs,
Carlos Vega's avatar
Carlos Vega committed
            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
//------------------------------------------------------