SiLeBAT/FSK-Lab

View on GitHub
de.bund.bfr.knime.js/src/js/app/app.simulation.js

Summary

Maintainability
F
1 wk
Test Coverage
/*

version: 1.0.0
author: Ahmad Swaid
date: 17.12.2020

*/

class APPSimulation {
    constructor ( settings, $container ) {
        let O = this;
        // defaults maintable simulations modal
        O._$container = $container;
        O._opts = $.extend( true, {}, {
            classes : '',
            data     : null,
            on         : {
                afterInit     : null, // function
                show         : ( O, event ) => {
                    O._updateModal( event );
                }, // function
                hide         : null // function
            }
        }, settings );
        O._create();
    }
    get opts () {
        return this._opts;
    }
    set opts ( settings ) {
        this._opts = $.extend( true, {}, this.opts, settings );
    }
    /**
     * CREATE
     * calls super class and sets _metadata
     */
     
    _create () {
        let O = this;
        _log( 'SIM / _create', 'primary' );
        _log( O.opts );

        // global
        O._metadata = O.opts.data;
        O._state = 'params'; // default state: params form
        O._savedState = 'clean'

        O._$simInputs = []; // inputs from params and customs
        O._simFields = {};
        O._simSelectedIndex = 0; // initial simulation
    }
    _createSimulationContent () {
        let O = this;
        _log( 'panel SIM / _createSimulationContent' );

        // nav
        O._$modalNav = $( '<div class="modal-body sim-select"></div>' )
            .appendTo( O._$container );
        O._$globalValidation = $( '<ul class="col-12 errorMessages"></ul>' )
            .appendTo( O._$container );

        // navbar
        O._$navBar = $( '<nav class="navbar">' )
            .appendTo( O._$modalNav )
            .wrap( '<form></form>' ); 

        // sim select label
        O._$simSelectLabel = $( '<label class="col-12 col-md-3 sim-select-label" for="simulationSelect">Simulations</label>' )
            .appendTo( O._$navBar );

        // sim select counter
        O._$simSelectCounter = $( '<span class="badge badge-primary ml-1">x1</span>' )
            .appendTo( O._$simSelectLabel );

        // sim select
        O._$simSelect = $( '<select id="simulationSelect" class="custom-control custom-select"></select>' )
            .attr( 'id', 'simulationSelect' )
            .appendTo( O._$navBar )
            .wrap( '<div class="col-12 col-xs-auto col-md-4 sim-select-field"></div>' )
            .on( 'change', ( event ) => {
                let selectedIndex = O._$simSelect[0].selectedIndex;
                if ( selectedIndex >= 0 ) {
                    O._updateSimIndex( selectedIndex );
                }
            } ); 

        // create actions
        O._createSimActions();

        // panel bodys

        // panel params
        O._$modalParams = $( '<div class="modal-body p-0 sim-params"></div>' )
            .appendTo( O._$container );
        // param content container
        O._$modalParams._$content = $( '<div class="tab-content h-100"></div>' )
            .appendTo( O._$modalParams );

        // panel execution
        O._$modalExecution = $( '<div class="modal-body p-0 sim-execution"></div>' )
            .appendTo( O._$container );
        // execution content container
        O._$modalExecution._$content = $( '<div class="tab-content h-100"></div>' )
            .appendTo( O._$modalExecution );
    }
    
    /**
     * CREATE SIM ACTIONS
     * creates actionss
     */
     
