/*
    $Revision$
    $Id$

    File:           console.js

    Description:    Generic include file for consoles for Facilitor


*/

var targets = {},
    models = {},
    day_in_ms = 86400000,
    language = "EN",
    windowObjectReference,
    environment = "production";

/* System constants */
var dataTables_i18n = {
    NL: {
        "sProcessing": "Bezig...",
        "sLengthMenu": "_MENU_ resultaten weergeven",
        "sZeroRecords": "Geen resultaten gevonden",
        "sInfo": "_START_ tot _END_ van _TOTAL_ resultaten",
        "sInfoEmpty": "Geen resultaten om weer te geven",
        "sInfoFiltered": " (gefilterd uit _MAX_ resultaten)",
        "sInfoPostFix": "",
        "sSearch": "Zoeken:",
        "sEmptyTable": "Geen resultaten aanwezig in de tabel",
        "sInfoThousands": ".",
        "sLoadingRecords": "Een moment geduld aub - bezig met laden...",
        "oPaginate": {
            "sFirst": "Eerste",
            "sLast": "Laatste",
            "sNext": "Volgende",
            "sPrevious": "Vorige"
        },
        "oAria": {
            "sSortAscending": ": activeer om kolom oplopend te sorteren",
            "sSortDescending": ": activeer om kolom aflopend te sorteren"
        }
    },
    DE: {
        "sEmptyTable": "Keine Daten in der Tabelle vorhanden",
        "sInfo": "_START_ bis _END_ von _TOTAL_ Einträgen",
        "sInfoEmpty": "Keine Daten vorhanden",
        "sInfoFiltered": "(gefiltert von _MAX_ Einträgen)",
        "sInfoPostFix": "",
        "sInfoThousands": ".",
        "sLengthMenu": "_MENU_ Einträge anzeigen",
        "sLoadingRecords": "Wird geladen ..",
        "sProcessing": "Bitte warten ..",
        "sSearch": "Suchen",
        "sZeroRecords": "Keine Einträge vorhanden",
        "oPaginate": {
            "sFirst": "Erste",
            "sPrevious": "Zurück",
            "sNext": "Nächste",
            "sLast": "Letzte"
        },
        "oAria": {
            "sSortAscending": ": aktivieren, um Spalte aufsteigend zu sortieren",
            "sSortDescending": ": aktivieren, um Spalte absteigend zu sortieren"
        },
        "select": {
            "rows": {
                "_": "%d Zeilen ausgewählt",
                "0": "",
                "1": "1 Zeile ausgewählt"
            }
        },
        "buttons": {
            "print": "Drucken",
            "colvis": "Spalten",
            "copy": "Kopieren",
            "copyTitle": "In Zwischenablage kopieren",
            "copyKeys": "Taste <i>ctrl</i> oder <i>\u2318</i> + <i>C</i> um Tabelle<br>in Zwischenspeicher zu kopieren.<br><br>Um abzubrechen die Nachricht anklicken oder Escape drücken.",
            "copySuccess": {
                "_": "%d Zeilen kopiert",
                "1": "1 Zeile kopiert"
            },
            "pageLength": {
                "-1": "Zeige alle Zeilen",
                "_": "Zeige %d Zeilen"
            }
        }
    },
    EN: {
        "sEmptyTable": "No data available in table",
        "sInfo": "Showing _START_ to _END_ of _TOTAL_ entries",
        "sInfoEmpty": "Showing 0 to 0 of 0 entries",
        "sInfoFiltered": "(filtered from _MAX_ total entries)",
        "sInfoPostFix": "",
        "sInfoThousands": ",",
        "sLengthMenu": "Show _MENU_ entries",
        "sLoadingRecords": "Loading...",
        "sProcessing": "Processing...",
        "sSearch": "Search:",
        "sZeroRecords": "No matching records found",
        "oPaginate": {
            "sFirst": "First",
            "sLast": "Last",
            "sNext": "Next",
            "sPrevious": "Previous"
        },
        "oAria": {
            "sSortAscending": ": activate to sort column ascending",
            "sSortDescending": ": activate to sort column descending"
        }
    },
    FR: {
        "sEmptyTable": "Aucune donnée disponible dans le tableau",
        "sInfo": "Affichage de l'élément _START_ à _END_ sur _TOTAL_ éléments",
        "sInfoEmpty": "Affichage de l'élément 0 à 0 sur 0 élément",
        "sInfoFiltered": "(filtré à partir de _MAX_ éléments au total)",
        "sInfoPostFix": "",
        "sInfoThousands": ",",
        "sLengthMenu": "Afficher _MENU_ éléments",
        "sLoadingRecords": "Chargement...",
        "sProcessing": "Traitement...",
        "sSearch": "Rechercher :",
        "sZeroRecords": "Aucun élément correspondant trouvé",
        "oPaginate": {
            "sFirst": "Premier",
            "sLast": "Dernier",
            "sNext": "Suivant",
            "sPrevious": "Précédent"
        },
        "oAria": {
            "sSortAscending": ": activer pour trier la colonne par ordre croissant",
            "sSortDescending": ": activer pour trier la colonne par ordre décroissant"
        },
        "select": {
            "rows": {
                "_": "%d lignes sélectionnées",
                "0": "Aucune ligne sélectionnée",
                "1": "1 ligne sélectionnée"
            }
        }
    }
};

