SiLeBAT/FSK-Lab

View on GitHub
de.bund.bfr.knime.fsklab.deprecatednodes/js-src/de/bund/bfr/knime/fsklab/nodes/metadataeditor/editor.js

Summary

Maintainability
F
1 wk
Test Coverage
/*jshint esversion: 6 */
/* global $ */
metadata_editor = function() {

    var softwareDic = {
        'R': 'R',
        'Matlab': 'Matlab'
    };
    var modelTypeDic = {
        'EXPERIMENTAL_DATA': 'Experimental data',
        'PRIMARY_MODEL_WDATA': 'Primary model with data',
        'PRIMARY_MODEL_WODATA': 'Primary model without data',
        'TWO_STEP_SECONDARY_MODEL': 'Two step secondary model',
        'ONE_STEP_SECONDARY_MODEL': 'One step secondary model',
        'MANUAL_SECONDARY_MODEL': 'Manual secondary model',
        'TWO_STEP_TERTIARY_MODEL': 'Two step tertiary model',
        'ONE_STEP_TERTIARY_MODEL': 'One step tertiary model',
        'MANUAL_TERTIARY_MODEL': 'Manual tertiary model'
    };
    var modelClassDic = {
        'UNKNOWN': 'unknown',
        'GROWTH': 'growth',
        'INACTIVATION': 'inactivation',
        'SURVIVAL': 'survival',
        'GROWTH_INACTIVATION': 'growth/inactivation',
        'INACTIVATION_SURVIVAL': 'inactivation/survival',
        'GROWTH_SURVIVAL': 'growth/survival',
        'GROWTH_INACTIVATION_SURVIVAL': 'growth/inactivation/survival',
        'T': 'T',
        'PH': 'pH',
        'AW': 'aw',
        'T_PH': 'T/pH',
        'T_AW': 'T/aw',
        'PH_AW': 'pH/aw',
        'T_PH_AW': 'T/pH/aw'
    };
    var dataTypeDic = {
        'character': 'character',
        'integer': 'integer',
        'numeric': 'numeric',
        'array': 'array'
    };

    /**
     * Create a form-group envolving an input.
     * - type: Input type {text, url, checkbox}
     * - label: Text label
     * 
     * The input is accesible through the property `input`.
     */
    function InputForm(type, label) {
        var form = $('<div class="form-group">' +
            '  <label class="col-sm-3 control-label">' + label + '</label>' +
            '  <div class="col-sm-9"></div>' +
            '</div>');
        form.input = new Input(type);
        $('div', form).append(form.input);

        return form;
    }

    function Input(type) {
        var input;

        if (type === 'text') {
            input = $('<input type="text" class="form-control no-border" value="">');
        }
        else if (type === 'url') {
            input = $('<input type="url" class="form-control no-border" value="">');
        }
        else if (type === 'checkbox') {
            input = $('<input type="checkbox" class="form-control no-border">');
        }
        else if (type === 'date') {
            input = $('<input type="text" class="form-control no-border">');
            input.datepicker({
                dateFormat: 'mm.dd.yy'
            });
            input.click(function() {
                $(this).datepicker('show');
            });
        }

        return input;
    }

    /**
     * Create a form-group envolving a textarea.
     * - label: Text label
     */
    function TextAreaForm(label) {
        var form = $('<div class="form-group">' +
            '  <label class="col-sm-3 control-label">' + label + '</label>' +
            '  <div class="col-sm-9"></div>' +
            '</div>');
        form.textarea = $('<textarea class="form-control no-border" rows="3"></textarea>');
        $('div', form).append(form.textarea);

        return form;
    }

    function Select(entries) {
        var select = $('<select class="form-control no-border"></select>');
        for (var key in entries) {
            select.append($('<option value="' + key + '">' + entries[key] + '</option>'));
        }
        return select;
    }

    /**
     * Create a form-group envolving a select.
     * - label: Text label
     * - entries: Dictionary where the keys are the labels and the values are
     *   the option values.
     */
    function SelectForm(label, entries) {
        var form = $('<div class="form-group">' +
            '  <label class="col-sm-3 control-label">' + label + '</label>' +
            '  <div class="col-sm-9"></div>' +
            '</div>');
        form.input = new Select(entries);
        $('div', form).append(form.input);

        return form;
    }

    function ParametersTable(depvars, indepvars) {
        /**
         * Table select with border. Extends Select.
         */
        function TableSelect(entries) {
            var input = new Select(entries);
            input.removeClass('no-border');

            return input;
        }

        /**
         * Text input with border. Extends input.
         */
        function TableInput() {
            var input = new Input('text');
            input.removeClass('no-border');

            return input;
        }

        function VariableRowBase() {
            // HTML elements
            this.nameInput = new TableInput();
            this.unitInput = new TableInput();
            this.typeSelect = new TableSelect(dataTypeDic);
            this.valueInput = new TableInput();
            this.minInput = new TableInput();
            this.maxInput = new TableInput();
            this.isDependentInput = null;
            this.removeAddButton = null;

            this.row = $('<tr>' +
                '<td class="has-success">' + // name column
                '<td class="has-success">' + // unit column
                '<td class="has-success">' + // data type column
                '<td class="has-success">' + // value column
                '<td class="has-success">' + // min column
                '<td class="has-success">' + // max column
                '<td></td>' + // dependent column
                '<td></td>' + // remove parameter button
                '</tr>'
            );

            // Add HTML elements to this.row
            $('td:eq(0)', this.row).append(this.nameInput);
            $('td:eq(1)', this.row).append(this.unitInput);
            $('td:eq(2)', this.row).append(this.typeSelect);
            $('td:eq(3)', this.row).append(this.valueInput);
            $('td:eq(4)', this.row).append(this.minInput);
            $('td:eq(5)', this.row).append(this.maxInput);
        }

        /**
         * Returns whether the name is valid.
         * name is not null or undefined.
         */
        VariableRowBase.prototype.validateName = function() {
            var outer = this;
            $('tbody tr').filter(function() {return $(this) !== outer.row;}).each(function() {
                var currInput = $('td:first-child input', this);
                if (outer.nameInput.val() === currInput.val()) {
                    return false;
                }
            });
            return true;
        };

        /**
         * Returns whether value is valid.
         * value is not null or undefined.
         */
        VariableRowBase.prototype.validateValue = function() {
            var value = Number(this.valueInput.val());
            var min = Number(this.minInput.val());
            var max = Number(this.maxInput.val());

            return min <= value && value <= max;
        };

        /**
         * Returns whether min is valid.
         * min and max are not null or undefined.
         */
        VariableRowBase.prototype.validateMin = function() {
            var min = Number(this.minInput.val());
            var max = Number(this.maxInput.val());

            return min <= max;
        };

        /**
         * Returns whether max is valid.
         * min and max are not null or undefined.
         */
        VariableRowBase.prototype.validateMax = function() {
            var min = Number(this.minInput.val());
            var max = Number(this.maxInput.val());

            return max >= min;
        };

        /**
         * Create a table row with variable data.
         * 
         * Changes with VariableRowBase:
         * - isDependentInput is enabled
         * - removeAddButton is a removeButton
         */
        function VariableRow(variable, is_dependent) {
            VariableRowBase.call(this);

            // HTML elements
            this.isDependentInput = new Input('checkbox');
            $('td:eq(6)', this.row).append(this.isDependentInput);

            this.removeAddButton = $('td:eq(7)', this.row).append(
                '<button type="button" class="btn btn-default btn-danger">' +
                '  <span class="glyphicon glyphicon-minus"></span>' +
                '</button>');

            // Load data
            if (variable.name) {
                this.nameInput.val(variable.name);
            }
            if (variable.unit) {
                this.unitInput.val(variable.unit);
            }
            // Mark variable type as selected in typeSelect
            $('option[value=' + variable.type + ']', this.typeSelect).prop('selected', true);
            this.valueInput.val(variable.value);
            this.minInput.val(variable.min);
            this.maxInput.val(variable.max);

            // Disable value, min and max inputs for array variables
            if (variable.type === 'array') {
                this.valueInput.prop('disabled', true);
                this.minInput.prop('disabled', true);
                this.maxInput.prop('disabled', true);
            }

            if (is_dependent) {
                this.isDependentInput.prop('checked', true);
                this.row.addClass('danger');
            } else {
                this.row.removeClass('danger');
            }

            // Save data
            var outer = this;

            this.nameInput.on('input', function() {
                var nameTd = outer.nameInput.parent();
                if (outer.nameInput.val()) {
                    if (outer.validateName()) {
                        outer.markValidTd(nameTd);
                    } else {
                        outer.markInvalidTd(nameTd);
                    }
                }
                // If name is empty, mark cell as invalid
                else {
                    outer.markInvalidTd(nameTd);
                }
            });

            // When the type changes, discard the former value, min and max
            this.typeSelect.change(function() {
                outer.valueInput.val('');
                outer.minInput.val('');
                outer.maxInput.val('');
            });

            // Validates value
            this.valueInput.on('input', function() {
                var newVal = Number(outer.valueInput.val());
                var valTd = outer.valueInput.parent();

                if (outer.variable.type === 'integer' && newVal % 2 !== 0) {
                    outer.markInvalidTd(valTd);
                }
                else {
                    if (outer.validateValue()) {
                        outer.markValidTd(valTd);
                    } else {
                        outer.markInvalidTd(valTd);
                    }
                }
            });

            // Validate min
            this.minInput.on('input', function() {
                var minTd = outer.maxInput.parent(); // Parent td of minInput

                if (outer.validateMin()) {
                    outer.markValidTd(minTd);
                } else {
                    outer.markInvalidTd(minTd);
                }
            });

            // Validate max
            this.maxInput.on('input', function() {
                var maxTd = outer.maxInput.parent(); // Parent td of maxInput

                if (outer.validateMax()) {
                    outer.markValidTd(maxTd);
                } else {
                    outer.markInvalidTd(maxTd);
                }
            });

            this.isDependentInput.change(function() {
                // Style row
                if (outer.isDependentInput.is(':checked')) {
                    outer.row.addClass('danger');
                    outer.valueInput.val('');
                } else {
                    outer.row.removeClass('danger');
                }
            });

            this.removeAddButton.click(function() {
                _table.variableRows.splice(outer.row.index(), 1); // Remove this row data
                outer.row.remove(); // Remove row from table
            });

            return this;
        }

        VariableRow.prototype = Object.create(VariableRowBase.prototype);
        VariableRow.prototype.constructor = VariableRowBase;

        /** Mark a table cell as valid. */
        VariableRow.prototype.markValidTd = function(td) {
            td.removeClass('has-error');
            td.addClass('has-success');
        };

        /** Mark a table cell as invalid. */
        VariableRow.prototype.markInvalidTd = function(td) {
            td.removeClass('has-success');
            td.addClass('has-error');
        };

        /**
         * Create a table row to introduce a new variable.
         * 
         * Changes with VariableRowBase:
         * - isDependentInput is disabled
         * - removeAddButton is an add button
         */
        function NewVariableRow() {
            VariableRowBase.call(this);

            // HTML elements
            this.isDependentInput = $('td:eq(6)', this.row).append(
                '<input type="checkbox" class="form-control input-sm" disabled>');
            this.removeAddButton = $('td:eq(7)', this.row).append(
                '<button type="button" class="btn btn-default btn-success">' +
                '  <span class="glyphicon glyphicon-plus"></span>' +
                '</button>');
            var outer = this;

            // When the type changes, discards the former value, min and max
            this.typeSelect.change(function() {
                outer.valueInput.val('');
                outer.minInput.val('');
                outer.maxInput.val('');
            });

            this.removeAddButton.click(function() {
                if (!outer.nameInput.val()) {
                    outer.createWarning('Missing name');
                } else if (!outer.unitInput.val()) {
                    outer.createWarning('Missing unit');
                } else if (!outer.typeSelect.val()) {
                    outer.createWarning('Missing type');
                } else if (!outer.valueInput.val()) {
                    outer.createWarning('Missing value');
                } else if (!outer.minInput.val()) {
                    outer.createWarning('Missing min');
                } else if (!outer.maxInput.val()) {
                    outer.createWarning('Missing max');
                }

                else if (!outer.validateName()) {
                    outer.createWarning('Invalid name: ' + outer.nameInput.val());
                } else if (!outer.validateValue()) {
                    outer.createWarning('Invalid value: ' + outer.valueInput.val());
                } else if (!outer.validateMin()) {
                    outer.createWarning('Invalid min: ' + outer.minInput.val());
                } else if (!outer.validateMax()) {
                    outer.createWarning('Invalid max: ' + outer.maxInput.val());
                }

                else {
                    // Create new row with new variable data
                    var variable = {
                        'name': outer.nameInput.val(),
                        'unit': outer.unitInput.val(),
                        'type': outer.typeSelect.val(),
                        'value': outer.valueInput.val(),
                        'min': outer.minInput.val(),
                        'max': outer.maxInput.val()
                    };
                    var variableRow = new VariableRow(variable);

                    // Insert new row just on top of the new variable row
                    $('tbody').append(variableRow.row);

                    // Include the new variable data at the end of _variableRows
                    _table.variableRows.push(variableRow);

                    // Wipe out contents of the new variable data row
                    outer.nameInput.val('');
                    outer.unitInput.val('');
                    outer.valueInput.val('');
                    outer.minInput.val('');
                    outer.maxInput.val('');
                    outer.typeSelect.val('');
                }
            });

            return this;
        }

        NewVariableRow.prototype = Object.create(VariableRowBase.prototype);
        NewVariableRow.prototype.constructor = VariableRowBase;

        NewVariableRow.prototype.createWarning = function(reason) {
            var warning = $('<div class="alert alert-danger" role="alert">' + reason + '</div>');
            $('table').before(warning);
            window.setTimeout(function() {
                warning.remove();
            }, 5000);
        };

        var table = $('<table class="table table-condensed">' +
            '  <thead><tr>' +
            '    <th>Name</th>' +
            '    <th>Unit</th>' +
            '    <th>Type</th>' +
            '    <th>Value</th>' +
            '    <th>Min</th>' +
            '    <th>Max</th>' +
            '    <th>Dependent</th>' +
            '    <th>Remove</th>' +
            '  </tr></thead>' +
            '  <tfoot></tfoot>' +
            '  <tbody></tbody>' +
            '</table>');

        table.variableRows = [];
        for (var i = 0; i < depvars.length; i++) {
            var vr = new VariableRow(depvars[i], true);
            $('tbody', table).append(vr.row);
            table.variableRows.push(vr);
        }
        for (var i = 0; i < indepvars.length; i++) {
            var vr = new VariableRow(indepvars[i], false);
            $('tbody', table).append(vr.row);
            table.variableRows.push(vr);
        }
        $('tfoot', table).append(new NewVariableRow().row);

        return table;
    }

    function Form(metadata) {
        var form = $('<form class="form-horizontal"></form>');
        form.metadata = metadata;

        // modelName input
        var modelNameInput = new InputForm('text', 'Model name');
        modelNameInput.input.val(form.metadata.modelName);
        modelNameInput.input.on('input', function() {
            form.metadata.modelName = modelNameInput.input.val();
        });
        form.append(modelNameInput);

        // model id input
        var modelIdInput = new InputForm('text', 'Model id');
        modelIdInput.input.val(form.metadata.modelId);
        modelIdInput.input.on('input', function() {
            form.metadata.modelid = modelIdInput.input.val();
        });
        form.append(modelIdInput);

        // model link input
        var modelLinkInput = new InputForm('url', 'Model link');
        modelLinkInput.input.val(form.metadata.modelLink);
        modelLinkInput.input.on('input', function() {
            form.metadata.modelLink = modelLinkInput.input.val();
        });
        form.append(modelLinkInput);

        // organism input
        var organismInput = new InputForm('text', 'Organism');
        organismInput.input.val(form.metadata.organism);
        organismInput.input.on('input', function() {
            form.metadata.organism = organismInput.input.val();
        });
        form.append(organismInput);

        // organism details input
        var organismDetailsInput = new InputForm('text', 'Organism details');
        organismDetailsInput.input.val(form.metadata.organismDetailsInput);
        organismDetailsInput.input.on('input', function() {
            form.metadata.organismDetails = organismDetailsInput.input.val();
        });
        form.append(organismDetailsInput);

        // matrix input
        var matrixInput = new InputForm('text', 'Matrix');
        matrixInput.input.val(form.metadata.matrix);
        matrixInput.input.on('input', function() {
            form.metadata.matrix = matrixInput.input.val();
        });
        form.append(matrixInput);

        // matrix details input
        var matrixDetailsInput = new InputForm('text', 'Matrix details');
        matrixDetailsInput.input.val(form.metadata.matrixDetails);
        matrixDetailsInput.input.on('input', function() {
            form.metadata.matrixDetails = matrixDetailsInput.input.val();
        });
        form.append(matrixDetailsInput);

        // creator input
        var creatorInput = new InputForm('text', 'Creator');
        creatorInput.input.val(form.metadata.creator);
        creatorInput.input.on('input', function() {
            form.metadata.creator = creatorInput.input.val();
        });
        form.append(creatorInput);

        // family name input
        var familyNameInput = new InputForm('text', 'Family name');
        familyNameInput.input.val(form.metadata.familyName);
        familyNameInput.input.on('input', function() {
            form.metadata.familyName = familyNameInput.input.val();
        });
        form.append(familyNameInput);

        // contact input
        var contactInput = new InputForm('text', 'Contact');
        contactInput.input.val(form.metadata.contact);
        contactInput.input.on('input', function() {
            form.metadata.contact = contactInput.input.val();
        });
        form.append(contactInput);

        // software input
        var softwareInput = new SelectForm('Software', softwareDic);
        softwareInput.input.val(form.metadata.software);
        softwareInput.input.change(function() {
            form.metadata.softwareInput = softwareInput.input.val();
        });
        form.append(softwareInput);

        // reference description input
        var referenceDescriptionInput = new InputForm('text', 'Reference description');
        referenceDescriptionInput.input.val(form.metadata.referenceDescriptionInput);
        referenceDescriptionInput.input.on('input', function() {
            form.metadata.referenceDescription = referenceDescriptionInput.input.val();
        });
        form.append(referenceDescriptionInput);

        // reference description link input
        var referenceDescriptionLinkInput = new InputForm('text', 'Reference link description');
        referenceDescriptionLinkInput.input.val(form.metadata.referenceDescriptionLinkInput);
        referenceDescriptionLinkInput.input.on('input', function() {
            form.metadata.referenceDescriptionLink = referenceDescriptionLinkInput.input.val();
        });
        form.append(referenceDescriptionLinkInput);

        // created date input
        var createdDateInput = new InputForm('date', 'Created date');
        createdDateInput.input.datepicker('setDate', form.metadata.createdDate);
        createdDateInput.input.datepicker({
            onSelect: function() {
                form.metadata.createdDate = this.value;
            }
        });
        form.append(createdDateInput);

        // modified date input
        var modifiedDateInput = new InputForm('date', 'Modified date');
        modifiedDateInput.input.datepicker('setDate', form.metadata.modifiedDate);
        modifiedDateInput.input.datepicker({
            onSelect: function() {
                form.metadata.modifiedDate = this.value;
            }
        });
        form.append(modifiedDateInput);

        // rights input
        var rightsInput = new InputForm('text', 'Rights');
        rightsInput.input.val(form.metadata.rights);
        rightsInput.input.on('input', function() {
            form.metadata.rights = rightsInput.input.val();
        });
        form.append(rightsInput);

        // notes input
        var notesInput = new TextAreaForm('Notes');
        notesInput.textarea.val(form.metadata.notes);
        notesInput.textarea.on('input', function() {
            form.metadata.notes = notesInput.textarea.val();
        });
        form.append(notesInput);

        // curated input
        var curatedInput = new InputForm('checkbox', 'Curated');
        curatedInput.input.val(form.metadata.curated);
        curatedInput.input.change(function() {
            form.metadata.curated = curatedInput.input.is(':checked');
        });
        form.append(curatedInput);

        // type input
        var typeInput = new SelectForm('Model type', modelTypeDic);
        typeInput.input.val(form.metadata.type);
        typeInput.input.change(function() {
            form.metadata.type = typeInput.input.val();
        });
        form.append(typeInput);

        // subject input
        var subjectInput = new SelectForm('Model subject', modelClassDic);
        subjectInput.input.val(form.metadata.subject);
        subjectInput.input.change(function() {
            form.metadata.subject = subjectInput.input.val();
        });
        form.append(subjectInput);

        // food process input
        var foodProcessInput = new InputForm('text', 'Food process');
        foodProcessInput.input.val(form.metadata.foodProcess);
        foodProcessInput.input.on('input', function() {
            form.metadata.foodProcess = foodProcessInput.input.val();
        });
        form.append(foodProcessInput);

        // has data input
        var hasDataInput = new InputForm('checkbox', 'Has data?');
        hasDataInput.input.val(form.metadata.hasData);
        hasDataInput.input.change(function() {
            form.metadata.hasData = hasDataInput.input.is(':checked');
        });
        form.append(hasDataInput);

        return form;
    }


    var editor = {
        version: '0.0.1'
    };
    editor.name = 'FSK Metadata Editor';

    var _value; // Raw FskMetadataEditorViewValue


    // UI elements
    var _form;
    var _table;

    editor.init = function(representation, value) {
        _value = value;
        _form = new Form(_value.metadata);
        _table = new ParametersTable(_value.metadata.dependentVariables, _value.metadata.independentVariables);

        checkVariables();
        create_body();
    };


    editor.getComponentValue = function() {
        _value.metadata = _form.metadata;

        _value.metadata.dependentVariables = [];
        _value.metadata.independentVariables = [];
        for (var i = 0; i < _table.variableRows.length; i++) {
            var row = _table.variableRows[i];
            var variable = {
                'name': row.nameInput.val(),
                'unit': row.unitInput.val(),
                'type': row.typeSelect.val(),
                'value': row.valueInput.val(),
                'min': row.minInput.val(),
                'max': row.maxInput.val(),
            };
            if (row.isDependentInput.is(':checked')) {
                _value.metadata.dependentVariables.push(variable);
            }
            else {
                _value.metadata.independentVariables.push(variable);
            }
        }

        return _value;
    };

    return editor;

    // --- utility functions ---
    function create_body() {
        document.createElement("body");
        $('body').html('<div class="container"></div>');
        $('.container').append(_form); // Add form
        $('.container').append(_table); // Add parameters table
    }

    /** Check variables. Integer variables will truncate decimals. */
    function checkVariables() {
        for (var i = 0; i < _value.metadata.independentVariables.length; i++) {
            var variable = _value.metadata.independentVariables[i];
            if (variable.type === 'integer') {
                variable.value = Math.floor(variable.value);
            }
        }
    }
}();