    _createSimActions() {
        let O = this;
        _log( 'panel SIM / _createSimActions' );

        // sim select actions
        O._$simSelectActions = $( '<div class="col-12 col-xs-auto col-md-5 mt-2 mt-xs-0 sim-select-actions"></div>' )
            .appendTo( O._$navBar );

        // create actions
        // action group 1
        let $actionGroup1 = $( '<div class="col-auto sim-select-actions-group"></div>' )
            .appendTo( O._$simSelectActions );

        // remove
        O._$simActionRemove = $( '<button type="button" class="btn btn-icon btn-outline-light"><i class="feather icon-trash-2"></i></button>' )
            .attr( 'id', 'simActionRemove' )
            .attr( 'data-tooltip', '' )
            .attr( 'title', 'Remove simulation' )
            .appendTo( $actionGroup1 )
            .on( 'click', ( event ) => {
                O._removeSimulation();
            } );        
        
        // add
        O._$simActionAdd = $( '<button type="button" class="btn btn-icon btn-outline-light ml-1"><i class="feather icon-plus"></i></button>' )
            .attr( 'id', 'simActionAdd' )
            .attr( 'data-tooltip', '' )
            .attr( 'title', 'Add simulation' )
            .appendTo( $actionGroup1 )
            .on( 'click', ( event ) => {
                O._addSimulation();
            } );    

        // save
        O._$simActionSave = $( '<button type="button" class="btn btn-icon btn-outline-light ml-1"><i class="feather icon-save"></i></button>' )
            .attr( 'id', 'simActionSave' )
            .attr( 'data-tooltip', '' )
            .attr( 'title', 'Save changes' )
            .appendTo( $actionGroup1 )
            .on( 'click', ( event ) => {
                O._saveSimulation();
            } );    

        // col divider
        
        if(!window.noExecution){
            $( '<div class="col-divider ml-auto ml-xs-0"></div>' )
            .appendTo( O._$simSelectActions );

            // action group 2
            let $actionGroup2 = $( '<div class="col-auto sim-select-actions-group"></div>' )
            .appendTo( O._$simSelectActions );
            
            O._$simActionRun = $( '<button type="button" class="btn btn-icon btn-outline-light ml-1"><i class="feather icon-play"></i></button>' )
                .attr( 'id', 'simActionRun' )
                .attr( 'data-tooltip', '' )
                .attr( 'title', 'Run simulation' )
                .appendTo( $actionGroup2 )
                .on( 'click', ( event ) => {
                    O._runModelView();
                } );
        }

        O._$simSelectActions
            .wrapInner( '<div class="row justify-content-end align-items-center"></div>' );
    }


    /**
     * CREATE PARAM METADATA LIST
     * create table for metadata collapse container
     * @param {array} param 
     */
     
    _createParamMetadataList( param ) {
        let O = this;
        _log( 'PANEL SIM / _createParamMetadataList' );

        let listData = {
            'ID'                    : param.id,
            'Name'                    : param.name,
            'Description'             : param.description,
            'Unit'                    : param.unit,
            'Unit category'            : param.unitCategory,
            'Data type'                : param.dataType,
            'Source'                : param.source,
            'Subject'                : param.subject,
            'Distribution'            : param.distribution,
            'Reference'                : param.reference,
            'Variability subject'     : param.variabilitySubject,
            'Min value'                : param.minValue,
            'Max value'                : param.maxValue,
            'Error'                    : param.error,
        };
        _log( listData );

        // create table
        let $table = $( '<table class="table table-sm table-hover table-params-metadata"></table>' );

        // create rows
        $.each( listData, ( name, value ) => {

            if ( value ) {
                let $row = $( '<tr></tr>')
                    .appendTo( $table );
                $row.append( '<td class="td-label">'+ name +'</td>' ); // label/name
                $row.append( '<td>'+ value +'</td>' ); // value
            }
        } );

        return $table;
    }


    /**
     * CREATE FORM FIELD
     * create field as form group
     * @param {array} param
     */
     