/* global functions */
// console messages for development support
window.console.original_log = window.console.log;
window.console.log = function (msg, force) {
    if (environment == "development" || force) {
        window.console.original_log(msg);
    }
};
// return the data of a single object or of a row object (e.g. obj.data["issue"] or obj.data["issues"][row])
function rowInfo(obj, row) {
    "use strict";
    return (obj.single ? obj.data[obj.table] : obj.data[obj.table][row]);
}

// return the value of a column
function dataInfo(obj, row, column) {
    "use strict";
    return rowInfo(obj, row)[column];
}

// return a formatted column value according to the column_type
function typeFormat(parms) {
    "use strict";
    switch (parms.column_type) {
        case "datetime":
            if (parms.single) {
                // return the datevalue in case of a single object column
                return new Date(parms.value);
            }
            return new Intl.DateTimeFormat(undefined, {
                dateStyle: 'medium',
                timeStyle: 'short',
                hour: '2-digit',
                minute: '2-digit'
            }).format(new Date(parms.value));
            // niet na een return break;
        case "date":
            if (parms.single) {
                // return the datevalue in case of a single object column
                return new Date(parms.value);
            }
            return new Intl.DateTimeFormat(undefined, {
                dateStyle: 'medium'
            }).format(new Date(parms.value));
            // niet na een return break;
        case "integer":
            return parseInt(parms.value, 10);
            // niet na een return break;
        case "number":
            // return safe number
            return $.fn.dataTable.render.number().display(parms.value);
            // niet na een return break;
        default:
            // return safe text
            return $.fn.dataTable.render.text().display(parms.value);
    }
}

// return label of the column
function columnType(obj, column) {
    "use strict";
    // determine column_type from [ targets[target].columns[column].type ] or from [ models[model].field[column].type ] or from fallback value "text"
    return (obj.columns[column].type || (models[obj.url].fields[column] && models[obj.url].fields[column].type) || "text");
}

// return label of the column
function columnLabel(obj, column) {
    "use strict";
    // determine label from [ models[model].field[column].label ] or else from [ target.columns[column].label ] or else from [ column name ]
    return ((models[obj.url].fields[column] && models[obj.url].fields[column].label) || obj.columns[column].label || column);
}

// return value of the column (single value or [column].name value)
function columnValue(data) {
    "use strict";
    // determine whether the value is an object with a { name: value }
    return (data && typeof data.name !== "undefined" ? data.name || "" : data || "");
}

// return the formatted column value, independent of single object or row object
function dataFormat(obj, row, column) {
    "use strict";
    // get column value (as single value or as { id: xx, name: yy })
    var data = dataInfo(obj, row, column);
    return typeFormat({
        value: columnValue(data),
        column_type: columnType(obj, column),
        single: obj.single
    });
}