    _createFormField ( param ) {
        let O = this;
        _log( 'PANEL SIM / _createFormField' );
        _log( param );

        if ( param ) {

            // formgroup
            let $formGroup = $( '<div class="form-group row"></div>' );
                // .appendTo( O._$simForm );

            // label
            let $label = $( '<label class="col-form-label col-form-label-sm col-9 col-xs-3 order-1 sim-param-label"></label>' )
                .attr( 'for', 'paramInput_'+ param.id )
                .appendTo( $formGroup );
            // set custom label or id
            if(O.paramsColorMap[param.id]){
                param._label ? $label.text( param._label ) : $label.text(O.paramsColorMap[param.id][1] );
                $label.css("background-color",O.paramsColorMap[param.id][0]);
                //console.log($label.attr('style'));
            }
            else
                param._label ? $label.text( param._label ) : $label.text( param.id );
            // field
            let $field = $( '<div class="col-12 col-xs-7 col-md-6 order-3 order-xs-2 sim-param-field"></div>' )
                .appendTo( $formGroup );

            // actions
            let $actions = $( '<div class="col-3 col-xs-auto order-2 order-xs-3 sim-param-actions"></div>' )
                .appendTo( $formGroup );

            // input item
            let $input = null;

            // set input type
            let inputType = null;
            if ( param.dataType.toLowerCase() === 'integer' 
                || param.dataType.toLowerCase() === 'double' 
                || param.dataType.toLowerCase() === 'number' ) {
                inputType = 'number';
            } 
            else if( param.dataType.toLowerCase() === 'simname' ) {
                inputType = 'simName'; // custom type for name and description
            }
            else if( param.dataType.toLowerCase() === 'simdescription' ) {
                inputType = 'simDescription'; // custom type for name and description
            }
            else if( param.dataType.toLowerCase() === 'vectorofnumbers' ) {
                inputType = 'vectorofnumbers';
            }
            else if( param.dataType.toLowerCase() === 'matrixofnumbers' ) {
                inputType = 'matrixofnumbers';
            }
            else {
                inputType = 'text';
            }

            // create param metadata action
            if ( _isNull( param._showMetadata )
                || param._showMetadata === true ) {
                // action metadata list
                let $actionMetadata = $( '<button class="action action-pure" type="button"><i class="feather icon-info"></i></button>' )
                    .attr( 'data-toggle', 'collapse' )
                    .attr( 'data-target', '#paramMetadata_'+ param.id )
                    .attr( 'aria-expanded', false )
                    .attr( 'aria-controls', 'paramMetadata_'+ param.id )
                    .attr( 'title', 'Show Metadata' )
                    .appendTo( $actions );
            }

            // add special action for vector or matrix of numbers
            if( inputType == 'vectorofnumbers' ) {

                let $actionVectoEditor = $( '<button class="action action-pure" type="button"><i class="feather icon-edit"></i></button>' )
                    .attr( 'title', 'Edit Vector' )
                    .appendTo( $actions );

                // TO DO
                // action what to do on click
            }
            else if( inputType == 'matrixofnumbers' ) {

                let $actionVectoEditor = $( '<button class="action action-pure" type="button"><i class="feather icon-edit"></i></button>' )
                    .attr( 'title', 'Edit Matrix' )
                    .appendTo( $actions );
                    
                // TO DO
                // action what to do on click
            }



            if ( inputType ) {

                // numeric
                if ( inputType == 'number' && param.classification != "CONSTANT") {

                    let $inputGroup = $( '<div class="input-group input-group-sm"></div>' )
                        .appendTo( $field );
                    
                    $input = $( '<input type="text" />' )
                        .attr( 'id', 'paramInput_'+ param.id )
                        .data( 'param-input', param ) 
                        .attr( 'aria-invalid', false )
                        .appendTo( $inputGroup );
                    // rangeslider single, if min/max
                    if ( param.minValue && param.maxValue  && param.minValue != "-" && param.maxValue != "-") {

                        let step = 1;
                        // calc decimals for slider steps depending on min-max values
                        if ( param.dataType.toLowerCase() === 'double' ) {
                            let decimals = param.value.substring(param.value.indexOf('.') + 1).length;
                            for ( let j = 0; j < decimals; j++ ) {
                                step = step / 10;
                            }
                        }

                        // add rangeslider attributes
                        $input
                            .addClass( 'custom-range' )
                            .attr( 'data-rangeslider', '' )
                            .attr( 'data-step', step )
                            .attr( 'data-min', parseFloat(param.minValue) ) // min value
                            .attr( 'data-max', parseFloat(param.maxValue) ) // max value
                            .attr( 'data-control-single', '#paramControlSingle_'+ param.id );

                        // control input field for range value
                        let $inputControl = $( '<input type="text" class="form-control" />' )
                            .attr( 'id', 'paramControlSingle_'+ param.id )
                            .data( 'param-input', param )  
                            .appendTo( $field )
                            .wrap( '<div class="input-range-controls"></div>' )
                            .wrap( '<div class="input-group input-group-sm input-range-control-single"></div>' );

                        // O._$simInputs.push( $inputControl );

                        // add unit postfix to touchspin
                        if ( param.unit && param.unit != '[]' && param.unit != 'Others' ) {
                            let $append = $( '<div class="input-group-text"></div>' )
                                .text( param.unit )
                                .insertAfter( $inputControl )
                                .wrap( '<div class="input-group-append"></div>' )
                        }
                    }
                    // touchspin
                    else {
                        $input
                            .addClass( 'form-control form-control-sm' )
                            .attr( 'data-touchspin', '' );
                        // add unit postfix to touchspin
                        if ( param.unit && param.unit != '[]' && param.unit != 'Others' ) {
                            $input.attr( 'data-touchspin-postfix', param.unit );
                        }
                        let step = 1;

                        // calc decimals for slider steps depending on min-max values
                        if ( param.dataType.toLowerCase() === 'double' ) {
                            let decimals = param.value.substring( param.value.indexOf( '.' ) + 1 ).length;
                            for ( let j = 0; j < decimals; j++ ) {
                                step = step / 10;
                            }
                            // add decimals support
                            if ( decimals > 0 ) {
                                $input.attr( 'data-touchspin-decimals', decimals );
                            }
                        }
                        // add step range
                        $input.attr( 'data-touchspin-step', step );
                    }
                }
                // sim name
                else if ( inputType == 'simName' ) {
                    $input = $( '<input type="text" class="form-control form-control-sm" />' )
                        .attr( 'id', 'customInput_'+ param.id )
                        .data( 'custom-input', param )
                        .appendTo( $field );

                    O._$simNameInput = $input;
                }
                // sim description
                else if ( inputType == 'simDescription' ) {
                    $input = $( '<textarea type="text" class="form-control form-control-sm" rows="6" /></textarea>' )
                        .attr( 'id', 'customInput_'+ param.id )
                        .data( 'custom-input', param ) 
                        .appendTo( $field );

                    O._$simDescInput = $input;
                }
                // string or others
                else {
                    $input = $( '<input type="text" class="form-control form-control-sm" />' )
                        .attr( 'id', 'paramInput_'+ param.id )
                        .data( 'param-input', param )  
                        .appendTo( $field );
                }

                // readonly attribute
                param.classification === "CONSTANT" ? $input.attr( 'readonly', '' ) : null;

                // add $input to global variable
                O._$simInputs.push( $input );
                O._simFields[param.id] = {
                    input     : $input,
                    param     : param
                }
            }

            // create validation container
            $input.$validationContainer = $( '<div class="validation-message mt-1"></div>' )
                .appendTo( $field );

            // create param metadata list
            if ( _isNull( param._showMetadata ) 
                || param._showMetadata === true ) {
                // metadata table
                let $metadataContainer = $( '<div class="collapse param-metadata"></div>' )
                    .attr( 'id', 'paramMetadata_'+ param.id )
                    .attr( 'aria-expanded', false )
                    .appendTo( $field );

                $metadataContainer.append( O._createParamMetadataList( param ) );
            }
        
            return $formGroup;
        }

        return null;
    }



    /**
     * POPULATE SIM SELECT
     * create select options 
     */
     
    _populateSimSelect() {
        let O = this;
        _log( 'PANEL SIM / _populateSimSelect' );

        if ( O._simulations 
            && O._simulations.length > 0 
            && O._$simSelect ) {
            // clear sim select
            O._$simSelect.empty();
            // options
            $.each( O._simulations, ( i, sim ) => {
                if ( sim.name ) {
                    let $option = $( '<option>'+ sim.name +'</option>' )
                        .appendTo( O._$simSelect );
                }
            } );
            // update badge counter
            O._$simSelectCounter.text( 'x'+ O._simulations.length );
        } 
    }


    /**
     * POPULATE SIMULATION FORM
     * creates all input fields
     */
     
    _populateSimForm () {
        let O = this;
        _log( 'PANEL SIM / _populateSimForm' );

        // clear
        O._state == 'form'
        O._$simInputs = []; // stores all inputs in global var to provide access on params an custom fields like name and description
        O._simFields = {};
        O._selectedSimIndex = 0;
        O._$simForm ? O._clear( O._$simForm ) : null; // clear form


        // create form
        O._$simForm = $( '<form class="form-striped"></form>' )
            .attr( 'id', 'simParamsForm' )
            .appendTo( O._$modalParams._$content );

        // create mandatory custom form group sim name 
        let simNameParam = {
            id                 : 'simName',
            dataType         : 'SIMNAME',
            _showMetadata     : false,
            _label             : 'Simulation Name',
            _isCustom        : true,
            _on             : {
                update             : ( O, $input ) => {
                    O._updateSimName();
                }
            }
        };
        let $simNameFormGroup = O._createFormField( simNameParam );
        $simNameFormGroup ? $simNameFormGroup.appendTo( O._$simForm ) : null;

        // create optional custom form group sim description 
        let simDescParam = {
            id                 : 'simDescription',
            dataType         : 'SIMDESCRIPTION',
            _showMetadata     : false,
            _label             : 'Description (optional)',
            _isCustom        : true,
            _on             : {
                update             : ( O, $input ) => {
                    O._updateSimDescription();
                }
            }
        };
        let $simDescFormGroup = O._createFormField( simDescParam );
        $simDescFormGroup ? $simDescFormGroup.appendTo( O._$simForm ) : null;

        // model metadata params
        let params = O._modelMetadata['modelMath']['parameter']
        if ( params.length > 0 ) {
            // create form group for each param
            $.each( params, ( i, param ) => {
                if(O.paramsColorMap[param.id][2] ){
                    // create model name title 
                            // formgroup
                    let $formGroup = $( '<div class="form-group row"></div>' );
        
                    // label containing the model name
                    let $label = $( '<label class="col-form-label col-form-label-sm col-9 col-xs-3 order-1 sim-param-label"></label>' )
                        .text(O.paramsColorMap[param.id][2] )
                        .css("background-color",O.paramsColorMap[param.id][0])
                        .appendTo( $formGroup );
                        
                    let $simDescFormGroup = O._createFormField( simDescParam );
                    $formGroup ? $formGroup.appendTo( O._$simForm ) : null;
                }
                if ( param.classification != 'OUTPUT' ) {

                    let $formGroup = O._createFormField( param );
                    $formGroup ? $formGroup.appendTo( O._$simForm ) : null;
                }
                
            } );

            // init form items' functions: touchspin, range, select2 ...
            _appUI._initFormItems( O._$simForm );
        }
        _log( O._simFields );
    }