// return combined server-data and targets[target]/models[model][column] information object
function columnInfo(obj, row, column) {
    "use strict";
    var data, column_label, column_data, column_type, column_valuemin, column_valuemax, column_recid;
    // get column value (as single value or as { id: xx, name: yy })
    data = dataInfo(obj, row, column);
    // get column label
    column_label = columnLabel(obj, column);
    // get formatted column value
    column_data = dataFormat(obj, row, column);
    // get column_type
    column_type = columnType(obj, column);
    // determine minimum value (only for use in a progressbar)
    column_valuemin = (obj.columns[column].valuemin || 0);
    // determine maximum value (only for use in a progressbar)
    column_valuemax = (obj.columns[column].valuemax || 100);
    // determine record id in case the column is a foreign table value
    column_recid = (data && typeof data.id !== "undefined" ? parseInt(data.id, 10) : false);
    return {
        data: data,
        column_label: column_label,
        column_data: column_data,
        column_type: column_type,
        column_valuemin: column_valuemin,
        column_valuemax: column_valuemax,
        column_recid: column_recid
    };
}

// return the column [ data-order ] value, used for ordering DataTable rows ([ date ] or [ datetime ] serial number only)
function dataOrder(obj, row, column) {
    "use strict";
    var column_type = columnType(obj, column),
        value, data = dataInfo(obj, row, column);
    if (obj.single) {
        value = "";
    } else {
        value = columnValue(data);
    }
    switch (column_type) {
        case "datetime":
        case "date":
            return new Date(value).getTime();
            // niet na een return break;
        default:
            return false;
    }
}

// return DataTable settings based on targets[target]columns
function tColumnDefs(obj) {
    "use strict";
    var defs = {
            language: dataTables_i18n[language],
            columnDefs: []
        },
        index = 0;
    $.each(obj.columns, function (key, value) {
        if (key !== "id") {
            if (value.hidden) {
                defs.columnDefs.push({
                    targets: [index],
                    visible: false,
                    searchable: true
                });
            }
            index++;
        }
    });
    console.log(defs);
    return defs;
}

// return tableHeader row
function theadColumns(obj) {
    "use strict";
    var thead = "",
        column;
    for (column in obj.columns) {
        if (column !== "id") {
            thead += '<th>' + $.fn.dataTable.render.text().display(columnLabel(obj, column)) + '</th>';
        }
    }
    return thead;
}

// return tableBody rows
function tbodyData(obj) {
    "use strict";
    var tbody = "",
        row, column, order, col_info;
    for (row = 0; row < obj.data[obj.table].length; row += 1) {
        tbody += '<tr recid=' + obj.data[obj.table][row].id + '>';
        for (column in obj.columns) {
            if (column !== "id") {
                col_info = columnInfo(obj, row, column);
                tbody += '<td name="' + column + '"' +
                    (col_info.column_recid ? 'recid="' + col_info.column_recid + '"' : '') +
                    ((order = dataOrder(obj, row, column)) ? 'data-order="' + order + '"' : '') +
                    '>' +
                    col_info.column_data +
                    '</td>';
            }
        }
        tbody += '</tr>';
    }
    return tbody;
}

// return formdata (single record form)
function formData(obj) {
    "use strict";
    var target_selector = targets[obj.target].selector;
    var formbody = "",
        column, column_id, col_info;
    for (column in obj.columns) {
        if (column !== "id" && !(targets[obj.target].columns[column].hidden || false)) {
            column_id = target_selector + "-" + column;
            col_info = columnInfo(obj, undefined, column);
            col_info.column_id = column_id;
            col_info.single = obj.single;
            formbody +=
                '<div class="form-group form-group-sm pb-0 mb-0 form-row">' +
                '<label for="' + column_id + '" class="col-sm-4 col-form-label text-nowrap">' + col_info.column_label + '</label>' +
                new Field(col_info).render() +
                '</div>';
        }
    }
    return formbody;
}