    /**
     * UPDATE SIMULATION INDEX
     * set selected simulation index and trigger update
     */
     
    _updateSimIndex ( simIndex ) {
        let O = this;
        _log( 'PANEL SIM / _updateSimIndex: '+ simIndex );


        simIndex = ! _isNull( simIndex ) ? simIndex : O._simSelectedIndex;

        if ( simIndex >= -1 
            && simIndex != O._simSelectedIndex
            && simIndex <= O._simulations.length - 1 ) {
            // update sim index
            O._simSelectedIndex = simIndex;

            O._setState( 'params' );

            // update inputs
            O._updateSimForm( simIndex );
            O._updateSimActions( simIndex );
        }
        
        // update badge counter
        O._$simSelectCounter.text( 'x'+ O._simulations.length );
    }


    /**
     * UPDATE SIMULATION INPUTS
     * changes the selected simulation's inputs
     * @param {integer} simIndex of selected simulation: -1 ich add action, 0 = default
     */
     
    _updateSimForm ( simIndex ) {
        let O = this;
        _log( 'PANEL SIM / _updateSimForm: '+ simIndex );

        simIndex = ! _isNull( simIndex ) ? simIndex : O._simSelectedIndex;

        // set value if param or custom
        let valIndex = simIndex;

        if( simIndex < 0 ) {
            valIndex = 0;
        }    

        let simulation = O._simulations[valIndex];

        // disable parameter inputs for the default simulation.
        O._simDisabled = simIndex == 0;

        // remove error classes
        $( '.has-error' ).removeClass( 'has-error' );
        $( '.is-invalid' ).removeClass( 'is-invalid' );

        // update param values
        // $.each( O._$simInputs, ( i, $input ) => {

        $.each( O._simFields, ( id, field ) => {
            _log( field );
            if( field.input && field.param ) {

                // disable or enable 
                field.input.prop( 'disabled', O._simDisabled );

                // disable rangeslider 
                if ( field.input.data( 'rangeslider' ) ) {
                    field.input.data( 'rangeslider' )
                        .update( {
                            disable : O._simDisabled
                        } );
                }
                // disable touchspin
                else if ( ! _isUndefined( field.input.attr( 'data-touchspin' ) ) ) {
                    // disable/enable buttons oof touchspin group
                    field.input.parent()
                        .find( 'button' )
                        .prop( 'disabled', O._simDisabled );
                }
                // param fields
                if( ! field.param._isCustom ) {    
                    // set value of current selected sim index
                    _log( field.param.id +' : '+ simulation.parameters[field.param.id], 'level1' );
                    let paramValue = simulation.parameters[field.param.id];

                    // change rangeslider value
                    if ( ! _isUndefined( $( field.input ).data( 'rangeslider' ) ) ) {
                        let $inputControl = $( $( field.input ).data( 'control-single' ) );
                        if ( $inputControl.length > 0 ) {
                            $inputControl.val( paramValue );
                            // trigger change
                            $inputControl.trigger( 'change' );
                        }
                    }
                    // change other inputs
                    else {
                        ! _isNull( paramValue ) ? field.input.val( paramValue ) : null;
                    }
                }
                // custom fields like name and desc
                else if( field.param._isCustom && field.param._isCustom == true ) {

                    _log( field.param.id +' : ', 'level1' );

                    // check opt for custom update function 
                    if( field.param._on 
                        && field.param._on.update 
                        && $.isFunction( field.param._on.update ) ) {
                        field.param._on.update.call( this, O );
                    }
                }

                // trigger change
                field.input.trigger( 'change' );
            }
        });
    }