// return filterdata (buttons for filtering DataTable data)
function filterData(obj) {
    "use strict";
    //    var container_selector = targets[obj.target].selector;
    var formbody = "",
        row, column, col_info,
        btn_size_class = targets[obj.target].btn_size_class,
        btn_has_icon = targets[obj.target].btn_has_icon,
        icon_column = targets[obj.target].icon_column,
        color_column = targets[obj.target].color_column,
        icon;
    for (row = 0; row < obj.data[obj.table].length; row += 1) {
        if (icon_column && !!obj.data[obj.table][row][icon_column] && obj.data[obj.table][row][icon_column].substring(0, 3) == "fa-") {
            icon = obj.data[obj.table][row][icon_column];
        } else {
            icon = "fa-tag";
        }
        formbody += '<button type="button" class="btn text-center ' + btn_size_class + ' ' + settings.btn_text_class + ' ' + settings.btn_class + '" recid=' + obj.data[obj.table][row].id + (color_column && !!obj.data[obj.table][row][color_column] ? ' color="' + obj.data[obj.table][row][color_column].split(";")[0] + '"' : "") + '>' +
            '<div class="">' + (btn_has_icon ? '<i class="fa ' + icon + ' fa-lg"></i>' : "") + '</div>';
        for (column in obj.columns) {
            if (column !== "id") {
                col_info = columnInfo(obj, row, column);
                formbody += '<div class="' + (targets[obj.target].columns[column].hidden ? "" : "d-inline-block text-truncate") + '" name="' + column + '"' +
                    ((targets[obj.target].columns[column].hidden || false) ? "hidden" : '') +
                    (col_info.column_recid ? ' recid=' + col_info.column_recid : '') + '>' +
                    "<small>" +
                    ((targets[obj.target].columns[column].hidden || false) ? '' : col_info.column_data) +
                    '</small></div>';
            }
        }
        formbody += '<div name="badge"></div>';
        formbody += '</button>';
    }
    return formbody;
}

// populate the target (datatable, form or filter)
function populate(obj) {
    "use strict";
    // the model needs to be present (loaded) for correct workings, except for reports (graphs)
    if (targets[obj.target].report || models[obj.url].id) {
        var $this = $("#" + targets[obj.target].selector),
            h2;
        obj.columns = obj.columns || targets[obj.target].columns;
        console.log("populate: " + obj.target);
        obj.table = obj.single || obj.url;
        h2 = '<h2 name="formtitle">' + (lcl[language][targets[obj.target].title] || obj.table) + '</h2>';
        obj.single = obj.single || false;
        // render single record form html
        if (obj.single) {
            var fdata = formData(obj);
            $this.html(h2 + '<form id="' + targets[obj.target].selector + '-form">' + fdata + '</form>' + new Customfields(obj).render());
            // render buttons from rows for filter form html
        } else if (targets[obj.target].filter) {
            var fdata = filterData(obj);
            $this.html(h2 + '<form id="' + targets[obj.target].selector + '-form" class="form-inline">' + fdata + '</form>');
            // render h2 header only for report
        } else if (targets[obj.target].report) {
            $this.html(h2);
            // render table html
        } else {
            var $this, thead, tbody, table;
            thead = '<thead class="thead-dark">' + theadColumns(obj) + '</thead>';
            tbody = '<tbody>' + tbodyData(obj) + '</tbody>';
            table = '<table class="table table-sm table-striped table-hover" name="' + obj.target + '">' + thead + tbody + '</table>';
            $this.html(h2 + table);
        }
        // set trigger(s) if available
        if (targets[obj.target].trigger) {
            $(targets[obj.target].trigger_selector, $this).off().on(targets[obj.target].events, targets[obj.target].trigger);
        }
        // run ready function if available
        if (targets[obj.target].ready) {
            targets[obj.target].ready(obj);
        }
    } else {
        console.log("model " + obj.url + "not yet present!");
        // retry the populate until the model is present
        setTimeout(function () {
            populate(obj);
        }, 100);
    }
}

// save the model definition in models
function setModel(obj) {
    "use strict";
    if (obj.model) {
        models[obj.model] = obj.data;
        obj.data.fields.forEach(function (field) {
            models[obj.model].fields[field.id] = field;
        });
    }
}

// get api2 data through ajax
function api2(obj) {
    "use strict";
    if (obj.target) {
        obj.filter = (Object.keys(targets[obj.target].columns).length ? (obj.filter ? obj.filter + "&" : "?") + "fields=" + Object.keys(targets[obj.target].columns).join(",") : obj.filter || "");
    }
    var ajaxHdl = $.ajax({
        url: settings.facilitor_url + "api2/" + obj.url + (obj.filter || ""),
        dataType: "json",
        success: function (data, textStatus, jqXHR) {
            console.log("Ajax success start: " + (obj.target || obj.model));
            obj.data = data;
            if (obj.model) {
                setModel(obj);
            } else {
                populate(obj);
            }
            console.log(data);
            console.log("Ajax success stop: " + (obj.target || obj.model));
        },
        complete: function (jqXHR, textStatus) {
            console.log("Ajax complete: " + (obj.target || obj.model));
        }
    });
    console.log({
        target: (obj.target || "model_" + obj.model),
        ajaxHdl: ajaxHdl
    });
}

// google maps initialisation
var map, marker, geocoder, panorama, selector;

// google maps interface to set a location and a marker
function codeAddress(address) {
    "use strict";
    geocoder.geocode({
        'address': address
    }, function (results, status) {
        if (status == 'OK') {
            map.setCenter(results[0].geometry.location);
            marker = new google.maps.Marker({
                map: map,
                position: results[0].geometry.location
            });
            panorama.setPosition(results[0].geometry.location);
            panorama.addListener('position_changed', function () {
                if (!panorama.getVisible()) {
                    panorama.setVisible(true);
                }
            });
            $("#" + selector).closest(".row").attr("hidden", false);
        } else {
            alert('Geocode was not successful for the following reason: ' + status);
        }
    });
}

// google maps initialisation and first location set to Facilitor
function initAutocomplete(mapselector, viewselector, location) {
    "use strict";
    selector = mapselector;
    // The location of Facilitor
    var facilitor = location || {
        lat: 52.216670,
        lng: 6.946060
    };
    // map part
    map = new google.maps.Map(
        document.getElementById(mapselector), {
            zoom: 13,
            center: facilitor,
            mapTypeId: 'roadmap'
        });
    marker = new google.maps.Marker({
        map: map,
        position: facilitor
    });
    // streetview part
    panorama = new google.maps.StreetViewPanorama(
        document.getElementById(viewselector), {
            position: facilitor,
            panControl: false,
            addressControl: false,
            pov: {
                heading: 34,
                pitch: 20
            }
        });
    map.setStreetView(panorama);
    geocoder = geocoder || new google.maps.Geocoder();
    $("#" + mapselector).closest(".row").attr("hidden", !location);
}