    /**
     * UPDATE SIMULATION ACTIONS
     * changes the selected simulation's inputs
     * @param {integer} simIndex of selected simulation
     */

     _updateSimActions( simIndex ) {
        let O = this;
        _log( 'PANEL SIM / _updateSimActions' );

        simIndex = ! _isNull( simIndex ) ? simIndex : O._simSelectedIndex;

        if ( simIndex == -1 ) { // add simulation
            O._$simActionRemove.prop( 'disabled', false );
            O._$simActionAdd.prop( 'disabled', true );
            O._$simActionSave.prop( 'disabled', false );
        }
        else if ( simIndex == 0 ) { // default sim
            O._$simActionRemove.prop( 'disabled', true );
            O._$simActionAdd.prop( 'disabled', false );
            O._$simActionSave.prop( 'disabled', true );
        }
        else { // selected sim > 0
            O._$simActionRemove.prop( 'disabled', false );
            O._$simActionAdd.prop( 'disabled', false );
            O._$simActionSave.prop( 'disabled', false );
        }
    }


    /**
     * UPDATE SIMULATION CUTOM NAME
     * changes the custom input name to selected simulation's name
     */
     
    _updateSimName() {
        let O = this;
        _log( 'PANEL SIM / _updateSimName' );

        if( O._$simNameInput ) {
            let simName = '';
            if( ! _isNull( O._simulations )
                && ! _isNull( O._simSelectedIndex )
                && O._simulations[O._simSelectedIndex] ) {
                // get simulation name
                simName = O._simulations[O._simSelectedIndex].name;
            }
            // set name
            O._$simNameInput.val( simName );
        }
    }


    /**
     * UPDATE SIMULATION CUSTOM DESCRIPTUION
     * changes the custom input description to selected simulation's name
     * @param {jquery elem} $input
     */
     
    _updateSimDescription( $input ) {
        let O = this;
        _log( 'PANEL SIM / _updateSimDescription' );

        if( O._$simDescInput ) {
            let simDesc = '';
            if( ! _isNull( O._simulations )
                && ! _isNull( O._simSelectedIndex )
                && O._simulations[O._simSelectedIndex] ) {
                // get simulation name
                simDesc = O._simulations[O._simSelectedIndex].desc;
            }
            // set name
            O._$simDescInput.val( simDesc );
        }
    }


    /**
     * ADD SIMULATION
     * add the selected simulation
     */
     
    _addSimulation () {
        let O = this;
        _log( 'PANEL SIM / _addSimulation', 'secondary' );

        // clear sim select
        O._$simSelect.val( '' );

        // update sim select to index for add-action : -1
        O._updateSimIndex( -1 );
        O._setSavedState( 'dirty' );

    }


    /**
     * REMOVE SIMULATION
     * remove the selected simulation
     */
     
    _removeSimulation () {
        let O = this;
        _log( 'PANEL SIM / _removeSimulation', 'secondary' );

        if ( O._simSelectedIndex != 0 ) {
            // remove from sim select
            O._$simSelect.find( 'option' ).eq( O._simSelectedIndex ).remove();

            // remove from simulations var
            O._simulations.splice( O._simSelectedIndex, 1 );

            // reset to default
            O._$simSelect.find( 'option' ).eq( 0 ).prop( 'selected', true );
            O._updateSimIndex( 0 );
        }
    }


    /**
     * SAVE SIMULATION
     * save the selected simulation
     */
     