/* settings and functions from refactoring of targets object */
var settings = {
    facilitor_url: "../../",
    btn_class: "btn-secundary",
    btn_text_class: "text-dark",
    btn_selected_class: "btn-success",
    btn_partial_class: "btn-success",
    btn_size_class: "col-sm-3",
    btn_has_icon: true,
    icon_column: false,
    color_column: false
};
// set partial, full or no class on parent button dependent on the number of selected child buttons
function setParentSelectedClass(parms) {
    "use strict";
    var selected = $("button:visible." + settings.btn_selected_class + " div[name=" + parms.name + "][recid=" + parms.value + "]", parms.context).length;
    var present = $("button:visible div[name=" + parms.name + "][recid=" + parms.value + "]", parms.context).length;
    // reset classes to default
    var button = $("button[recid=" + parms.value + "]", parms.parent)
        .removeClass(settings.btn_selected_class + ' ' + settings.btn_partial_class)
        .addClass(settings.btn_class + ' ' + settings.btn_text_class)
        .css({
            backgroundColor: ""
        });
    // all childs are selected
    if (present == selected) {
        button
            .addClass(settings.btn_selected_class)
            .removeClass(settings.btn_class + ' ' + settings.btn_text_class)
            .css({
                backgroundColor: (button.attr("color") ? button.attr("color") : "")
            });
        // part of the childs are selected
    } else if (selected > 0) {
        button
            .addClass(settings.btn_selected_class + ' ' + settings.btn_partial_class)
            .removeClass(settings.btn_class + ' ' + settings.btn_text_class)
            .css({
                backgroundColor: (button.attr("color") ? button.attr("color") : "")
            });
    }
    return button;
}
// set full or no class on child buttons dependent on the parent button
function setChildSelectedClass(parms) {
    "use strict";
    var button;
    var items = $("button div[name=" + parms.name + "][recid=" + parms.$this.attr("recid") + "]", parms.parent);
    items
        .each(
            function () {
                button = $(this).closest("button");
                button
                    .removeClass(settings.btn_partial_class)
                    .toggleClass(settings.btn_selected_class, parms.$this.hasClass(settings.btn_selected_class))
                    .toggleClass(settings.btn_class, !parms.$this.hasClass(settings.btn_selected_class))
                    .toggleClass(settings.btn_text_class, !parms.$this.hasClass(settings.btn_selected_class))
                    .css({
                        backgroundColor: (button.attr("color") && button.hasClass(settings.btn_selected_class) ? button.attr("color") : "")
                    });
            }
        );
    return items;
}
// set color of child-buttons to color of parent-button when a child-button has no specific color of it's own
function setChildrenColor(parms) {
    "use strict";
    var $this, parent;
    var items = $("button:not([color]) div[name=" + parms.name + "]", parms.context);
    items
        .each(
            function () {
                $this = $(this);
                // parent button has a specific colour
                parent = $("button[recid=" + $this.attr("recid") + "]", parms.parent);
                if (parent.length) {
                    $this.closest("button")
                        .attr("color", parent.attr("color"))
                        .css({
                            backgroundColor: parent.attr("color")
                        });
                }
            }
        );
}
// save recid's of selected buttons in [ context ]
function saveSelectedbuttonIds(parms) {
    "use strict";
    targets[parms.target].selected_ids = [];
    $("button." + settings.btn_selected_class, parms.context).each(function () {
        targets[parms.target].selected_ids.push($(this).attr("recid"));
    });
}
// save recid's used in table
function saveUsedtabledataIds(parms) {
    "use strict";
    var index, recid, key_count = {};
    targets[parms.target].selected_ids = [];
    $("td[name=" + parms.name + "]", parms.context).each(function () {
        recid = $(this).attr("recid");
        key_count[recid] = key_count[recid] ? key_count[recid] + 1 : 1;
        index = targets[parms.target].selected_ids.indexOf(recid);
        if (index === -1) {
            targets[parms.target].selected_ids.push(recid);
        }
    });
    return key_count;
}
// set classes for button dependent on button_selected_class
function setButtonClasses(parms) {
    "use strict";
    parms.$this.removeClass(settings.btn_selected_class == settings.btn_partial_class ? "" : settings.btn_partial_class)
        .toggleClass(settings.btn_selected_class)
        .toggleClass(settings.btn_class, !parms.$this.hasClass(settings.btn_selected_class))
        .toggleClass(settings.btn_text_class, !parms.$this.hasClass(settings.btn_selected_class))
        .css({
            backgroundColor: (parms.$this.attr("color") && parms.$this.hasClass(settings.btn_selected_class) ? parms.$this.attr("color") : "")
        });
}
// reset classes for all buttons
function resetAllButtonClasses(parms) {
    "use strict";
    $("button", parms.context)
        .removeClass(settings.btn_partial_class + ' ' + settings.btn_selected_class)
        .addClass(settings.btn_class + ' ' + settings.btn_text_class)
        .css({
            backgroundColor: ""
        });
}
// set hidden attr for buttons dependent on button_selected_class
function setButtonHidden(parms) {
    "use strict";
    $("button", parms.context)
        .attr("hidden", function () {
            $(this).attr("hidden", !$(this).hasClass(settings.btn_selected_class))
        });
}
// autoselect first row in case there is only 1 result
function autoselectRow(parms) {
    "use strict";
    // select (click) the first row if there is only 1 result
    if ($("tbody tr", "#" + targets[parms.target].selector).length == 1) {
        $("tbody tr:first", "#" + targets[parms.target].selector).click();
    }
}
// redraw the table and select first row in case there is only 1 result
function redrawTable(parms) {
    "use strict";
    targets[parms.target].data_table.draw();
    $("#" + targets[parms.target].selector).closest("div.card").resize();
    autoselectRow(parms);
}
// resize innerHeight of target-card to innerHeight of base-card
function resizeCard(targetselector, baseselector) {
    function _resizeCard() {
        console.log("resize " + targetselector);
        $("#" + targetselector).closest("div.card").innerHeight($("#" + baseselector).closest("div.card").innerHeight());
    }
    return function _setTimeout() {
        setTimeout(_resizeCard, 100);
    }
}