    async _saveSimulation () {
        let O = this;
        _log( 'PANEL SIM / _saveSimulation', 'secondary' );

        if(O._$simNameInput.val() == ""){
            O._$simNameInput.val("simulationname" + O._getRandomInt(100));
        }
        // run validation
        let validation = await O._validateSimForm();

        // no errors ?
        if( validation.length == 0 ) {

            let newSimulation = JSON.parse( JSON.stringify( O._simulations[0] ) );
            // set new simulation's name
            newSimulation.name = O._$simNameInput.val();
            // set sim desc
            newSimulation.desc = O._$simDescInput ? O._$simDescInput.val() : '';

            // set new simulation's parameters
            if( O._simFields ) {
                // for each input get value and et param value
                $.each( O._simFields, ( id, field ) => {

                    if( field.input && ! _isNull( field.param ) ) {
                        // create simulation param
                        newSimulation.parameters[field.param.id] = field.input.val();
                    }
                } );

                O._simulations.push( newSimulation );
                // re-populate sim select
                O._populateSimSelect();
                // update sim index
                O._$simSelect.find( 'option' ).last().prop( 'selected', true );
                O._updateSimIndex( O._simulations.length - 1 );
            }
        }
        O._setSavedState( 'clean' );
        // TO DO 
        // save to endpoint ?
    }


    /**
     * VALIDATE SIMULATION FORM
     * run validation on sim form inputs
     */
     
    async _validateSimForm () {
        let O = this;
        _log( 'PANEL SIM / _validateSimForm' );
        O._$globalValidation.empty(); 

        let validationErrors = [];
        // remove error classes
        $( '.has-error' ).removeClass( 'has-error' );
        $( '.is-invalid' ).removeClass( 'is-invalid' );
        $( '.validation-message' ).empty();

        // validate sim inputs values
        if( O._simFields ) {

            $.each( O._simFields, ( id, field ) => {
                // let param = $input.data( 'param-input' );

                if( ! _isNull( field.param ) ) {
                    let validationParam = O._validateSimField( field ) ;
                    validationParam ? validationErrors.push( validationParam ) : null; 
                } 
            } );
        }

        // proceed error visualization
        $.each( validationErrors, ( i, error ) => {

            error.input.parents( '.form-group' ).addClass( 'has-error' );
            error.input.addClass( 'is-invalid' );

            error.input.$validationContainer.text( error.msg );
            
            $( '<li>'+error.msg+'</li>' )
                .appendTo( O._$globalValidation );
            // let $errorMsg = $( '<div class="alert alert-danger alert-xs"></div>' )
            //     .appendTo( error.input.$validationContainer )
            //     .text( error.msg );

        } );
        O._$globalValidation.show();

        _log( validationErrors );

        return validationErrors;
    }


    /**
     * VALIDATE SIMULATION PARAM
     * run validation on param field
     */
     
    _validateSimField ( field ) {
        let O = this;
        _log( 'PANEL SIM / _validateSimField' );
        _log( field );

        if( field && field.input ) {

            let fieldValue = field.input.val();

            // check simulation name
            if( field.param.dataType.toLowerCase() === 'simname' ) {

                let idRegexp = /^[A-Za-z_^s]\w*$/;

                // name length
                if( fieldValue.length == 0 ) {
                    return {
                        input     : field.input,
                        msg     : 'Simulation name is required'
                    };
                }
                // name already exists?
                for ( let i = 0; i < O._simulations.length; ++i ) {
                    if ( fieldValue === O._simulations[i].name) {
                        return {
                            input     : field.input,
                            msg     : 'Simulation name already exists'
                        };
                    }
                }
                // name fits regexp
                if ( ! idRegexp.test( fieldValue ) ) {
                    return {
                        input     : field.input,
                        msg     : 'Not a valid simulation name (SId)'
                    };
                }
            }
            else if( field.param.dataType.toLowerCase() === 'simdescription' ) {


            }
            else {

                // no value
                if( ! fieldValue ) {
                    return {
                        input      : field.input,
                        msg     : 'Please provide a value for '+ field.param.id
                    };
                }

                // check range for integers and doubles
                if ( field.param.dataType.toLowerCase() === 'integer' 
                    || field.param.dataType.toLowerCase() === 'double' ) {
                    
                    if ( field.param.dataType.toLowerCase === 'integer' && fieldValue.indexOf('.') > 0 ){
                        return {
                                input   : field.input,
                                msg     : 'Invalid integer value'
                            };
                    }
                    if ( field.param.minValue && parseFloat( field.param.minValue ) > fieldValue ) {
                        return {
                            input      : field.input,
                            msg     : 'Invalid value. Value is lower than minimal value: '+ field.param.minValue
                        };
                    }
                    if ( field.param.maxValue && parseFloat( field.param.maxValue ) < fieldValue ) {
                        return {
                            input      : field.input,
                            msg     : 'Invalid value. Value is higher than maximum value: '+ field.param.maxValue
                        };
                    }
                }    
                else if ( field.param.dataType.toLowerCase() === 'file' ) {

                    // TBD

                }
                else if ( field.param.dataType.toLowerCase() === 'vectorofnumbers' ) {

                    // TBD

                }
                else if ( field.param.dataType.toLowerCase() === 'matrixofnumbers' ) {

                    // TBD
                    
                }

            }
        }
        
        return false;
    }
    /**
     * BUILD PANEL
     * build PANEL content
     * @param {event} event 
     */
     