/* Objects to be used for rendering dashboards */

// Object with render function to return field html (without label)
function Field(parms) {
    "use strict";
    var self = this,
        date, time;
    // prefix a string with a zero when string length < length to be used ( hour/minute/day/month fields )
    var prefixZero = function (str, len) {
        return ("0" + str).slice(-len);
    };
    this.single = parms.single;
    this.column_name = parms.column_name;
    this.column_type = parms.column_type;
    this.column_id = parms.column_id;
    this.column_data = typeFormat({
        value: (parms.column_data || ""),
        column_type: parms.column_type,
        single: parms.single
    });
    this.column_valuemin = parms.column_valuemin;
    this.column_valuemax = parms.column_valuemax;
    this.render = function () {
        switch (self.column_type) {
            case "progressbar":
                return '<div class="col-sm-8 progress" style="height: 2rem;">' +
                    '<div class="progress-bar progress-bar-info form-control form-control-sm" role="progressbar" id="' + self.column_id + '" style="width: ' + Math.round(1 / self.column_valuemax * self.column_data * 100) + '%;" aria-valuenow="' + self.column_data + '" aria-valuemin="' + self.column_valuemin + '" aria-valuemax="' + self.column_valuemax + '"></div>' +
                    '</div>';
                // niet na een return break;
            case "check":
                return '<div class="form-check">' +
                    '<input type="checkbox" class="form-control form-control-sm form-check-input" id="' + self.column_id + '" name=' + self.column_name + (self.column_data == 1 ? ' checked' : '') + ' disabled>' +
                    '</div>';
                // niet na een return break;
            case "textarea":
                return '<div class="col-sm-8">' +
                    '<textarea class="sm-textarea form-control form-control-sm" id="' + self.column_id + '" readonly>' + self.column_data + '</textarea>' +
                    '</div>';
                // niet na een return break;
            case "date":
                date = self.column_data == "" ? "" : self.column_data.getFullYear().toString() + "-" + prefixZero((self.column_data.getMonth() + 1).toString(), 2) + "-" + prefixZero(self.column_data.getDate().toString(), 2);
                return '<div class="col-sm-8">' +
                    '<input type="date" class="form-control form-control-sm" id="' + self.column_id + '" name=' + self.column_name + '" value="' + date + '" readonly>' +
                    '</div>';
                // niet na een return break;
            case "datetime":
                time = self.column_data == "" ? "" : prefixZero(self.column_data.getHours().toString(), 2) + ":" + prefixZero(self.column_data.getMinutes().toString(), 2);
                date = self.column_data == "" ? "" : self.column_data.getFullYear().toString() + "-" + prefixZero((self.column_data.getMonth() + 1).toString(), 2) + "-" + prefixZero(self.column_data.getDate().toString(), 2);
                return '<div class="col-sm-5">' +
                    '<input type="date" class="form-control form-control-sm" id="' + self.column_id + '" name=' + self.column_name + '" value="' + date + '" readonly>' +
                    '</div>' +
                    '<div class="col-sm-3">' +
                    '<input type="time" class="form-control form-control-sm" id="' + self.column_id + '_time" name=' + self.column_name + '_time" value="' + time + '" readonly>' +
                    '</div>';
                // niet na een return break;
            case "time":
                time = self.column_data == "" ? "" : prefixZero(self.column_data.getHours().toString(), 2) + ":" + prefixZero(self.column_data.getMinutes().toString(), 2);
                return '<div class="col-sm-3">' +
                    '<input type="time" class="form-control form-control-sm" id="' + self.column_id + '_time" name=' + self.column_name + '_time" value="' + time + '" readonly>' +
                    '</div>';
                // niet na een return break;
            default:
                return '<div class="col-sm-8">' +
                    '<input type="' + self.column_type + '" class="form-control form-control-sm" id="' + self.column_id + '" name=' + self.column_name + '" value="' + self.column_data + '" readonly>' +
                    '</div>';
                // niet na een return break;
        }
    }
    console.log(self);
}

// Object with render function to return label and customfield html
function Customfield(parms) {
    "use strict";
    var self = this;
    this.single = parms.single;
    this.column_name = parms.column_name;
    this.custom_field = parms.custom_field;
    this.selector = parms.selector;
    this.column_id = parms.selector + "-" + self.custom_field.propertyid;
    // translate customfield-type to model field-type
    this.custom_field_type = function () {
        switch (self.custom_field.type) {
            case "V":
                return "check";
            case "D":
                return "date";
            case "T":
                return "time";
            case "N":
                return "number";
            default:
                return "text";
        }
    }
    this.render = function () {
        return '<div class="form-group form-group-sm pb-0 mb-0 form-row">' +
            '<label for="' + self.column_id + '" class="col-sm-4 col-form-label text-nowrap">' + self.custom_field.label + '</label>' +
            new Field({
                single: self.single,
                column_name: self.column_name,
                column_id: self.column_id,
                column_type: self.custom_field_type(),
                column_data: self.custom_field.value
            }).render() +
            '</div>';
    }
    console.log(self);
}

// Object with render function to return html for all customfields including labels
function Customfields(parms) {
    "use strict";
    var self = this;
    // sort customfields on sequence
    this.custom_fields = (parms.data[parms.table].custom_fields ? parms.data[parms.table].custom_fields.sort(function (a, b) {
        return (a.sequence > b.sequence ? 1 : -1)
    }) : []);
    this.single = parms.single;
    this.target = parms.target;
    this.table = parms.table;
    this.render = function () {
        var fields = "",
            custom_field;
        if (targets[self.target].show_custom_fields && self.custom_fields.length) {
            for (custom_field in self.custom_fields) {
                // only customfields other than type Q, L or M
                if (["Q", "L", "M"].indexOf(self.custom_fields[custom_field].type) == -1 && (targets[self.target].show_custom_field_when_empty || self.custom_fields[custom_field].value != null)) {
                    fields += new Customfield({
                        single: self.single,
                        column_name: custom_field,
                        selector: targets[self.target].selector,
                        custom_field: self.custom_fields[custom_field]
                    }).render();
                }
            }
        }
        return '<div name="custom-fields">' + fields + '</div>';
    }
    console.log(self);
}

// Object base for targets
function Target(parms) {
    "use strict";
    var self = this;
    var choose = function (first_choice, fallback_choice) {
        return (typeof first_choice != "undefined" ? first_choice : fallback_choice);
    };
    this.title = parms.title || "";
    this.selector = parms.selector;
    this.mode = parms.mode;
    this.columns = choose(parms.columns, {});
    this.events = choose(parms.events, "click");
    this.trigger = parms.trigger || false;
    this.ready = parms.ready || false;
    this.filter = parms.filter || false;
    this.report = parms.report || false;
    this.trigger_selector = (this.filter ? "button" : "table tbody tr");
    this.btn_size_class = choose(parms.btn_size_class, settings.btn_size_class);
    this.btn_has_icon = choose(parms.btn_has_icon, true);
    this.icon_column = choose(parms.icon_column, settings.icon_column);
    this.color_column = choose(parms.color_column, settings.color_column);
    this.show_custom_fields = parms.show_custom_fields || false;
    this.show_custom_field_when_empty = parms.show_custom_field_when_empty || false;
    console.log(self);
}