    async _updateContent(_modelMetadata, _modelId, _simulations, paramsColorMap) {
        let O = this;
        _log( 'PANEL SIM / _updateContent' );
        O._setState( 'params' ); // reset state to form params when opening PANEL

        O._modelMetadata = _modelMetadata;
        O._modelId = _modelId;
        // clear tab-panes
        O._clear( O._$modalParams._$content );
        
        O._simSelectedIndex = 0; // reset simulation
        // get simulations
        O._simulations = _simulations;
        O.paramsColorMap = paramsColorMap;

        // create params        
        if ( O._simulations && O._simulations.length > 0 ) {

            // populate sim select
            O._populateSimSelect();
            // populate sim form
            O._populateSimForm();
            // update form groups' value
            O._updateSimForm( 0 );
            O._updateSimActions( 0 );
        }
    }

    /**
     * RUN MODEL VIEW
     * run modelview
     */
     
    async _runModelView ( modelId ) {
        let O = this;
        _log( 'PANEL SIM / _runModelView : '+ O._modelId, 'secondary' );

        modelId = modelId || O._modelId;

        if ( modelId >= 0 ) {    

            O._fetchController = new AbortController();
            O._signal = O._fetchController.signal;

            // clear tab content
            O._clear( O._$modalExecution._$content );
            O._clear( O._$modalExecution._$title );
            O._setState( 'execution' );

            // activate loader
            O._$container.addClass( 'loading' );
            O._opts._loader._setState( true );
            // create loading alert
            let $alert = _appUI._createAlert( 
                'executing model... please wait',
                {
                    type      : 'primary',
                    state     : 'show',
                    classes : 'm-1'
                },
                O._$modalExecution._$content
            );

            // TO DO 
            // run simulation by selected index

            // execute result
            let result = await _fetchData._content( _endpoints.execution, modelId, O._signal ); //O._app._getExecutionResult( modelId ) ;
            
            // clear tab content
            O._clear( O._$modalExecution._$content );
            O._clear( O._$modalExecution._$title );
            
            $alert.remove();

            // add executet simulation name as panel-title
            let $title = $( '<div class="panel-heading"></div>' )
                .text( O._simulations[O._selectedSimIndex].name )
                .appendTo( O._$modalExecution._$content );

            // add result as plot
            let $plot = $( result )
                .appendTo( O._$modalExecution._$content )
                .wrapAll( '<div class="panel-plot"></div>' );

            // deactivate loader
            O._$container.removeClass( 'loading' );
            O._opts._loader._setState( false );

            // callback
            if ( $.isFunction( O.opts.on.simRunModelView ) ) {
                O.opts.on.simRunModelView.call( O, O, modelId, O._simulations[O._selectedSimIndex] );
            }
        }
    }


    /**
     * CLEAR
     * removes content from element
     * @param {jquery selector} $elem
     */

    _clear ( $elem ) {
        let O = this;
        _log( 'PANEL SIM / _clear' );
        _log( $elem );

        if ( $elem ) {
            $elem.empty();
        }
    }
    
    /**
     * CLEAR
     * removes content from element
     * @param {string} state: execute or params
     */

    _setState ( state ) {
        let O = this;
        _log( 'PANEL SIM / _setState: '+ state );
        if ( state && O._state != state ) {

            if( state == 'execution' ) {
                O._$modalExecution.show();
                O._$modalParams.hide();
                O._$modalNav.hide();
            }
            else {
                O._$modalExecution.hide();
                O._$modalParams.show();
                O._$modalNav.show();
            }
            // set sim modal state
            O._state = state;
        }
    }
    _setSavedState ( state ) {
        // set sim modal state
        this._savedState = state;
    }
    
    _getRandomInt(max) {
      return Math.floor(Math.random() * max);
    }
}