import { LasalRuntimeSigElement, SigPolymer } from '../../sigmatek/sig-element/sig-element.js';
import { RecipeHandling } from './sig-control-recipe-handling-mixin.js';
import "../../user/sig-control-titlebar/sig-control-titlebar.js";
import "../../user/sig-control-titlebar/sig-control-titlebar-css.js";
import "../../user/sig-control-datepicker/sig-control-datepicker.js"
import '../../user/sig-control-datepicker/sig-control-datepicker-css.js'
import "../../user/sig-control-button/sig-control-button.js";
import "../../user/sig-control-button/sig-control-button-css.js";
import "../../user/sig-control-input/sig-control-input.js";
import "../../user/sig-control-input/sig-control-input-css.js";
// import "../../user/sig-control-enum/sig-control-enum.js"
// import '../../user/sig-control-enum/sig-control-enum-css.js'
import "../../user/sig-control-listview/sig-control-listview.js";
import "../../user/sig-control-listview/sig-control-listview-css.js";

import '../../user/sig-control-scrollbar/sig-control-scrollbar-css.js'

import './moment-with-locales.js';

/**
 * @class
 * @classdesc Implements sig-control-recipe-handling.
 * @mixes Recipehandling
 * @extends {LasalRuntimeSigElement}
 * 
 */

export class LasalRuntimeSigControlRecipeHandling extends RecipeHandling(LasalRuntimeSigElement) {


    /**
     * define the custom tag name 
     *
     * @readonly
     * @static
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    static get is() {
        return "sig-control-recipe-handling";
    }


    /**
     * When the element is loaded, importMeta() assigns reference to the path from 
     * which an element was imported to its importPath property. 
     * Relative URLs in styles are automatically re-written to be relative to the importPath property. 
     * You can use [[importPath]] in your component to load url resources
     *
     * @readonly
     * @static
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    static get importMeta() { return import.meta; }


    /**
     * Returns the Template HTML & CSS of this Control
     *
     * @readonly
     * @static
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    static get template() {
        return SigPolymer.html`
            <style include="sig-element-css sig-control-titlebar-css sig-control-button-css sig-control-input-css sig-control-listview-css sig-control-scrollbar-css sig-control-datepicker-css">
                :host {
                    /* Basic styling of the component */
                    color: var(--theme-sig-control-recipe-handling-color, inherit);
                    background-color: var(--theme-sig-control-recipe-handling-background-color, var(--palette-default-color253));

                    border-width: var(--theme-sig-control-recipe-handling-border-width, 2px);
                    border-color: var(--theme-sig-control-recipe-handling-border-color, var(--palette-default-color244));
                    border-style: var(--theme-sig-control-recipe-handling-border-style, solid);
                    border-radius: var(--theme-sig-control-recipe-handling-border-radius, 3px);

                    /* Basic box model settings */
                    display: var(--theme-sig-control-recipe-handling-display, block);
                    position: var(--theme-sig-control-recipe-handling-position, absolute);
                    margin: var(--theme-sig-control-recipe-handling-margin, 0);
                    padding: var(--theme-sig-control-recipe-handling-padding, 0);
                    overflow: var(--theme-sig-control-recipe-handling-overflow, hidden);

                    top: var(--theme-sig-control-recipe-handling-top, var(--theme-sig-element-top, auto));
                    left: var(--theme-sig-control-recipe-handling-left, var(--theme-sig-element-left, auto));
                    width: var(--theme-sig-control-recipe-handling-width, var(--theme-sig-element-width, auto));
                    height: var(--theme-sig-control-recipe-handling-height, var(--theme-sig-element-height, auto));
                }

                :host * {
                    /* Reset margin and padding for child elements and turn off text selection */
                    @apply --notextselect;
                    margin: 0;
                    padding: 0;
                }

                .clearfix:after {
                    /* Import the global clearfix css mixin */
                    @apply --clearfix;
                }

                .sig-control-recipe-handling {
                    /* should be relative for use in designer */
                    display: flex;
                    flex-flow: column;
                    align-items: stretch;

                    position: relative;
                    width: 100%;
                    height: 100%;
                    top: 0;
                    left: 0;
                }

                :host([hidetitlebar]) sig-control-titlebar {
                    display: none !important;
                }

                :host([hideoverlaytitle]) sig-control-titlebar#overlayTitlebar{
                    display: none !important;
                }

                :host sig-control-titlebar {
                    position: relative;
                    /* height: 100%; */
                    width: 100%;
                    top: 0;
                    left: 0;

                    /* position: unset; */
                    display: block;

                    --theme-sig-control-recipe-handling-titlebar-height: var(--theme-sig-element-height, 26px);
                    height: var(--theme-sig-control-recipe-handling-titlebar-height);
                }

                :host sig-control-listview {
                    position: relative;
                    height: 100%;
                    width: 100%;
                    top: 0;
                    left: 0;

                    /* overwrite the background coler of the listview with that from the recipe handling */
                    --theme-sig-control-listview-background-color: var(--theme-sig-control-recipe-handling-background-color, var(--palette-default-color253)) !important;
                }

                :host sig-control-button {
                    --theme-sig-control-button-height: var(--theme-sig-element-height, 50px);
                    height: var(--theme-sig-control-button-height, 50px);
                    width: var(--theme-sig-element-width, 100px); 
                }

                /*******************************************************************************************************************/
                /**************************************************** HEADER *******************************************************/
                /*******************************************************************************************************************/
                #recipesheader{
                    height: auto;
                    flex: 0 1 auto;
                }

                /*******************************************************************************************************************/
                /***************************************************** MAIN ********************************************************/
                /*******************************************************************************************************************/
                #recipesmain{
                    overflow: auto;
                    flex: 1 1 100%;
                    width: 100%;
                    height: 100%;
                }

                #recipesmain > #listcontainer{
                    top: 0;
                    left: 0;
                    min-height: 100%;
                    width: 100%;
                    position: relative;
                }

                #overlayBackground {
                    position: absolute;
                    top: 0;
                    left: 0px;
                    width: 100%;
                    height: 100%;
                    display: none;
                }

                #overlayBackground.visible {
                    display: block;
                    z-index: 1000;
                }

                :host([isdesignmode]) #overlayBackground.visible {
                    z-index: 180;
                }

                :host([isdesignmode]) #overlayWrapper {
                    pointer-events: none;
                }

                #overlayWrapper {
                    overflow: hidden;

                    position:absolute;
                    height: var(--theme-sig-control-recipe-handling-overlay-height, 33%);
                    width: var(--theme-sig-control-recipe-handling-overlay-width, 100%);
                    bottom: var(--theme-sig-control-recipe-handling-overlay-bottom, 0px);
                    left: var(--theme-sig-control-recipe-handling-overlay-left, 0px);
                    z-index: 101;

                    color: var(--theme-sig-control-recipe-handling-color, inherit);
                    background-color: var(--theme-sig-control-recipe-handling-overlay-background-color, rgba(255,255,255,1));

                    border-width: var(--theme-sig-control-recipe-handling-overlay-border-width, 0px);
                    border-color: var(--theme-sig-control-recipe-handling-overlay-border-color, transparent);
                    border-style: var(--theme-sig-control-recipe-handling-overlay-border-style, none);
                    border-radius: var(--theme-sig-control-recipe-handling-overlay-border-radius, 0px);

                    opacity: 0;
                    transition: opacity .2s ease-in-out;

                    box-sizing: border-box;
                }

                .visible #overlayWrapper {
                    opacity: 1;
                }

                #overlayContent {
                    height: calc(100% - var(--theme-sig-control-recipe-handling-overlay-header-height, 26px));
                    box-sizing: border-box;
                }

                #overlayFilterViewContent {
                    display: grid;
                    grid-template-columns: auto 1fr 1fr;
                    grid-template-rows: auto;
                    grid-template-areas:
                        "text1 filter1 filter1"
                        "text2 filter2 filter2"
                        ". . button";
                    column-gap: 10px;
                    row-gap: 5px;
                    height: 100%;
                }

                #overlayWrapper[overlaytype^="filter"] #overlayFilterViewContent {
                    display: grid;
                }
                #overlayWrapper:not([overlaytype^="filter"]) #overlayFilterViewContent {
                    display: none;
                }

                #overlayWrapper[overlaytype^="filter"]:not([filterkey^='modified']) #overlayFilterViewContent {
                    grid-template-columns: 1fr 1fr;
                    grid-template-areas:
                        "filter1 filter1"
                        ". ."
                        ". button";
                }

                #overlayWrapper[filterkey^='modified'] sig-control-datepicker,
                #overlayWrapper[filterkey^='modified'] #filter_text_row1,
                #overlayWrapper[filterkey^='modified'] #filter_text_row2 {
                    display: initial;
                }

                #overlayWrapper:not([filterkey^='modified']) sig-control-input {
                    display: initial;
                }

                #overlayContent sig-control-datepicker,
                #overlayContent sig-control-input,
                #overlayContent sig-control-enum {
                    display: none;
                }

                #overlayContent sig-control-enum {
                    position: unset;
                }

                #overlayContent sig-control-datepicker,
                #overlayContent sig-control-button,
                #overlayContent sig-control-input
                /* sig-control-enum */ {
                    position: unset;
                    width: 100%;
                    height: 100%;
                /*  --theme-sig-element-height: 100% !important;
                    --theme-sig-element-width: 100% !important; */
                }

                #filter_fromDate {grid-area: filter1;}
                #filter_toDate {grid-area: filter2;}
                #filter_input_1 {grid-area: filter1;}
                #filter_enum {grid-area: filter1;}
                #filter_submit {grid-area: button;}
                #filter_text_row1 {grid-area: text1; display: none; align-self: center;}
                #filter_text_row2 {grid-area: text2; display: none; align-self: center;}

                #closeOverlayBtn {
                    position: absolute;
                    top: 0px;
                    right: 5px;
                    height: var(--theme-sig-control-recipe-handling-overlay-header-height, 20px);
                    width: var(--theme-sig-control-recipe-handling-overlay-header-height, 20px);
                    border: none;
                    background-color: transparent;
                    transition: background-color .2s ease-in-out;
                    cursor: pointer;
                }

                #closeOverlayBtn:before, #closeOverlayBtn:after {
                    position: absolute;
                    top: 5%;
                    left: 50%;
                    content: ' ';
                    height: 90%;
                    width: 3px;
                    /* thats the color of the X */
                    background-color: var(--theme-sig-control-recipe-handling-overlay-header-text-color, rgba(0,0,0,1));
                }
                #closeOverlayBtn:before {
                    transform: rotate(45deg);
                }
                #closeOverlayBtn:after {
                    transform: rotate(-45deg);
                }

                #overlayContent {
                    padding: var(--theme-sig-control-recipe-handling-overlay-content-padding-top, 5px) var(--theme-sig-control-recipe-handling-overlay-content-padding-right, 5px) var(--theme-sig-control-recipe-handling-overlay-content-padding-bottom, 5px) var(--theme-sig-control-recipe-handling-overlay-content-padding-left, 5px);
                    color: var(--theme-sig-control-recipe-handling-overlay-text-color, rgba(0,0,0,1));
                }   

                #wait {
                    position: absolute;
                    display: none;
                    top: 0;
                    left: 0;
                    width: 100%;
                    height: 100%;
                    background: transparent none;
                    z-index: 2000;
                }

                :host([isdesignmode]) #wait {
                    z-index: 200;
                }

                :host([showiswaiting]) #wait {
                    display: block;
                }

                :host([showprogress]) #wait,
                :host(:not([isonline])) #wait {
                    background-color: var(--theme-sig-control-recipe-handling-progress-dialog-modal-background-color, rgba(215, 215, 215, 1));
                }

                .disabled {
                    opacity: .5;
                    pointer-events: none;
                }

                #progressbox {
                    position: absolute;
                    background-color: var(--theme-sig-control-recipe-handling-progress-dialog-background-color, rgba(42, 42, 42, 1));
                    color: var(--theme-sig-control-recipe-handling-progress-dialog-color, rgba(255, 255, 255, 1));
                    top: 0;
                    left: 0;
                    right: 0;
                    bottom: 0;
                    margin: auto;
                    width: 80%;
                    height: var(--theme-sig-control-recipe-handling-progress-dialog-height, 120px);
                    min-height: 120px;
                    display: none;
                    overflow: hidden;
                }

                :host([showprogress]:not([progresswindowset])) #progressbox {
                    display: block;
                }

                :host([isdesignmode]) #progressbox {
                    pointer-events: none;
                }

                .progressbarouter {
                    left: 0;
                    bottom: 10px;
                    position: absolute;
                    overflow: hidden;
                    width: 100%;
                    height: calc(var(--theme-sig-control-recipe-handling-progress-dialog-height, 120px) - 50% - 10px);
                    text-decoration: inherit;
                }

                .progressbarinner {
                    top: 0;
                    left: 0;
                    position: relative;
                    margin: 0 10px !important;
                    width: calc(100% - 20px);
                    height: 100%;
                    text-decoration: inherit;
                }

                .progressbar {
                    position: absolute;
                    top: 0;
                    left: 0;
                    height: 100%;
                    width: 0;
                    overflow: hidden;
                    background-color: var(--theme-sig-control-recipe-handling-progress-dialog-bar-background-color, rgba(160, 160, 160, 1));
                    color: var(--theme-sig-control-recipe-handling-progress-dialog-bar-color, rgba(255, 255, 255, 1));
                    line-height: calc((var(--theme-sig-control-recipe-handling-progress-dialog-height, 120px) - 50% - 10px) / 2);
                    text-indent: 5px;
                    text-align: center;
                    text-decoration: inherit;
                }

                .progressmessage {
                    padding: 10px !important;
                    position: absolute;
                    text-overflow: ellipsis;
                    overflow: hidden;
                    white-space: nowrap;
                    width: calc(100% - 20px);
                    height: auto;
                    text-decoration: inherit;
                }

                /*******************************************************************************************************************/
                /**************************************************** FOOTER *******************************************************/
                /*******************************************************************************************************************/
                #recipesfooter{
                    /* height: auto;
                    width: auto; */
                    height: auto;
                    width: auto;
                    flex: 0 0 auto;

                    background-color: var(--theme-sig-control-recipe-handling-footer-background-color, var(--palette-default-color244));

                    border-top-width: var(--theme-sig-control-recipe-handling-footer-border-width, 2px);
                    border-top-color: var(--theme-sig-control-recipe-handling-footer-border-color, var(--palette-default-color244));
                    border-top-style: var(--theme-sig-control-recipe-handling-footer-border-style, solid);

                    padding-top: var(--theme-sig-control-recipe-handling-footer-content-padding-top, 5px);
                    padding-right: var(--theme-sig-control-recipe-handling-footer-content-padding-right, 5px);
                    padding-bottom: var(--theme-sig-control-recipe-handling-footer-content-padding-bottom, 5px);
                    padding-left: var(--theme-sig-control-recipe-handling-footer-content-padding-left, 5px);
                }
                
                #recipesfooter > .button{
                    top: 0;
                    left: 0;
                    position: relative;
                }

                .buttons {
                    clear: both;
                    height: auto;
                    width: 100%;
                }

                .button {
                    position: unset;
                }

                .button.right {
                    margin-left: var(--theme-sig-control-recipe-handling-button-margin, 10px);
                    float: right;
                }

                .button.left {
                    margin-right: var(--theme-sig-control-recipe-handling-button-margin, 10px);
                    float: left;
                }

                :host([hideload]) #loadrecipe {
                    display: none;
                }

                :host([hidesave]) #saverecipe {
                    display: none;
                }

                :host([hidecopy]) #copyrecipe {
                    display: none;
                }

                :host([hiderename]) #renamerecipe {
                    display: none;
                }

                :host([hidedelete]) #deleterecipe {
                    display: none;
                }

                :host(([savebtndisabled])) #savecontainer *,
                :host(([loadbtndisabled])) #loadcontainer *,
                :host(([copybtndisabled])) #copycontainer *,
                :host(([renamebtndisabled])) #renamecontainer *,
                :host(([deletebtndisabled])) #deletecontainer * {
                    pointer-events: none !important;
                    opacity: 0.7;
                }

            </style>
            <!--------------------------------------------------------------------------------------------------------------------------->
            <!-- BODY -->
            <!--------------------------------------------------------------------------------------------------------------------------->
            <div class="sig-control-recipe-handling clearfix">
                <div id="wait">
                    <div id="progressbox">
                        <div class="progressbarouter">
                            <div class="progressbarinner">
                                <div id="progressbar" class="progressbar">[[progress]] %</div>
                            </div>
                        </div>
                        <div class="progressmessage">[[currentaction]]</div>
                    </div>
                </div>
                <!-- HEADER -->
                <!------------------------------------------------------------------------------>
                <div id="recipesheader">
                    <sig-control-titlebar id="recipestitle" class="titlebar" value="[[texts.caption]]" shadowactive="[[titlebarshadowactive]]" applybordereffect="[[applybordereffect]]"></sig-control-titlebar>
                </div>
                <!-- MAIN -->
                <!------------------------------------------------------------------------------>
                <div id="recipesmain">
                    <div id="listcontainer">
                        <sig-control-listview id="recipeslistgrid"></sig-control-listview>
                    </div>
                    <div id="overlayBackground">
                        <div id="overlayWrapper">
                            <div>
                                <sig-control-titlebar id="overlayTitlebar" value="[[overlaytitletext]]" applybordereffect="[[applybordereffect]]"
                                    shadowactive="[[shadowactive]]">
                                </sig-control-titlebar>
                                <a id="closeOverlayBtn"></a>
                            </div>
                            <div id="overlayContent">
                                <!-- <div id="overlayDetailViewContent"> -->
                                    <!-- <p id="longText">Filter: </p> -->
                                <!-- </div> -->
                                <div id="overlayFilterViewContent">
                                    <p id="filter_text_row1">[[modifiedfilterfromtext]]</p>
                                    <sig-control-datepicker id="filter_fromDate" class="datepicker" context$=[[context]] value={{filterSettings.datepicker.value_from}}></sig-control-datepicker>
                                    <p id="filter_text_row2">[[modifiedfiltertotext]]</p>
                                    <sig-control-datepicker id="filter_toDate" class="datepicker" context$=[[context]] value={{filterSettings.datepicker.value_to}}></sig-control-datepicker>

                                    <sig-control-input id="filter_input_1" class="inputs" type$="[[filterSettings.input_1.type]]"
                                        limit-low$="[[filterSettings.input_1.limit_low]]" limit-high$="[[filterSettings.input_1.limit_high]]"
                                        name$="[[filterSettings.input_1.title]]" titletext="[[filterSettings.input_1.title]]" value={{filterSettings.input_1.value}}></sig-control-input>
                                        
                                    <sig-control-button id="filter_submit" class="buttonclass" sigbtntext="[[filter_submitbuttontext]]"></sig-control-button>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <!-- FOOTER -->
                <!------------------------------------------------------------------------------>
                <div id="recipesfooter">
                    <div id="buttons" class="buttons clearfix">
                        <div id="savecontainer">
                            <sig-control-button id="saverecipe" sigbtntext="[[texts.save]]" class="button right savebutton" on-tap="_saveSequence" sigbtndisabled="[[savebtndisabled]]"></sig-control-button>
                        </div>
                        <div id="loadcontainer">
                            <sig-control-button id="loadrecipe" sigbtntext="[[texts.load]]" class="button right loadbutton" on-tap="_loadSequence" sigbtndisabled="[[loadbtndisabled]]"></sig-control-button>
                        </div>
                        <div id="copycontainer">
                            <sig-control-button id="copyrecipe" sigbtntext="[[texts.copy]]" class="button right copybutton" on-tap="_copySequence" sigbtndisabled="[[copybtndisabled]]"></sig-control-button>
                        </div>
                        <div id="renamecontainer">
                            <sig-control-button id="renamerecipe" sigbtntext="[[texts.rename]]" class="button right renamebutton" on-tap="_renameSequence" sigbtndisabled="[[renamebtndisabled]]"></sig-control-button>
                        </div>
                        <div id="deletecontainer">
                            <sig-control-button id="deleterecipe" sigbtntext="[[texts.delete]]" class="button right deletebutton" on-tap="_deleteSequence" sigbtndisabled="[[deletebtndisabled]]"></sig-control-button>
                        </div>
                    </div>
                </div>
            </div>`;
    }

    /**********************************************************************************************************************************************************
     * define the main porperties of the component
     * reflected property names should always be lower case so it can be used directly on the element as an attribute
    ***********************************************************************************************************************************************************/
    static get properties() {
        let props = {
        // ################################################################
        // json properties
        // ################################################################
            // data
            // ############################
                startpath: {
                    type: String,
                    value: 'c:\\',
                    observer: '_toggleStartPath'
                },
                selectedrecipe: {
                    type: String,
                    notify: true,
                    value: "",
                    readOnly: true
                },
                selectedrecipefilepath: {
                    type: String,
                    notify: true,
                    value: "",
                    readOnly: true
                },
                loadedrecipe: {
                    type: String,
                    notify: true,
                    value: "",
                    readOnly: true
                },
                loadedrecipefilepath: {
                    type: String,
                    notify: true,
                    value: "",
                    readOnly: true
                },
                activerecipe: {
                    type: String,
                    notify: true,
                    value: ""
                },
                activerecipefilepath: {
                    type: String,
                    notify: true,
                    value: ""
                },
            // filter variables
            // ############################
                filter11: {
                    type: Number,
                    value: 0,
                    observer: '_updateFilter',
                    notify: true
                },
                filter12: {
                    type: Number,
                    value: 0,
                    observer: '_updateFilter',
                    notify: true
                },
                filter21: {
                    type: Number,
                    value: 0,
                    observer: '_updateFilter',
                    notify: true
                },
                filter22: {
                    type: Number,
                    value: 0,
                    observer: '_updateFilter',
                    notify: true
                },
            // settings
            // ############################
                usesimplemode: {
                    type: Boolean,
                    value: true
                },
                metafileforlistview: {
                    type: String,
                    value: '',
                    observer: '_updateMetafileForListview'
                },
                metafileforrecipe: {
                    type: String,
                    value: '',
                    observer: '_updateMetafileForRecipe'
                },
                recipefileextension: {
                    type: String,
                 //   value: 'txt',
                    value: '',
                    observer: '_updateRecipeFileExtension'
                },
                hidetitlebar: {
                    type: Boolean,
                    value: false,
                    reflectToAttribute: true
                },
            // save settings
            // ############################
                allowcreation: {
                    type: Boolean,
                    value: true
                },
                confirmsave: {
                    type: Boolean,
                    value: true
                },
                allowcreationwnd: {
                    type: Object,
                    value: () => { return {} }
                },
                confirmsavewnd: {
                    type: Object,
                    value: () => { return {} }
                },
                showsavesuccessful: {
                    type: Boolean,
                    value: true
                },
                savesuccessfulwnd: {
                    type: Object,
                    value: () => { return {} }
                },
                showsavefailed: {
                    type: Boolean,
                    value: true
                },
                savefailedwnd: {
                    type: Object,
                    value: () => { return {} }
                },
            // load settings
            // ############################
                confirmload: {
                    type: Boolean,
                    value: true
                },
                confirmloadwnd: {
                    type: Object,
                    value: () => { return {} }
                },
                hidesave: {
                    type: Boolean,
                    value: false,
                    reflectToAttribute: true
                },
                hideload: {
                    type: Boolean,
                    value: false,
                    reflectToAttribute: true
                },
                showloadsuccessful: {
                    type: Boolean,
                    value: true
                },
                loadsuccessfulwnd: {
                    type: Object,
                    value: () => { return {} }
                },
                showloadfailed: {
                    type: Boolean,
                    value: true
                },
                loadfailedwnd: {
                    type: Object,
                    value: () => { return {} }
                },
            // rename settings
            // ############################
                renamewnd: {
                    type: Object,
                    value: () => { return {} }
                },
                hiderename: {
                    type: Boolean,
                    value: false,
                    reflectToAttribute: true
                },
                confirmrename: {
                    type: Boolean,
                    value: true
                },
                confirmrenamewnd: {
                    type: Object,
                    value: () => { return {} }
                },
            // copy settings
            // ############################
                hidecopy: {
                    type: Boolean,
                    value: false,
                    reflectToAttribute: true
                },
                confirmcopy: {
                    type: Boolean,
                    value: true
                },
                confirmcopywnd: {
                    type: Object,
                    value: () => { return {} }
                },
                copywnd: {
                    type: Object,
                    value: () => { return {} }
                },
            // delete settings
            // ############################
                hidedelete: {
                    type: Boolean,
                    value: false,
                    reflectToAttribute: true
                },
                confirmdelete: {
                    type: Boolean,
                    value: true
                },
                confirmdeletewnd: {
                    type: Object,
                    value: () => { return {} }
                },
                showdeletesuccessful: {
                    type: Boolean,
                    value: true
                },
                deletesuccessfulwnd: {
                    type: Object,
                    value: () => { return {} }
                },
                showdeletefailed: {
                    type: Boolean,
                    value: true
                },
                deletefailedwnd: {
                    type: Object,
                    value: () => { return {} }
                },            
            // progress dialog styling
            // ############################
                progresswindow: {
                    type: Object,
                    value: () => { return {} },
                    observer: '_checkCustomProgressWindow'    
                },
            // overlay styling
            // ############################
                hideoverlaytitle: {
                    type: Boolean,
                    value: false,
                    reflectToAttribute: true
                },
            // columns settings: name
            // ############################
                namevisible: {
                    type: Boolean,
                    value: true,
                    observer: '_updateColumns'
                },
                namesortable: {
                    type: Boolean,
                    value: true,
                    observer: '_updateColumns'
                },
                namefilterable: {
                    type: Boolean,
                    value: true,
                    observer: '_updateColumns'
                },
                namefilterwindow: {
                    type: Object,
                    value: () => { return {} }
                },
                nametitle: {
                    type: String,
                    value: 'Name',
                    observer: '_updateColumns'
                },
                namewidth: {
                    type: String,
                    value: '120px',
                    observer: '_updateColumns'
                },
                nameorder:{
                    type: Number,
                    value: 10,
                    observer: '_updateColumns'
                },
                filter_name: {
                    type: String, 
                    value: '',
                    // observer: '_handleFilterUpdate_name'
                    observer: '_handleFilterUpdate'
                },
            // columns settings: modified
            // ############################
                modifiedvisible: {
                    type: Boolean,
                    value: true,
                    observer: '_updateColumns'
                },
                modifiedsortable: {
                    type: Boolean,
                    value: true,
                    observer: '_updateColumns'
                },
                datetimeformat: {
                    type: String,
                    value: "DD.MM.YYYY HH:mm",
                    observer: '_updateColumns'
                },
                modifiedfilterable: {
                    type: Boolean,
                    value: true,
                    observer: '_updateColumns'
                },
                modifiedfilterfromtext: {
                    type: String,
                    value: 'From:'
                },
                modifiedfiltertotext: {
                    type: String,
                    value: 'To:'
                },
                modifiedfilterwindow: {
                    type: Object,
                    value: () => { return {} }
                },
                modifiedtitle: {
                    type: String,
                    value: 'Modified',
                    observer: '_updateColumns'
                },
                modifiedwidth: {
                    type: String,
                    value: '150px',
                    observer: '_updateColumns'
                },
                modifiedorder:{
                    type: Number,
                    value: 20,
                    observer: '_updateColumns'
                },
                filter_date_from: {
                    type: Number, 
                    value: null,
                    // observer: '_handleFilterUpdate_dateFrom'
                    observer: '_handleFilterUpdate'
                },
                filter_date_to: {
                    type: Number,
                    value: null,
                    // observer: '_handleFilterUpdate_dateTo'
                    observer: '_handleFilterUpdate'
                },
            // columns settings: general
            // ############################
                generalfilterwindow: {
                    type: Object,
                    value: () => { return {} }
                },
            // Design time settings
            // ############################
                showfilteroverlay: {
                    type: Boolean,
                    value: false,
                    observer: '_designModeShowOverlay'
                },
                designtimeshowprogress: {
                    type: Boolean,
                    value: false,
                    reflectToAttribute: true,
                    observer: '_designtimeshowprogress'
                },
        // ################################################################
        // local properties
        // ################################################################
            // data variables
            // ############################
                activerow: {
                    type: Object,
                    notify: true,
                    value: () => {
                        return {
                            active: null
                        }
                    }
                },
                activepath: {
                    type: String,
                    notify: true,
                    value: null,
                    observer: '_updateActivePath'
                },
                // activepathdata: {
                //     type: String,
                //     value: "",
                //     notify: true,
                //     readOnly: true
                // },
                currentaction: {
                    type: String,
                    value: "",
                    reflectToAttribute: true
                },
                iscmdactive: {
                    type: Boolean,
                    value: false,
                    observer: '_disableInputs'
                },
            // progress variables
            // ############################
                isonline: {
                    type: Boolean,
                    value: false,
                    reflectToAttribute: true
                },
                iswaiting: {
                    type: Boolean,
                    value: false,
                    reflectToAttribute: true,
                    observer: '_toggleIsWaiting'
                },
                showiswaiting: {
                    type: Boolean,
                    value: false,
                    reflectToAttribute: true
                },
                progress: {
                    type: Number,
                    value: 0
                },
                progressc: {
                    type: String,
                    computed: '_computeProgress(progress)'
                },
                showprogress: {
                    type: Boolean,
                    value: false,
                    reflectToAttribute: true,
                    observer: '_toggleShowProgress'
                },
                progresswindowset: {
                    type: Boolean,
                    value: false,
                    reflectToAttribute: true
                },
            // button variables
            // ############################
                savebtndisabled: {
                    type: Boolean,
                    value: false,
                    reflectToAttribute: true
                },
                loadbtndisabled: {
                    type: Boolean,
                    value: false,
                    reflectToAttribute: true
                },
                copybtndisabled: {
                    type: Boolean,
                    value: false,
                    reflectToAttribute: true
                },
                renamebtndisabled: {
                    type: Boolean,
                    value: false,
                    reflectToAttribute: true
                },
                deletebtndisabled: {
                    type: Boolean,
                    value: false,
                    reflectToAttribute: true
                },
            // overlay variables
            // ############################
                overlaytitletext: {
                    type: String,
                    value: ''
                },
                overlaycontentfont: {
                    type: Object,
                    value: (() => {return {}})(),
                    observer: '_applFontObjects'
                },
            // filter variables
            // ############################
                filter_submitbuttontext: {
                    type: String,
                    value: 'Apply'
                },
                filter1: {
                    type: Number,
                    value: 0
                },
                filter2: {
                    type: Number,
                    value: 0
                },
            // window variables
            // ############################
                windowopened: {
                    type: Boolean,
                    value: false,
                    observer: "_toggleWindowOpened"
                },
            // key variables
            // ############################
                // isactive: {
                //     type: Boolean,
                //     value: false,
                //     reflectToAttribute: true,
                //     observer: '_toggleIsActive'
                // },
                disablekeyactions: {
                    type: Boolean,
                    value: false
                },
                disablekeymovement: {
                    type: Boolean,
                    value: false,
                    observer: '_toggleDisableKeyMovement'
                },
                disablekeysindialogs: {
                    type: Boolean,
                    value: false,
                    observer: '_toggleDisableKeysInDialogs'
                }
        }
        return props;
    }

    /**
     * Returns the defined complex observers used by Polymer.
     * @readonly
     * @static
     * @returns {Array} An array containing the defined complex observers.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    static get observers() {
        return [
        ]
    }

    /**
     * Creates an instance of LasalRuntimeSigControlRecipeHandling.
     * Called when the element has been created, but before property values are set and local DOM is initialized
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    constructor() {
        super();

        // the fonts for the listview, titlebar and overlay
        this.fontObjHeader = null;
        this.fontObjBody = null;
        this.fontObjTitle = null;

        // debouncer
        this.updateColumnsDebouncer = 0;
        this.debouncerProgress = 0;
        this.overlayBtnDebouncer = 0;
        this.fontDebouncer = 0;
        this.updatePathDebouncer = 0;

        // set a valid start path
        this.validStartPath = '/';
        this.oldPath = null;

        // variables
        this.refreshCount = 0;
        this.refreshCountMax = 1;

        this.isonline = true;

        this.gridConfig = {};
        this.listviewConfigOfMetafile = null;
        this.recipesMetaData = [];

        this.userdefinedcrc = -1;

        // progress dialog
        this.progress = 0;
        this.showprogress = false;
        this.iswaiting = false;
        this.showiswaiting = false;
        this.iswaitingTimeout = null;
        this.iswaitingDelay = 100;

        this.metafileextension = "json";

        // test variables
        this.test_showprogress = false;
        this.test_showprogressInterval = null;

        // storage initialization
        this.recipeHandlingStorage_KeyIdentifier = "sigRecipeHandlingStorage_";
        this.recipeHandlingStorage = this._isStorageSupported(sessionStorage);

        this.sortdata = {
            sorttype: 'none'
        };

        // A helper object to initialize the filters
        const _filterInputObj = {
            limit_low: null,
            limit_high: null,
            value: ''
        }

        // Default settings for the filter inputs
        this.filterSettings = {
            input_1: Object.assign({type: null}, _filterInputObj),                          // the input for the recipe filter
            datepicker: Object.assign({value_from: '', value_to: ''}, _filterInputObj)      // the inputs for the date range
        }

        this.minMax = {
            date: {
                min: null,
                max: null
            }
        }

        // We need to pass this string to moment.js as the format string to get a valid date
        this.sigDateTimeFormat = 'DD.MM.YYYY HH:mm:ss';

        // Is used to determine if this control triggerd an update or not
        // this.iUpdatedFilterSort = false;
        // Is used to determine if the filer/sort of the runtime buffer needs to be updated
        // this.updateFilterSort = true;

        // Color and height of the colosebutton of the overlays
        this.overlayCloseBtnSettings = {
            color: '',
            height: ''
        }

        // create shortcuts to the common APIs - this makes the code more readable
        if (window.sigApi) {
            this.evtMedApi = window.sigApi.eventMediator;
            this.evtApi = window.sigApi.events;
            this.tmApi = window.sigApi.textManager;
            this.vmApi = window.sigApi.vm;
            this.utilsApi = window.sigApi.sigUtils;
            this.dsApi = window.sigApi.defaultStyleManager;
            this.fmApi = window.sigApi.fileManager;
            this.rmApi = window.sigApi.recipeManager;
            this.wmApi = window.sigApi.windowManager;
            this.constApi = window.sigApi.SIG_CONST;
            this.controlTextList = 'controlTexts_sig_control_recipe_handling';
        } else {
            this._log('API not ready!', 'ERROR');
        }

        this.initialized = false;

        this.sigAddRequiredProperty('startpath');
        this.sigAddRequiredProperty('metafileforlistview');
        this.sigAddRequiredProperty('metafileforrecipe');
        this.sigAddRequiredProperty('recipefileextension');
    }

   
    /**
     * called after property default values are set and local DOM is initialized
     *
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    ready() {
        super.ready();
        this.isReady = true;
        //check if ready to initialize
        if (this.isdesignmode) {
            SigPolymer.afterNextRender(this, () => {
                this._init();
            })
        }
        else if (this.requiredPropertiesReady) {
            this._init();
        }
    }
    

    /**
     * This callback runs when the component is connected in the DOM.
     * 
     * @returns {Undefined} Undefined
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    connectedCallback() {
        super.connectedCallback();

        if (!this.isdesignmode) {
            // Bind the event mediator for language switch
            this.sigAddEventListener('LANGUAGE_CHG', (majorId, minorId, msg) => {
                // necessary to reset filter?
                // this.filter = null;
                this._switchLang(majorId, minorId, msg);
            }, 'runtimeEvent');

            // Bind the event mediator for ws state change
            this.sigAddEventListener('WS_STATE_CHG', (majorId, minorId, msg) => {
                this._onWSStateChg(majorId, minorId, msg);
            }, 'runtimeEvent');

            // Bind the event mediator for route change
            this.sigAddEventListener('APPROUTE_CHG', (maID, miID, message) => {
                this._refreshPane();
            }, 'runtimeEvent');

            // set sort
            const sortDataStorage = this._getStorageItem(this.recipeHandlingStorage, this.id +'sortdata');
            if(sortDataStorage) {
                this.sortdata = JSON.parse(sortDataStorage);
            }

            // Add the eventlistener for the overlay close button
            this.sigAddEventListener('click', () => this._closeOverlay(), 'jsEvent', this.$.closeOverlayBtn);
        }
    }

    /**
     * This overrides the method inherited from the sig-element.
     * 
     * @override
     * @returns {Undefined} Undefined
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    sigOnRequiredPropertiesReady() {
        super.sigOnRequiredPropertiesReady();
        this.requiredPropertiesReady = true;
        if (this.isReady) {
            this._init();
        }
    };

    /**
    * Initializes the component.
    * 
    * @private
    * @returns {Undefined} Undefined
    * @memberof LasalRuntimeSigControlRecipeHandling
    */
    _init() {
        this.gridConfig = {
            onEnterCallback: (data) => {
                if (data.row) {
                    // handle enter
                    this._handleEnter(data.row);
                }
            },
            doubleClickCallback: (data) => {
                if (data.row) {
                    // handle dblclick
                    this._handleDblClick(data.row);
                }
            },
            longPressCallback: (data) => {
                if (data.row) {
                    // handle longpress
                    this._handleLongPress(data.row);
                }
            },
            updateActiveRowCallback: (data) => {
                this._updateActiveRow(data);
            },
            onRenderReadyCallback: (data, pan) => {
                this._updateSort();
            },
            onSortingCallback: (sortdata, pane, col) => this._onSort(sortdata, pane, col),
            onFilteringCallback: (filterdata, pane, col) => this._onFilter(filterdata, pane, col),
            onFilterInputCallback: (data, pane) => this._onFilterInput(data, pane),
            self: this
        };

        this.$.recipeslistgrid.setactiverow = true;

        // user should not be able to select multiple rows
        this.$.recipeslistgrid.disablemultiselect = true;
        this.$.recipeslistgrid.disableautodeactivate = true;
        this.$.recipeslistgrid.$.grid.style.setProperty('scroll-snap-type', 'none');

        if (this.isdesignmode) {
            this._renderPreview();
        }

        this._switchLang();
        this._applFontObjects();

        // Explicitly empty readOnly props to update datapoints 
        this._setSelectedrecipe("");
        this._setSelectedrecipefilepath("");
        this._setLoadedrecipe("");
        this._setLoadedrecipefilepath("");

        // all important data initialized
        this.initialized = true;
    }

    /**
     * Gets a row element and checks if it is a recipe file. 
     *
     * @param {*} rowData
     * @returns {*}  {boolean} Returns true if it is a recipe file, false if not.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _isRecipe(rowData){
        let parentPath = rowData.parent;

        // delete last character if trailing slash
        if(parentPath[parentPath.length-1] === '/'){
            parentPath = parentPath.slice(0, -1);
        }

        const parentName = this._getLastPathItem(parentPath);
        const fileName = this._getFileName(rowData.name);

        return (parentName.normalize() === fileName.normalize()) ? true : false;
    }


    /**
     * Opens the filter window/overlay
     *
     * @param {HTMLElement} data - the clicked header element
     * @param {String} pane - the name of the listview
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _onFilterInput(data, pane) {
        // The clicked header id
        const _elemId = data.getAttribute('id');
        //get the column name
        const _filterkey = _elemId.replace(/thead-/i, "");

        //Depending on the column name, open the correct window
        if (_filterkey === 'modified'){
            if((Object.entries(this.modifiedfilterwindow).length !== 0)){
                this._openFilterWindow_Modified(data);
            }else{
                // workaround datepicker
                this.$.filter_toDate.propReady = true;
                this.$.filter_fromDate.propReady = true;
                // workaround datepicker

                this._openOverlay('filter', data);
            }
        }
        else if(_filterkey === 'name'){
            if((Object.entries(this.namefilterwindow).length !== 0)){
                this._openFilterWindow_Name(data);
            }else{
                this._openOverlay('filter', data);
            }
        }
        else if((Object.entries(this.generalfilterwindow).length !== 0)) {this._openFilterWindow_General(data)}
        else{
            // If there is no correct window, open the overlay
            this._openOverlay('filter', data);
        }
    }


    /**
     * Updates the sort after component rendered.
     *
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _updateSort() {
        if(this.sortdata.key) {
            if( this.$.recipeslistgrid.sort &&  
                this.$.recipeslistgrid.sort.key === this.sortdata.key && 
                this.$.recipeslistgrid.sort.sorttype === this.sortdata.sorttype) {
                const elem = this.$.recipeslistgrid.$.grid.querySelector('th#thead-'+this.sortdata.key);
                if(elem){
                    elem.setAttribute('sorttype', this.sortdata.sorttype);
                }
                return;
            }
            if(this.$.recipeslistgrid.$ && this.$.recipeslistgrid.$.grid) {
                const elem = this.$.recipeslistgrid.$.grid.querySelector('th#thead-'+this.sortdata.key);
                if(elem) {
                    this.$.recipeslistgrid._setSort(elem, this.sortdata.sorttype);
                }
            }   
        }
    }


    /**
     * Updates the listview data while the columns and the parameters stay the same
     *
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _updateData() {
        if (this.initialized === true) {
            this.$.recipeslistgrid.configdata = Object.assign({}, this.$.recipeslistgrid.configdata, {
                data: this._getData(),
                cols: Object.assign([], this.$.recipeslistgrid.config.data.cols),
                params: Object.assign([], this.$.recipeslistgrid.config.data.params)
            });
        }
    }

    
    /**
     * Gets the data entries for the listview.
     *
     * @returns {Object} - the nodes and datanodes Arrays for the listview
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _getData() {
        let _entries = null;

        // get the nodes
        if(!this.isdesignmode && this.currentFileSystem !== undefined) {
            _entries = Array.from(this.currentFileSystem);
            _entries = _entries.filter( (a)=>{return this._doFilter(a)} ).sort( (a,b)=>{return this._doSort(a,b)} );
        }
        
        // return the processed data
        return {
            "nodes": Object.assign([], _entries),
            "datanodes": Object.assign([], _entries)
        }
    }


    /**
     * Sorting callback of the listview. Called instead of sort function of listview.
     *
     * @param {Object} sortdata- information about the sort
     * @param {String} pane - the name of the listview
     * @param {String} col - the name of the column
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _onSort(sortdata, pane, col) {
        if (sortdata) {
            this.sortdata = sortdata;
            this._setStorageItem(this.recipeHandlingStorage, this.id +'sortdata', JSON.stringify(sortdata));
            this._updateData();
        }
    }


    /**
     * A sort function that gets two dates and times, a sorttype parameter 
     * that determines if the sort is ascending or descending
     *
     * @param {String} sorttype - the sorttype, either asc or desc
     * @param {Number} dateA - the first date for the sort
     * @param {Number} timeA - the first time for the sort
     * @param {Number} dateB - the second date for the sort
     * @param {Number} timeB - the second time for the sort
     * @returns {-1|0|1} - same as Array.sort
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _sortAfterDateTime(sorttype, dateA, timeA, dateB, timeB) {
        //Sort for Date
        let tmpSortState = this._sortAfterValue(sorttype, dateA, dateB);
        if (tmpSortState !== 0) {
            return tmpSortState;
        }
        //If the dates are the same, sort for time
        return this._sortAfterValue(sorttype, timeA, timeB);
    }


    /**
     * Sort function, compares two values, sorttype determines if the sort is ascending or descending
     *
     * @param {String} sorttype - the sort type either asc or desc
     * @param {Number} valueA - the first compare value
     * @param {Number} valueB - the second compare value
     * @returns {-1|0|1} - same as Array.sort
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _sortAfterValue(sorttype, valueA, valueB) {
        if (valueA === valueB) {
            return 0;
        }
        const retVal = ((valueA > valueB && sorttype === 'asc' || valueA < valueB && sorttype === 'desc') ? 1 : -1);
        return retVal;
    }


    /**
     * The sorting function for the listview.
     *
     * @param {Object} a - the first entry that needs to be compared
     * @param {Object} b - the second entry that needs to be compared
     * @returns {-1|0|1} - Same as the js sort Function on an Array
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _doSort(a, b) {
        const _sortData = this.sortdata;
        
        // Do the default sorting
        if ((_sortData) && (_sortData.sorttype) && (_sortData.key) && (_sortData.sorttype !== 'none')) {
            // _sortData.key determines for which property is sorted
            if (_sortData.key === 'modified') {
                let aDateTime = {}, bDateTime = {}
                if(a.dateHiLast) {
                    aDateTime.date = a.dateHiLast;
                    aDateTime.time = a.timeHiLast;
                } else {
                    aDateTime.date = a.date;
                    aDateTime.time = a.time;
                }

                if(b.dateHiLast) {
                    bDateTime.date = b.dateHiLast;
                    bDateTime.time = b.timeHiLast;
                } else {
                    bDateTime.date = b.date;
                    bDateTime.time = b.time;
                }

                return this._sortAfterDateTime(_sortData.sorttype, this._stripDate(aDateTime.date), aDateTime.time, this._stripDate(bDateTime.date), bDateTime.time);

            } else if (_sortData.key === 'name') {
                const _a_userName = a.name ? a.name : "";
                const _b_userName = b.name ? b.name : "";
                return this._sortAfterValue(_sortData.sorttype, _a_userName, _b_userName);

            }else {
                return this._sortAfterValue(_sortData.sorttype, a[_sortData.key], b[_sortData.key]);
            }
        } else {
            // Do Default sorting
            // the 0xFFFFFFF0 is needed to cut off unnecessary information of the date

            let aDateTime = {}, bDateTime = {}
            if(a.dateHiLast) {
                aDateTime.date = a.dateHiLast;
                aDateTime.time = a.timeHiLast;
            } else {
                aDateTime.date = a.date;
                aDateTime.time = a.time;
            }

            if(b.dateHiLast) {
                bDateTime.date = b.dateHiLast;
                bDateTime.time = b.timeHiLast;
            } else {
                bDateTime.date = b.date;
                bDateTime.time = b.time;
            }

            return this._sortAfterDateTime('desc', this._stripDate(aDateTime.date), aDateTime.time, this._stripDate(bDateTime.date), bDateTime.time);
        }
    }


    /**
     * Filtering callback of the listview. Called instead of filter function of listview.
     *
     * @param {*} filterdata - the filterdata; column name and filtervalue
     * @param {*} pane - the name of the listview
     * @param {*} col - the name of the column
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _onFilter(filterdata, pane, col) {
        this.filter = filterdata;
        this._updateData();
    }


    /**
     * Returns if the given entry should be visible or not.
     *
     * @param {*} entry - an listview entry
     * @returns {Boolean} - same as Array.filter
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _doFilter(entry) {
        const _filterData = this._getFilterdata();

        let result = true;

        if(_filterData instanceof Array) {
            for(let el of _filterData) {
                result = result && this._filterOneResult(el, entry, entry);
            }
        } else {
            result = this._filterOneResult(_filterData, entry);
        }

        return result;
    }


    /**
     * Checks if entry matches filter.
     *
     * @param {*} _filterData
     * @param {*} entry
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _filterOneResult(_filterData, entry) {
        // If we dont have filterdata, the element isn't filtered by default
        if ((!_filterData) || (!_filterData.key) || (!_filterData.filtervalue)) {return true;}
        let result = true;
        // Handle the filter keys differently
        if (_filterData.key === 'modified') {
            const dateAsNumber = this.utilsApi.getSigDateAsNumber(this._getDateOfFullDate(entry.modified));
            const _entryDate = this._stripDate(dateAsNumber);

            if ((_filterData.filtervalue.date_from < _entryDate && _entryDate < _filterData.filtervalue.date_to) ||
                (_filterData.filtervalue.date_from === _entryDate) ||
                (_filterData.filtervalue.date_to === _entryDate)) {
                result = true;
            } else return false;

        // for every other key filter for match
        } else{
            const keys = Object.keys(entry);
        
            for(let key of keys){
                if(key === _filterData.key){
                    result = !!entry[key] && !!entry[key].match(_filterData.filtervalue);                  
                }
            }
        }
        
        return result;
    }


    /**
     * Returns the filterdata from the listview, as well als all filter inputs.
     *
     * @returns {Object|Array}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _getFilterdata() {
        let filterData =  this.filter;
        // In design mode we don't really need to do anything here
        if(this.isdesignmode) {
            return filterData;
        }
        // Make sure the filter data is an array
        if(!(filterData instanceof Array)) {
            if(!filterData) {
                filterData = [];
            } else {
                filterData = [filterData];
            }
        }

        if(this.filter_name) {
            filterData.push({
                key: 'name',
                filtervalue: this.filter_name
            })
        }

        let dateTimeFilterValue = {
            key: 'modified',
            filtervalue: {
                time_from: 0,
                time_to: 389757696,
                date_from: this.minMax.date.min,
                date_to: this.minMax.date.max
            }
        }

        if(this.filter_date_from) {
            dateTimeFilterValue.filtervalue.date_from = typeof this.filter_date_from === 'string' ? this.utilsApi.getSigDateAsNumber(this.filter_date_from) : this.filter_date_from;
        }
        if(this.filter_date_to) {
            dateTimeFilterValue.filtervalue.date_to = typeof this.filter_date_to === 'string' ? this.utilsApi.getSigDateAsNumber(this.filter_date_to) : this.filter_date_to;;
        }

        if(this.filter_date_from || this.filter_date_to) {
            filterData.push(dateTimeFilterValue);
        }

        return filterData;
    }


    /**
     * Cuts off the unnecessary information of the sigmatek date format
     *
     * @param {*} date
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _stripDate(date) {
        return date & 0xFFFFFFF0;
    }
    

    /**
     * opens the filter window for the datetime filter
     *
     * @param {*} data
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _openFilterWindow_Modified(data) {
        if (data) {

            const openWindowResult = await this.wmApi.openWindow(this.modifiedfilterwindow).then(result => {
                return true;
            }).catch(error => {
                this._log(error);
                return false;
            });  
    
            // window opened successfully
            if(openWindowResult){
    
                // this.modified_windowref.getDomRef().front();
                //After opening the window, we can now get a reference to it
                const _window = this.wmApi.getWindow(this.modifiedfilterwindow);

                //From the window reference we can get the outputs and fill them with data
                const _datepickerFrom = this._getWindowChildRef(_window, 'dateFrom');
                const _datepickerTo   = this._getWindowChildRef(_window, 'dateTo');
                const _buttonApply    = this._getWindowChildRef(_window, 'apply');

                const _minDate = moment(this.utilsApi.getSigDateAsString(this.minMax.date.min), 'DD.MM.YYYY');
                const _maxDate = moment(this.utilsApi.getSigDateAsString(this.minMax.date.max), 'DD.MM.YYYY');

                if (_datepickerFrom) {
                    _datepickerFrom.mindate =  _minDate.format(_datepickerFrom.dateformat);
                    _datepickerFrom.maxdate =  _maxDate.format(_datepickerFrom.dateformat);
                    if (_datepickerFrom.value === '') _datepickerFrom.value = _datepickerFrom.mindate;
                }
                if (_datepickerTo) {
                    _datepickerTo.mindate =  _minDate.format(_datepickerTo.dateformat);
                    _datepickerTo.maxdate =  _maxDate.format(_datepickerTo.dateformat);
                    if (_datepickerTo.value === '') _datepickerTo.value = _datepickerTo.maxdate;
                }

                // Add the event listener to the close button
                this.sigAddEventListener('tap', (evt) => {
                    this.windowBtnDebouncer = SigPolymer.debouncer.debounce(
                        this.windowBtnDebouncer,
                        SigPolymer.timeOut.after(10),
                        () => {
                            const _value = {};
                            _value.date_from = this._stripDate((_datepickerFrom) ? this.utilsApi.getSigDateAsNumber(moment(_datepickerFrom.value, _datepickerFrom.dateformat).format('DD.MM.YYYY')) : this.minMax.date.min);
                            _value.date_to   = this._stripDate((_datepickerTo) ? this.utilsApi.getSigDateAsNumber(moment(_datepickerTo.value, _datepickerTo.dateformat).format('DD.MM.YYYY')) : this.minMax.date.max);

                            this.$.recipeslistgrid.updateFilterValue(data, _value);
                            this.wmApi.closeWindow(this.modifiedfilterwindow);
                        }
                    );
                }, 'polymerEvent', _buttonApply);
            }
        }
    }


    /**
     * opens the filter window for the name filter
     *
     * @param {*} data
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _openFilterWindow_Name(data) {
        if (data) {
            // await this.name_windowref.open();
            const openWindowResult = await this.wmApi.openWindow(this.namefilterwindow).then(result => {
                return true;
            }).catch(error => {
                this._log(error);
                return false;
            });  

            if(openWindowResult){
                // this.name_windowref.getDomRef().front();
                //After opening the window, we can now get a reference to it
                const _window = this.wmApi.getWindow(this.namefilterwindow);
                // const _window = this.name_windowref;

                //From the window reference we can get the outputs and fill them with data
                const _inputName     = this._getWindowChildRef(_window, 'name');
                const _buttonApply          = this._getWindowChildRef(_window, 'apply');
                if (_inputName) {
                    _inputName.type      = 'text';
                    _inputName.titletext = data.innerHTML;
                }

                // Add the event listener to the close button
                this.sigAddEventListener('tap', (evt) => {
                    this.windowBtnDebouncer = SigPolymer.debouncer.debounce(
                        this.windowBtnDebouncer,
                        SigPolymer.timeOut.after(10),
                        () => {
                            const _value = (_inputName) ? _inputName.value : null;
                            
                            this.$.recipeslistgrid.updateFilterValue(data, _value);
                            this.wmApi.closeWindow(this.namefilterwindow);
                        }
                    );
                }, 'polymerEvent', _buttonApply);
            }
        }
    }


    /**
     * opens the filter window for user defined columns
     *
     * @param {*} data
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _openFilterWindow_General(data) {
        if (data) {
            // await this.name_windowref.open();
            const openWindowResult = await this.wmApi.openWindow(this.generalfilterwindow).then(result => {
                return true;
            }).catch(error => {
                this._log(error);
                return false;
            });  

            if(openWindowResult){
                // this.name_windowref.getDomRef().front();
                //After opening the window, we can now get a reference to it
                const _window = this.wmApi.getWindow(this.generalfilterwindow);
                // const _window = this.name_windowref;

                //From the window reference we can get the outputs and fill them with data
                const _inputName     = this._getWindowChildRef(_window, 'value');
                const _buttonApply          = this._getWindowChildRef(_window, 'apply');
                if (_inputName) {
                    _inputName.type      = 'text';
                    _inputName.titletext = data.innerHTML;
                }

                // Add the event listener to the close button
                this.sigAddEventListener('tap', (evt) => {
                    this.windowBtnDebouncer = SigPolymer.debouncer.debounce(
                        this.windowBtnDebouncer,
                        SigPolymer.timeOut.after(10),
                        () => {
                            const _value = (_inputName) ? _inputName.value : null;
                            
                            this.$.recipeslistgrid.updateFilterValue(data, _value);
                            this.wmApi.closeWindow(this.generalfilterwindow);
                        }
                    );
                }, 'polymerEvent', _buttonApply);
            }
        }
    }


    /**
     * Returns a child of a window
     *
     * @param {VM} _window - the VM of the window
     * @param {String} _child - the name of the child
     * @returns {HTMLElement} - the child element; null if the element couldnt be found
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _getWindowChildRef(_window, _child) {
        const windowChild = this._getWindowChild(_window, _child);
        return (windowChild && windowChild !== undefined) ? windowChild.getDomRef() : null;
    }


    /**
     * gets the child of a window
     *
     * @param {*} _window
     * @param {*} _child
     * @return {*} 
     */
    _getWindowChild(_window, _child) {
        return _window.getChildByName("sigRecipeHandling_" + _child);
    }


    /**
     * Returns an array containing the children of a window.
     *
     * @param {VM} _window - the VM of the window
     * @returns {Array} - the children of the window
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _getWindowChildren(_window) {
        const children = [];
        const windowChildren = _window.getChildren();

        // add name and domRef of each child to array
        windowChildren.forEach(child => {
            let tmpChildObject = {};

            tmpChildObject.name = child.getName();
            tmpChildObject.domRef = child.getDomRef();

            children.push(tmpChildObject);
        });

        return children;
    }


    /**
     * Opens the overlay
     *
     * @param {*} overlaytype determines if the overlay is a detail or filter overlay
     * @param {*} data Either the header that was clicked , or information to the row
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _openOverlay(overlaytype, data) {
        let   _rowData    = null;
        const _background = this.$.overlayBackground;
        const _wrapper    = this.$.overlayWrapper;

        _wrapper.setAttribute('overlaytype', overlaytype);

        if (overlaytype === 'filter') {
            // Filter overlay
            //The title is set to the header text
            this.$.overlayTitlebar.value = data.innerText;
            const _elemId = data.getAttribute('id');
            const _filterkey = _elemId.replace(/thead-/i, "");

            if (_filterkey === 'modified') {
                // Set the min date to the first recipe that was modified
                // Set the max date to the last recipe that was modified
                let minDate = moment(this.utilsApi.getSigDateAsString(this.minMax.date.min), 'DD.MM.YYYY');
                let maxDate = moment(this.utilsApi.getSigDateAsString(this.minMax.date.max), 'DD.MM.YYYY');
                this.$.filter_fromDate.mindate =  minDate.format(this.$.filter_fromDate.dateformat);
                this.$.filter_fromDate.maxdate =  maxDate.format(this.$.filter_fromDate.dateformat);
                this.$.filter_toDate.mindate =  minDate.format(this.$.filter_toDate.dateformat);
                this.$.filter_toDate.maxdate =  maxDate.format(this.$.filter_toDate.dateformat);
                if (this.$.filter_fromDate.value === '') this.$.filter_fromDate.value = this.$.filter_fromDate.mindate;
                if (this.$.filter_toDate.value === '') this.$.filter_toDate.value = this.$.filter_toDate.maxdate;
            } else{
                // No specific filter settings 
                this.filterSettings = Object.assign({}, this.filterSettings, {input_1: {
                    type: 'text',
                    limit_low: null,
                    limit_high: null,
                    title: data.innerHTML
                }});
            }
            _wrapper.setAttribute('filterkey', _filterkey);
            
            // Add an event listener to the close button
            this.sigAddEventListener('tap', (evt) => {
                this.overlayBtnDebouncer = SigPolymer.debouncer.debounce(
                    this.overlayBtnDebouncer,
                    SigPolymer.timeOut.after(10),
                    () => {
                        this._applyOverlayFilter(data, _filterkey);
                        this._closeOverlay();
                    }
                );
            }, 'polymerEvent', this.$.filter_submit);
        }

        this.sigSetTimeout (() => {
            const computedTitleBarStyles = getComputedStyle(this.$.overlayTitlebar);
            this.overlayCloseBtnSettings.color = computedTitleBarStyles.getPropertyValue('--theme-sig-control-titlebar-color');
            this.style.setProperty('--theme-sig-control-recipe-handling-overlay-header-text-color', this.overlayCloseBtnSettings.color);
            // Get the titlebar height
            this.overlayCloseBtnSettings.height = computedTitleBarStyles.getPropertyValue('height');
            this.style.setProperty('--theme-sig-control-recipe-handling-overlay-header-height', this.overlayCloseBtnSettings.height);
        });

        _background.classList.add("visible");
    }


    /**
     * Updats the filter of the listview from the filter overlay
     *
     * @param {*} data the header that was clicked to open the filter overlay/window
     * @param {*} filterkey the column name where the filter is applied
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _applyOverlayFilter(data, filterkey) {
        let value = null;

        if (filterkey === 'modified') {
            value = {};
            value.date_from = this._stripDate(this.utilsApi.getSigDateAsNumber(moment(this.filterSettings.datepicker.value_from, this.$.filter_fromDate.dateformat).format('DD.MM.YYYY')));
            value.date_to   = this._stripDate(this.utilsApi.getSigDateAsNumber(moment(this.filterSettings.datepicker.value_to, this.$.filter_toDate.dateformat).format('DD.MM.YYYY')));

        } else {
            value = this.filterSettings.input_1.value;
        }
        this.$.recipeslistgrid.updateFilterValue(data, value);
    }


    /**
     * Returns the date string of a full date.
     *
     * @param {*} fulldate
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _getDateOfFullDate(fulldate){
        const date = fulldate.split(' ')[0];
        return date;
    }


    /**
     * Returns the time string of a fulldate.
     *
     * @param {*} fulldate
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _getTimeOfFullDate(fulldate){
        const time = fulldate.split(' ')[1];
        return time;
    }


    /**
     * Hides the overlay.
     *
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _closeOverlay() {
        this.$.overlayBackground.classList.remove("visible");
    }


    /**
     * Implements the _actionSwitchPath action and switches to the given path.
     *
     * @private
     * @param {Object} data The data attribute object attached to the toolbar button.
     * @param {HTMLElement} button The domref of the toolbar button, which triggered the action.
     * @param {Event} evt The raw event fired by polymer.
     * @returns {Undefined} Undefined
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _actionSwitchPath(data, button, evt) {
        this.activepath = data.path;
    }


    /**
     * Returns a valid path, which can be used as a startpath.
     *
     * @private
     * @param {String} [path=''] The path to convert.
     * @returns {String} The converted valid path.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _makeValidStartPath(path = '') {
        let validpath = path;
        if (path !== '/') {
            validpath = path.replace(/\/+$/, "") + '/';
        }
        return validpath;
    }


    /**
     * This observer is executed when the start path is updated.
     *
     * @param {String} newval The new path.
     * @param {String} oldval The old path.
     * @private
     * @returns {Undefined} Undefined
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _toggleStartPath(newval, oldval) {
        if (newval) {
            // if startpath changes -> update active path
            let startpath = newval;
            if (startpath.endsWith('\\')) {
                startpath = this._convertPath(newval);
            }
            this.validStartPath = this._makeValidStartPath(startpath);
            this.activepath = this.validStartPath;
        }
    }

    /**
     * Observer for the filter properties
     * filter11,filter12
     * filter21, filter22
     * combines them and saves them in the filter1 and filter2 variables
     * @returns {Undefined} Undefined
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _updateFilter() {
        if ((this.filter11 || this.filter11 === 0) && (this.filter12 || this.filter12 === 0)) {
            this.filter1 = Math.pow(2, 32) * this.filter11 + this.filter12;
        }
        if ((this.filter21 || this.filter21 === 0) && (this.filter22 || this.filter22 === 0)) {
            this.filter2 = Math.pow(2, 32) * this.filter21 + this.filter22;
        }        
    }

    /**
     * This observer handles the path update if the path changes.
     *
     * @private
     * @param {String | undefined} newval  The new value of the observed property.
     * @param {String | undefined} oldval  The old value of the observed property.
     * @returns {Undefined} Undefined
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _updateActivePath(newval, oldval) {
        if(newval){
            this.oldPath = oldval;

            this.updatePathDebouncer = SigPolymer.debouncer.debounce(
                this.updatePathDebouncer,
                SigPolymer.timeOut.after(100),
                () => {
                    this._updatePath(newval, oldval);
                }   
            );
        }
    }


    /**
     * Updates the activerow property with the row data of the selected row.
     * 
     * @private
     * @param {Object} data The row data object.
     * @returns {Undefined} Undefined
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _updateActiveRow(data) {
        this.activerow.active = data;
        
        // update selected recipe
        if(!this.usesimplemode){
            this._setSelectedrecipe(data.name);
            //this._setSelectedrecipefilepath(data.path + data.name + this.recipefileextension);
        }else{
            this._setSelectedrecipe(data.name.replace(this.recipefileextension, ""));
            //this._setSelectedrecipefilepath(data.path);
        }
        this._setSelectedrecipefilepath(data.path);
    }


    /**
     * updates the columns of the listview
     * while the data and the parameters stay the same
     *
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _updateColumns() {
        if (this.initialized === true) {
            this.updateColumnsDebouncer = SigPolymer.debouncer.debounce(
                this.updateColumnsDebouncer,
                SigPolymer.timeOut.after(50),
                () => {
                    this.$.recipeslistgrid.config = Object.assign({}, this.$.recipeslistgrid.config, {
                        data: {
                            data: Object.assign([], this.$.recipeslistgrid.config.data.data),
                            cols: this._getColumnsConfig()
                        }
                    });
                }
            );
        }
    }


    /**
     * Returns the count of the levels of a given path.
     *
     * @param {String} [path='']  The path to check.
     * @param {String} [delimiter='/'] The level delimiter of the path.
     * @private
     * @returns {Number} The count of the levels
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _getPathLevelsCount(path = '', delimiter = '/') {
        return path.split(delimiter).length - 1;
    }


    /**
     * Creates a blob object from the given data and returns it.
     * This function can be overwritten if the filetype of the metafile is changed.
     *
     * @param {*} data
     * @returns {*} Blob.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _createBlobForMetafile(data){
        // convert data to JSON string
        const convertedData = JSON.stringify(data);

        // create blob for file
        const blob = new Blob([convertedData], {type : 'application/json'});

        return blob;
    }


    /**
     * Converts the data of the metafile to JSON, so that the control can handle it.
     * This function can be overwritten if the filetype of the metafile is changed.
     *
     * @param {*} data
     * @returns {*} The converted data as JSON.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _convertDataOfMetafile(data){
        // format text by deleting line breaks
        const dataAsFormattedText = data.replace(/\n/g, '');

        // convert data to JSON object
        const convertedData = JSON.parse(dataAsFormattedText);

        return convertedData;
    }

    
    /**
     * Writes the data to the listview metafile.
     *
     * @param {*} path 
     * @param {*} data
     * @returns {*} True if successful, false if not.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _writeMetafile(path, data){
        let actPath = null;

        // are parameter NOT valid
        if(path === null && data === null){
            return null;
        }

        // if Unix convert path
        if(path.startsWith('/')){
            actPath = this._convertPath(path);
        }else{
            actPath = path;
        }

        // get blob for metafile
        const blob = this._createBlobForMetafile(data);

        // write content of file
        const writeResult = await this.fmApi.writeFile(actPath, blob).catch((error) => {
            return error;
        });

        if(writeResult && writeResult !== null){
            return (!writeResult.status) ? true : false;
        }

        return false;
    }


    /**
     * Reads the metafile and returns it´s data.
     *
     * @param {String} path The path of the metafile.
     * @returns {*} Returns an json object of metafile. Null if object is empty.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _readMetafile(path){
        let metafile, actPath = null;
        let dataAsJson = {};

        // if Unix convert path
        if(path.startsWith('/')){
            actPath = this._convertPath(path);
        }else{
            actPath = path;
        }

        // read content of file
        metafile = await this.fmApi.fileContent(actPath).catch((error) => {
            return error;
        });

        if(!metafile.status){
            // convert content to text
            const dataAsText = await this._readAsTextAsync(metafile.data);
                
            try{
                dataAsJson = this._convertDataOfMetafile(dataAsText);
            } catch(e){
                // todo log error
                this._log("Error parsing data: " + e);
            }
        }

        return (Object.entries(dataAsJson).length !== 0) ? dataAsJson : null;
    }


    /**
     * Search for a file in the given path. 
     *
     * @param {*} path
     * @param {*} name
     * @param {*} [maxDepth=-1]
     * @param {boolean} [singleHit=false]
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _searchForFile(path, name, maxDepth = -1, singleHit = false){
        let actPath, result = null;

        // check parameter
        if(path === undefined || path === null || name === undefined || name === null){
            return false;
        }

        // if Unix convert path
        if(path.startsWith('/')){
            actPath = this._convertPath(path);
        }else{
            actPath = path;
        }

        // if it should be searched only for first result
        if(singleHit){
            result = await this._recSearchSingleFile(actPath, name, 0, maxDepth, singleHit);
        }else{
            result = await this._recSearchMultipleFiles(actPath, name, 0, maxDepth, singleHit);
        }

        return result;   
    }


    /**
     * Searches for a file with the given name, in the given path and returns the path. 
     * The maxDepth parameter specifies how deep the search should go. If it is negative, no limit is set. 
     * If it is 0, it is only searched in the given path.
     *
     * @param {string} path The path in which the search starts.
     * @param {string} name The name plus fileextension of the file.
     * @param {number} [currentDepth=0] Must always be 0 if called outside of the function.
     * @param {number} [maxDepth=-1] How deep the search should go.
     * @returns {*} The path of the file. Null is returned if file has not been found.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _recSearchSingleFile(path, name, currentDepth = 0, maxDepth = -1){
        let fileSystem = null;
        let result = null;

        fileSystem = await this.fmApi.readDir(path).catch((error) => {
            return error;
        });

        if(fileSystem.status !== undefined || fileSystem.status === ""){
            return null;
        }

        // if maxDepth is set and current depth exceeds maxDepth 
        // -> return nodeResult 
        if(maxDepth > -1 && currentDepth > maxDepth){
            return null;

        }else{
            await this.asyncForEach(fileSystem.parent.nodes, async (node) => {
                let type, parent, path, push = false;

                const attributes = this.fmApi.getFileAttributesAsArray(node.attribute);

                if (node.dpne !== '.') {
                    if (attributes.directory) {
                        if(node.dpne !== '..') {
                            type = 'folder';
                            const nextDepth = currentDepth + 1;
                            const nodeResult = await this._recSearchSingleFile(fileSystem.parent.dpne + node.dpne + '\\', name, nextDepth, maxDepth);

                            if(nodeResult !== undefined && nodeResult !== null){
                                // return nodeResult;
                                result = nodeResult;
                            }
                        }
                    } else {
                        type = 'file';

                        // found our file
                        if(node.dpne === name){
                            result = fileSystem.parent.dpne + node.dpne;
                        }
                    }
                }
            });

            return result;
        }
    }


    /**
     * Searches for a file with the given name, in the given path and returns an array of paths. 
     * The maxDepth parameter specifies how deep the search should go. If it is negative, no limit is set. 
     * If it is 0, it is only searched in the given path.
     *
     * @param {string} path The path in which the search starts.
     * @param {string} name The name plus fileextension of the file.
     * @param {number} [currentDepth=0] Must always be 0 if called outside of the function.
     * @param {number} [maxDepth=-1] How deep the search should go.
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _recSearchMultipleFiles(path, name, currentDepth = 0, maxDepth = -1){
        let fileSystem = null;
        let nodeResult = [];

        fileSystem = await this.fmApi.readDir(path).catch((error) => {
            return error;
        });

        if(fileSystem.status !== undefined || fileSystem.status === ""){
            return null;
        }

        // if maxDepth is set and current depth exceeds maxDepth 
        // -> return nodeResult 
        if(maxDepth > -1 && currentDepth > maxDepth){
            return nodeResult;

        }else{
            await this.asyncForEach(fileSystem.parent.nodes, async (node) => {
                let type, parent, path, push = false;
                let tmpNodeResult = {};

                const attributes = this.fmApi.getFileAttributesAsArray(node.attribute);

                if (node.dpne !== '.') {
                    if (attributes.directory) {
                        if(node.dpne !== '..') {
                            type = 'folder';
                            tmpNodeResult = await this._recSearchMultipleFiles(fileSystem.parent.dpne + node.dpne + '\\', name, currentDepth++, maxDepth);

                            if(tmpNodeResult !== undefined && tmpNodeResult !== null && tmpNodeResult.length !== 0){
                                nodeResult.push(tmpNodeResult);
                            }
                        }
                    } else {
                        type = 'file';

                        // found our file
                        if(node.dpne === name){
                            const result = fileSystem.parent.dpne + node.dpne;
                            nodeResult.push(result);
                        }
                    }
                }
            });

            return nodeResult;
        }
    }

    /**
     *
     *
     * @param {*} array
     * @param {function} callback
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async asyncForEach(array, callback){
        for (let index = 0; index < array.length; index++) {
            await callback(array[index], index, array);
        }
    }

    /**
     * Converts a blob object to text.
     *
     * @param {*} file
     * @returns {*} Text.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _readAsTextAsync(file) {
        return new Promise((resolve, reject) => {
            let reader = new FileReader();
        
            // if reading file was successful, event "onload" is fired
            // -> return resolve
            reader.onload = () => {
                resolve(reader.result);
            };
        
            // if reading file failed, event "onerror" is fired
            // -> return reject
            reader.onerror = reject;
        
            // trigger reading
            reader.readAsText(file);
        })
    }


    /**
    * This observer sets the restore row on the listview of a pane.
    *
    * @private
    * @param {Boolean} newval The new value of the observer polymer property.
    * @param {Boolean} oldval The old value of the observer polymer property.
    * @returns {Undefined} Undefined
    * @memberof LasalRuntimeSigControlRecipeHandling
    */
    _toggleRestoreRow(restorerow = false) {
        this.$.recipeslistgrid.restorerow = restorerow;
    }


    /**
     * Handles the overwriting of existing recipes.
     * Todo: optimize code
     *
     * @param {*} parentDir
     * @param {*} recipeName
     * @param {string} [headertext=""]
     * @param {number} [filter1=0]
     * @param {number} [filter2=0]
     * @param {*} progress
     * @param {*} begin
     * @param {*} end
     * @param {number} [base=10]
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _saveRecipeOverwriteHandling(parentDir, recipeName, headertext = "", filter1 = 0, filter2 = 0, progress, begin, end, base = 10){
        let recipeDir, recipeFilePath = null;

        // init result
        let result = {
            status: -255,
            statustext: "Unexpected error occured!",
            result: null
        };

        // check if parentDir is invalid
        if(parentDir === undefined || parentDir === null || parentDir[parentDir.length-1] !== "/"){
            result.status = -1;
            result.statustext = "Invalid parent directory!";
            return result;
        }

        // open custom ConfirmWindow
        // ########################################################################################################################
        // ########################################################################################################################
        if(Object.entries(this.confirmsavewnd).length !== 0 && this.confirmsave){

            const confirmResult = await this._openCustomConfirmWindow(this.confirmsavewnd).then(result => {
                return result;
            }).catch(error => {
                return error;
            });

            // check if result is valid so we can evaluate it
            if (confirmResult !== undefined && confirmResult !== null) {

                // check for error
                if(confirmResult instanceof Error){
                    // confirm failed
                    result.status = -7;
                    result.statustext = "Confirm failed! See result for specific error information.";
                    result.result = confirmResult;
                    return result;
                }

                // if cancel was pressed
                if(confirmResult === false){
                    // save was cancelled, not an error
                    result.status = 1;
                    result.statustext = "Save cancelled!";
                    return result;
                }

            // result of confirm invalid
            } else{
                // confirm failed
                result.status = -40;
                result.statustext = "Confirm result invalid!";
                return result;
            }

            // specify recipe dir path
            if(!this.usesimplemode){
                recipeDir = parentDir + recipeName + '/';
            }else{
                recipeDir = parentDir;
            }

            // specify recipe path
            recipeFilePath = this._convertPath(recipeDir + recipeName + this.recipefileextension);

            // save recipe
            const saveRecipeResult = await this.rmApi.save(recipeFilePath, headertext, filter1, filter2, progress, begin, end, this.utilsApi.getUID(), base).catch((error) => {
                return error;
            });
    
            // check if result is valid so we can evaluate it
            if(saveRecipeResult && saveRecipeResult !== null){
                // check for error
                if(saveRecipeResult.status === undefined || saveRecipeResult.status !== 0){
                    // save recipe failed
                    result.status = -16;
                    result.statustext = "Saving recipe failed!";
                    result.result = saveRecipeResult;
                    return result;
                }

                // nothing to refresh
                // // update data for listview refresh
                // if(!this.usesimplemode) await this._updateListviewDataOfRecipe(recipeDir, this.metafileforrecipe);

                // save metafile successful
                result.status = 0;
                return result;

            // result of save recipe invalid
            } else{
                // save recipe failed
                result.status = -20;
                result.statustext = "Save recipe result invalid!";
                return result;
            }

        // open system ConfirmWindow
        // ########################################################################################################################
        // ########################################################################################################################
        }else if(this.confirmsave){
            const systemConfirmResult = await this._showConfirm("Do you want to overwrite this recipe?", "Save recipe").then(result => {
                return true;
            }).catch(error => {
                return false;
            });
            this.windowopened = false;

            // if cancel was pressed
            if(systemConfirmResult === false){
                // save was cancelled, not an error
                result.status = 1;
                result.statustext = "Save cancelled!";
                return result;
            }

            // specify recipe dir path
            if(!this.usesimplemode){
                recipeDir = parentDir + recipeName + '/';
            }else{
                recipeDir = parentDir;
            }

            // specify recipe path
            recipeFilePath = this._convertPath(recipeDir + recipeName + this.recipefileextension);

            const saveRecipeResult = await this.rmApi.save(recipeFilePath, headertext, filter1, filter2, progress, begin, end, undefined, base).catch((error) => {
                return error;
            });

            // check if result is valid so we can evaluate it
            if(saveRecipeResult && saveRecipeResult !== null){
                // check for error
                if(saveRecipeResult.status === undefined || saveRecipeResult.status !== 0){
                    // save recipe failed
                    result.status = -16;
                    result.statustext = "Saving recipe failed!";
                    result.result = saveRecipeResult;
                    return result;
                }

                // nothing to refresh
                // // update data for listview refresh
                // if(!this.usesimplemode) await this._updateListviewDataOfRecipe(recipeDir, this.metafileforrecipe);

                // save metafile successful
                result.status = 0;
                return result;

            // result of save recipe invalid
            } else{
                // save recipe failed
                result.status = -20;
                result.statustext = "Save recipe result invalid!";
                return result;
            }
        
        // just overwrite recipe
        // ########################################################################################################################
        // ########################################################################################################################
        }else{
            // specify recipe dir path
            if(!this.usesimplemode){
                recipeDir = parentDir + recipeName + '/';
            }else{
                recipeDir = parentDir;
            }

            // specify recipe path
            recipeFilePath = this._convertPath(recipeDir + recipeName + this.recipefileextension);

            const saveRecipeResult = await this.rmApi.save(recipeFilePath, headertext, filter1, filter2, progress, begin, end, undefined, base).catch((error) => {
                return error;
            });

            // check if result is valid so we can evaluate it
            if(saveRecipeResult && saveRecipeResult !== null){
                // check for error
                if(saveRecipeResult.status === undefined || saveRecipeResult.status !== 0){
                    // save recipe failed
                    result.status = -16;
                    result.statustext = "Saving recipe failed!";
                    result.result = saveRecipeResult;
                    return result;
                }

                // update data for listview refresh
                if(!this.usesimplemode) await this._updateListviewDataOfRecipe(recipeDir, this.metafileforrecipe);

                // save metafile successful
                result.status = 0;
                return result;

            // result of save recipe invalid
            } else{
                // save recipe failed
                result.status = -20;
                result.statustext = "Save recipe result invalid!";
                return result;
            }

        }
    } 

    /**
     * Handles the creation of recipes.
     * Todo: optimize code
     *
     * @param {*} parentDir
     * @param {*} recipeName
     * @param {*} metadata
     * @param {string} [headertext=""]
     * @param {number} [filter1=0]
     * @param {number} [filter2=0]
     * @param {*} progress
     * @param {*} begin
     * @param {*} end
     * @param {number} [base=10]
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _saveRecipeCreationHandling(parentDir, recipeName, metadata, headertext = "", filter1 = 0, filter2 = 0, progress, begin, end, base = 10){
        let recipeDir, recipeFilePath = null;

        // init result
        let result = {
            status: -255,
            statustext: "Unexpected error occured!",
            result: null
        };

        // check if parentDir is invalid
        if(parentDir === undefined || parentDir === null || parentDir[parentDir.length-1] !== "/"){
            result.status = -1;
            result.statustext = "Invalid parent directory!";
            return result;
        }

        // open custom PromptWindow
        // ########################################################################################################################
        // ########################################################################################################################
        if(Object.entries(this.allowcreationwnd).length !== 0){

            const promptResult = await this._openCustomCreationWindow(this.allowcreationwnd, recipeName, metadata).then(result => {
                return result;
            }).catch(error => {
                return error;
            });

            // check if result is valid so we can evaluate it
            if (promptResult && promptResult !== null) {

                // check for error
                if(promptResult instanceof Error){
                    // prompt failed
                    result.status = -5;
                    result.statustext = "Prompt failed! See result for specific error information.";
                    result.result = promptResult;
                    return result;
                }

                // if cancel was pressed
                if(!promptResult.result){
                    // save was cancelled, not an error
                    result.status = 1;
                    result.statustext = "Save cancelled!";
                    return result;
                }

                // if ok was pressed
                // check if name is valid for a recipe
                if(!this._isRecipeNameValid(promptResult.data.name)){
                    // recipe name invalid
                    result.status = -6;
                    result.statustext = "Invalid recipe name!";
                    return result;
                }

            // result of prompt invalid
            } else{
                // prompt failed
                result.status = -10;
                result.statustext = "Prompt result invalid!";
                return result;
            }
            
            // specify recipe dir path
            if(!this.usesimplemode){
                recipeDir = parentDir + promptResult.data.name + '/';
            }else{
                recipeDir = parentDir;
            }

            // specify recipe path
            recipeFilePath = this._convertPath(recipeDir + promptResult.data.name + this.recipefileextension);

            // check if name is available
            if(await this._isRecipeNameAvailable(parentDir, promptResult.data.name)){

                // create new recipe structure
                const createResult = await this._createNewRecipe(promptResult.data.name, parentDir, this.usesimplemode); 

                // if not successful
                if(!createResult){
                    // create recipe structure failed
                    result.status = -15;
                    result.statustext = "Creating recipe structure failed!";
                    return result;
                }

                // -> read meta data 
                if(!this.usesimplemode){
                    const metafilePath = this._convertPath(recipeDir + this.metafileforrecipe);
                    const readMetafileResult = await this._readMetafile(metafilePath);
    
                    const metadata = this._createObject_metadata(recipeDir + this.metafileforrecipe, readMetafileResult);
    
                    // update storage 
                    this._updateMetaStorageOfControl(this.recipeHandlingStorage, parentDir, recipeDir + this.metafileforrecipe, metadata);
                }

                // save recipe
                const saveRecipeResult = await this.rmApi.save(recipeFilePath, headertext, filter1, filter2, progress, begin, end, this.utilsApi.getUID(), base).catch((error) => {
                    return error;
                });

                // check if result is valid so we can evaluate it
                if(saveRecipeResult && saveRecipeResult !== null){
                    // check for error
                    if(saveRecipeResult.status === undefined || saveRecipeResult.status !== 0){
                        // save recipe failed
                        result.status = -16;
                        result.statustext = "Saving recipe failed!";
                        result.result = saveRecipeResult;
                        return result;
                    }

                    // -> dont perform equalCheck because metafile must be saved if values not empty
                    // -> check if metavalues are empty 
                    //   -> if so dont save metadata -> finished
                    if(promptResult.data.metavalues.length === 0 || this.usesimplemode){
                        // update active recipe and path
                        this.activerecipe = promptResult.data.name;
                        this.activerecipefilepath = this._convertPath(recipeDir);
                        // metavalues are empty or simple mode is active
                        result.status = 0;
                        return result;
                    }

                    // save meta data
                    const metafilePath = this._convertPath(recipeDir + this.metafileforrecipe);
                    const metaValues = Object.assign({}, ...promptResult.data.metavalues);
                    const writeMetafileResult = await this._writeMetafile(metafilePath, metaValues);

                    // check if successful
                    if(writeMetafileResult){
                        // update data for listview refresh
                        await this._updateListviewDataOfRecipe(recipeDir, this.metafileforrecipe);

                        // build metadata
                        const metadata = {
                            path: recipeDir + this.metafileforrecipe,
                            metadata: metaValues
                        }

                        // update storage 
                        this._updateMetaStorageOfControl(this.recipeHandlingStorage, parentDir, recipeDir + this.metafileforrecipe, metadata);

                        // update active recipe and path
                        this.activerecipe = promptResult.data.name;
                        this.activerecipefilepath = this._convertPath(recipeDir);

                        // save metafile successful
                        result.status = 0;
                        return result;

                    // if not successful
                    }else{
                        // save metafile failed
                        result.status = -17;
                        result.statustext = "Saving metafile failed!";
                        return result;
                    }

                // result of save recipe invalid
                } else{
                    // save recipe failed
                    result.status = -20;
                    result.statustext = "Save recipe result invalid!";
                    return result;
                }

            // recipe already exists -> should confirm overwrite?
            } else if(this.confirmsave){
                const confirmResult = await this._saveRecipeOverwriteHandling(parentDir, promptResult.data.name, headertext = "", filter1 = this.filter1, filter2 = this.filter2, progress, begin, end, base = 10);

                // if save recipe was successful 
                if(confirmResult.status === 0){
                    // if simplemode or no metafile
                    // -> we are done here
                    if(this.usesimplemode || this.metafileforrecipe === ""){
                        // update active recipe and path
                        this.activerecipe = promptResult.data.name;
                        this.activerecipefilepath = this._convertPath(recipeDir);

                        // save metafile successful
                        result.status = 0;
                        return result;
                    }

                    // if not simplemode 
                    // -> need to check if we have to save metadata too? 
                    const newMetadata = Object.assign({}, ...promptResult.data.metavalues);
                    const metadataEqual = this._shallowEqualCheck(newMetadata, metadata);

                    // -> check if metavalues are empty or equal to previous metadata
                    //  -> dont save -> finished
                    if(promptResult.data.metavalues.length === 0 || metadataEqual){
                        // metavalues are empty or have not changed
                        result.status = 0;
                        return result;
                    }

                    // -> save meta data
                    const metafilePath = this._convertPath(recipeDir + this.metafileforrecipe);
                    const metaValues = Object.assign({}, ...promptResult.data.metavalues);
                    const writeMetafileResult = await this._writeMetafile(metafilePath, metaValues);

                    // check if successful
                    if(writeMetafileResult){
                        // update data for listview refresh
                        await this._updateListviewDataOfRecipe(recipeDir, this.metafileforrecipe);

                        // build metadata
                        const metadata = {
                            path: recipeDir + this.metafileforrecipe,
                            metadata: metaValues
                        }

                        // update storage 
                        this._updateMetaStorageOfControl(this.recipeHandlingStorage, parentDir, recipeDir + this.metafileforrecipe, metadata);

                        // update active recipe and path
                        this.activerecipe = promptResult.data.name;
                        this.activerecipefilepath = this._convertPath(recipeDir);

                        // save metafile successful
                        result.status = 0;
                        return result;

                    // if not successful
                    }else{
                        // save metafile failed
                        result.status = -17;
                        result.statustext = "Saving metafile failed!";
                        return result;
                    }

                // if cancelled with reason...
                }else if(confirmResult.status > 0){
                    // result.status = confirmResult.status;
                    // result.result = confirmResult.result;
                    result = confirmResult;
                    return result;
                
                // error occured in confirm overwrite handling
                }else{
                    // return error information of function
                    // result.status = confirmResult.status;
                    // result.statustext = confirmResult.statustext;
                    // result.result = confirmResult.result;
                    result = confirmResult;
                    return result;
                }

            // just overwrite recipe and metafile
            }else{
                // save recipe
                const saveRecipeResult = await this.rmApi.save(recipeFilePath, headertext, filter1, filter2, progress, begin, end, this.utilsApi.getUID(), base).catch((error) => {
                    return error;
                });

                // check if result is valid so we can evaluate it
                if(saveRecipeResult && saveRecipeResult !== null){
                    // check for error
                    if(saveRecipeResult.status === undefined || saveRecipeResult.status !== 0){
                        // save recipe failed
                        result.status = -16;
                        result.statustext = "Saving recipe failed!";
                        result.result = saveRecipeResult;
                        return result;
                    }

                    // if simplemode or no metafile
                    // -> we are done here
                    if(this.usesimplemode || this.metafileforrecipe === ""){
                        // update active recipe and path
                        this.activerecipe = promptResult.data.name;
                        this.activerecipefilepath = this._convertPath(recipeDir);
                        // save metafile successful
                        result.status = 0;
                        return result;
                    }

                    // if not simplemode 
                    // -> need to check if we have to save metadata too? 
                    const newMetadata = Object.assign({}, ...promptResult.data.metavalues);
                    const metadataEqual = this._shallowEqualCheck(newMetadata, metadata);

                    // -> check if metavalues are empty or equal to previous metadata
                    //  -> dont save -> finished
                    if(promptResult.data.metavalues.length === 0 || metadataEqual){
                        // metavalues are empty or have not changed
                        result.status = 0;
                        return result;
                    }

                    // -> save meta data
                    const metafilePath = this._convertPath(recipeDir + this.metafileforrecipe);
                    const metaValues = Object.assign({}, ...promptResult.data.metavalues);
                    const writeMetafileResult = await this._writeMetafile(metafilePath, metaValues);

                    // check if successful
                    if(writeMetafileResult){
                        // update data for listview refresh
                        await this._updateListviewDataOfRecipe(recipeDir, this.metafileforrecipe);

                        // build metadata
                        const metadata = {
                            path: recipeDir + this.metafileforrecipe,
                            metadata: metaValues
                        }

                        // update storage 
                        this._updateMetaStorageOfControl(this.recipeHandlingStorage, parentDir, recipeDir + this.metafileforrecipe, metadata);

                        // update active recipe and path
                        this.activerecipe = promptResult.data.name;
                        this.activerecipefilepath = this._convertPath(recipeDir);
                        
                        // save metafile successful
                        result.status = 0;
                        return result;

                    // if not successful
                    }else{
                        // save metafile failed
                        result.status = -17;
                        result.statustext = "Saving metafile failed!";
                        return result;
                    }

                // result of save recipe invalid
                } else{
                    // save recipe failed
                    result.status = -20;
                    result.statustext = "Save recipe result invalid!";
                    return result;
                }
            }

        // open system PromptWindow
        // ########################################################################################################################
        // ########################################################################################################################
        }else{
            const systemPromptResult = await this._showPrompt("Enter name for recipe.", "Save recipe", recipeName).then(result => {
                return result;
            }).catch(error => {
                return false;
            });
            this.windowopened = false;

            // if cancel was pressed
            if(systemPromptResult === false){
                // save was cancelled, not an error
                result.status = 1;
                result.statustext = "Save cancelled!";
                return result;
            }

            // check if name is valid for a recipe
            if(!this._isRecipeNameValid(systemPromptResult)){
                // recipe name invalid
                result.status = -6;
                result.statustext = "Invalid recipe name!";
                return result;
            }

            // assign valid recipe name
            const newRecipeName = systemPromptResult;

            // specify recipe dir path
            if(!this.usesimplemode){
                recipeDir = parentDir + newRecipeName + '/';
            }else{
                recipeDir = parentDir;
            }

            // specify recipe path
            recipeFilePath = this._convertPath(recipeDir + newRecipeName + this.recipefileextension);

            // check if name is available
            if(await this._isRecipeNameAvailable(parentDir, newRecipeName)){

                // create new recipe structure
                const createResult = await this._createNewRecipe(newRecipeName, parentDir, this.usesimplemode); 

                // if not successful
                if(!createResult){
                    // create recipe structure failed
                    result.status = -15;
                    result.statustext = "Creating recipe structure failed!";
                    return result;
                }

                // -> read meta data 
                if(!this.usesimplemode){
                    const metafilePath = this._convertPath(recipeDir + this.metafileforrecipe);
                    const readMetafileResult = await this._readMetafile(metafilePath);
    
                    const metadata = this._createObject_metadata(recipeDir + this.metafileforrecipe, readMetafileResult);
    
                    // update storage 
                    this._updateMetaStorageOfControl(this.recipeHandlingStorage, parentDir, recipeDir + this.metafileforrecipe, metadata);
                }

                // save recipe
                const saveRecipeResult = await this.rmApi.save(recipeFilePath, headertext, filter1, filter2, progress, begin, end, this.utilsApi.getUID(), base).catch((error) => {
                    return error;
                });

                // check if result is valid so we can evaluate it
                if(saveRecipeResult && saveRecipeResult !== null){
                    // check for error
                    if(saveRecipeResult.status === undefined || saveRecipeResult.status !== 0){
                        // save recipe failed
                        result.status = -16;
                        result.statustext = "Saving recipe failed!";
                        result.result = saveRecipeResult;
                        return result;
                    }

                    // Update active recipe and path
                    this.activerecipe = newRecipeName;
                    this.activerecipefilepath = this._convertPath(recipeDir);

                    // save recipe successful
                    result.status = 0;
                    return result;

                // result of save recipe invalid
                } else{
                    // save recipe failed
                    result.status = -20;
                    result.statustext = "Save recipe result invalid!";
                    return result;
                }

            // recipe already exists -> should confirm overwrite?
            } else if(this.confirmsave){
                const confirmResult = await this._saveRecipeOverwriteHandling(parentDir, newRecipeName, headertext = "", filter1 = this.filter1, filter2 = this.filter2, progress, begin, end, base = 10);

                // if save recipe was successful 
                if(confirmResult.status === 0){
                    // Update active recipe and path
                    this.activerecipe = newRecipeName;
                    this.activerecipefilepath = this._convertPath(recipeDir);

                    // save recipe successful
                    result.status = 0;
                    return result;

                // if cancelled with reason...
                }else if(confirmResult.status > 0){
                    // result.status = confirmResult.status;
                    // result.result = confirmResult.result;
                    result = confirmResult;
                    return result;
                
                // error occured in confirm overwrite handling
                }else{
                    // return error information of function
                    // result.status = confirmResult.status;
                    // result.statustext = confirmResult.statustext;
                    // result.result = confirmResult.result;
                    result = confirmResult;
                    return result;
                }

            // just overwrite recipe and metafile
            }else{
                // save recipe
                const saveRecipeResult = await this.rmApi.save(recipeFilePath, headertext, filter1, filter2, progress, begin, end, this.utilsApi.getUID(), base).catch((error) => {
                    return error;
                });

                // check if result is valid so we can evaluate it
                if(saveRecipeResult && saveRecipeResult !== null){
                    // check for error
                    if(saveRecipeResult.status === undefined || saveRecipeResult.status !== 0){
                        // save recipe failed
                        result.status = -16;
                        result.statustext = "Saving recipe failed!";
                        result.result = saveRecipeResult;
                        return result;
                    }

                    // Update active recipe and path
                    this.activerecipe = newRecipeName;
                    this.activerecipefilepath = this._convertPath(recipeDir);

                    // save metafile successful
                    result.status = 0;
                    return result;

                // result of save recipe invalid
                } else{
                    // save recipe failed
                    result.status = -20;
                    result.statustext = "Save recipe result invalid!";
                    return result;
                }
            }
        }
    } 


    /**
     * Saves a recipe.
     *
     * @param {*} parentDir
     * @param {string} [headertext=""]
     * @param {number} [filter1=0]
     * @param {number} [filter2=0]
     * @param {*} progress
     * @param {*} begin
     * @param {*} end
     * @param {number} [base=10]
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _saveRecipe(parentDir, activeRow, headertext = "", filter1 = 0, filter2 = 0, progress, begin, end, base = 10){
        // get active row
        const row = activeRow;

        // init result
        let result = {
            status: -255,
            statustext: "Unexpected error occured!",
            result: null
        };

        // user can create new recipes
        // -> user input is needed in window
        // -> open prompt window
        if(this.allowcreation){
            let recipeName = "";
            let rowData, metaData = null;

            if(row !== null){
                rowData = JSON.parse(row.dataset.data);

                recipeName = rowData.name;

                // remove file extension
                if(this.usesimplemode){
                    recipeName = recipeName.replace(/\.[^/.]+$/, "");
                }

                // get metadata for default input values
                metaData = this._getMetaDataOfRow(rowData, this._getItemConfig());
            }

            const creationResult = await this._saveRecipeCreationHandling(parentDir, recipeName, metaData, headertext = "", filter1 = this.filter1, filter2 = this.filter2, progress, begin, end, base = 10);

            if(creationResult && creationResult !== null){
                result = creationResult; 
                return result;

            // result invalid
            } else{
                // save recipe failed
                result.status = -2;
                result.statustext = "Result of _saveRecipeCreationHandling() invalid! Save failed!";
                result.result = creationResult;
                return result;
            }

        // user can only overwrite existing recipes
        } else{
            if(row === null){
                result.status = -3;
                return result;
            }

            const rowData = JSON.parse(row.dataset.data);
            const recipeName = rowData.name;

            const confirmResult = await this._saveRecipeOverwriteHandling(parentDir, recipeName, headertext = "", filter1 = this.filter1, filter2 = this.filter2, progress, begin, end, base = 10);

            if(confirmResult && confirmResult !== null){
                // update active recipe
                if(!this.usesimplemode){
                    this.activerecipe = rowData.name;
                    this.activerecipefilepath = this._convertPath(rowData.path);
                }else{
                    this.activerecipe = rowData.name.replace(this.recipefileextension, "");
                    this.activerecipefilepath = this._convertPath(rowData.parent);
                }
                
                result = confirmResult; 
                return result;

            // result invalid
            } else{
                // save recipe failed
                result.status = -4;
                result.statustext = "Result of _saveRecipeOverwriteHandling() invalid! Save failed!";
                result.result = creationResult;
                return result;
            }
        }
    }


    /**
     * Loads a recipe 
     *
     * @param {number} [filter1=0]
     * @param {number} [filter2=0]
     * @param {number} [usercrc=0]
     * @param {*} progress
     * @param {*} begin
     * @param {*} end
     * @param {number} [base=10]
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _loadRecipe(activeRow, filter1 = 0, filter2 = 0, usercrc = 0, progress, begin, end, base = 10){
        let recipeFilePath = null;

        // get active row
        const row = activeRow;

        // init result
        let result = {
            status: -255,
            statustext: "Unexpected error occured!",
            result: null
        };

        if(row === null){
            result.status = -1;
            return result;
        }

        const rowData = JSON.parse(activeRow.dataset.data);

        // specify recipe path
        if(!this.usesimplemode){
            recipeFilePath = this._convertPath(rowData.path + rowData.name + this.recipefileextension);
        }else{
            recipeFilePath = this._convertPath(rowData.parent + rowData.name);
        }

        // need confirmation of user 
        if(this.confirmload === true){

            // open custom ConfirmWindow
            // ########################################################################################################################
            // ########################################################################################################################
            if(Object.entries(this.confirmloadwnd).length !== 0){

                const confirmResult = await this._openCustomConfirmWindow(this.confirmloadwnd).then(result => {
                    return result;
                }).catch(error => {
                    return error;
                });

                // check if result is valid so we can evaluate it
                if (confirmResult && confirmResult !== null) {

                    // check for error
                    if(confirmResult instanceof Error){
                        // confirm failed
                        result.status = -2;
                        result.statustext = "Confirm failed! See result for specific error information.";
                        result.result = confirmResult;
                        return result;
                    }

                    // if cancel was pressed
                    if(confirmResult === false){
                        // load was cancelled, not an error
                        result.status = 1;
                        result.statustext = "Load cancelled!";
                        return result;
                    }

                // result of confirm invalid
                } else {
                    // confirm failed
                    result.status = -3;
                    result.statustext = "Confirm result invalid!";
                    return result;
                }

                // checks the CRC in the file with the user CRC and returns the right CRC to use for loading
                const userCRCForLoading = await this._getCRCToLoadRecipe(recipeFilePath, usercrc);

                // load recipe
                let loadRecipeResult = await this.rmApi.load(recipeFilePath, filter1, filter2, userCRCForLoading, progress, begin, end, this.utilsApi.getUID(), base).catch((error) => {
                    return error;
                });

                // check if result is valid so we can evaluate it
                if(loadRecipeResult && loadRecipeResult !== null){
                    // check for error
                    if(loadRecipeResult.status === undefined || loadRecipeResult.status !== 0){
                        // load recipe failed
                        result.status = -4;
                        result.statustext = "Loading recipe failed!";
                        result.result = loadRecipeResult;
                        return result;
                    }

                    // update loaded and active recipe
                    if(!this.usesimplemode){
                        this._setLoadedrecipe(rowData.name);
                        this._setLoadedrecipefilepath(this._convertPath(rowData.path));

                        this.activerecipe = rowData.name;
                        this.activerecipefilepath = this._convertPath(rowData.path);
                    }else{
                        this._setLoadedrecipe(rowData.name.replace(this.recipefileextension, ""));
                        this._setLoadedrecipefilepath(this._convertPath(rowData.parent));

                        this.activerecipe = rowData.name.replace(this.recipefileextension, "");
                        this.activerecipefilepath = this._convertPath(rowData.parent);
                    }
                    //this._setLoadedrecipefilepath(this._convertPath(recipeFilePath));

                    // load recipe successful
                    result.status = 0;
                    return result;

                // result of load recipe invalid
                } else{
                    // update loaded recipe
                    this._setLoadedrecipe("");
                    this._setLoadedrecipefilepath("");

                    // load recipe failed
                    result.status = -5;
                    result.statustext = "Load recipe result invalid!";
                    return result;
                }

            // open system ConfirmWindow
            // ########################################################################################################################
            // ########################################################################################################################
            }else{

                const systemConfirmResult = await this._showConfirm("Do you want to load this recipe?", "Load recipe").then(result => {
                    return true;
                }).catch(error => {
                    this._log(error);
                    return false;
                });
                this.windowopened = false;

                // if cancel was pressed
                if(systemConfirmResult === false){
                    // load was cancelled, not an error
                    result.status = 1;
                    result.statustext = "Load cancelled!";
                    return result;
                }

                // checks the CRC in the file with the user CRC and returns the right CRC to use for loading
                const userCRCForLoading = await this._getCRCToLoadRecipe(recipeFilePath, usercrc);

                // load recipe
                let loadRecipeResult = await this.rmApi.load(recipeFilePath, filter1, filter2, userCRCForLoading, progress, begin, end, this.utilsApi.getUID(), base).catch((error) => {
                    return error;
                });

                // check if result is valid so we can evaluate it
                if(loadRecipeResult && loadRecipeResult !== null){
                    // check for error
                    if(loadRecipeResult.status === undefined || loadRecipeResult.status !== 0){
                        // load recipe failed
                        result.status = -4;
                        result.statustext = "Loading recipe failed!";
                        result.result = loadRecipeResult;
                        return result;
                    }

                    // update loaded and active recipe
                    if(!this.usesimplemode){
                        this._setLoadedrecipe(rowData.name);
                        this._setLoadedrecipefilepath(this._convertPath(rowData.path));

                        this.activerecipe = rowData.name;
                        this.activerecipefilepath = this._convertPath(rowData.path);
                    }else{
                        this._setLoadedrecipe(rowData.name.replace(this.recipefileextension, ""));
                        this._setLoadedrecipefilepath(this._convertPath(rowData.parent));

                        this.activerecipe = rowData.name.replace(this.recipefileextension, "");
                        this.activerecipefilepath = this._convertPath(rowData.parent);
                    }
                    //this._setLoadedrecipefilepath(this._convertPath(recipeFilePath));

                    // load recipe successful
                    result.status = 0;
                    return result;

                // result of load recipe invalid
                } else {
                    // update loaded recipe
                    this._setLoadedrecipe("");
                    this._setLoadedrecipefilepath("");

                    // load recipe failed
                    result.status = -5;
                    result.statustext = "Load recipe result invalid!";
                    return result;
                }
            }

        // load without confirmation
        } else {
            // checks the CRC in the file with the user CRC and returns the right CRC to use for loading
            const userCRCForLoading = await this._getCRCToLoadRecipe(recipeFilePath, usercrc);

            // load recipe
            let loadRecipeResult = await this.rmApi.load(recipeFilePath, filter1, filter2, userCRCForLoading, progress, begin, end, this.utilsApi.getUID(), base).catch((error) => {
                return error;
            });
            
            // check if result is valid so we can evaluate it
            if(loadRecipeResult && loadRecipeResult !== null){
                // check for error
                if(loadRecipeResult.status === undefined || loadRecipeResult.status !== 0){
                    // load recipe failed
                    result.status = -4;
                    result.statustext = "Loading recipe failed!";
                    result.result = loadRecipeResult;
                    return result;
                }

                // update loaded and active recipe
                if (!this.usesimplemode){
                    this._setLoadedrecipe(rowData.name);
                    this._setLoadedrecipefilepath(this._convertPath(rowData.path));

                    this.activerecipe = rowData.name;
                    this.activerecipefilepath = this._convertPath(rowData.path);
                } else {
                    this._setLoadedrecipe(rowData.name.replace(this.recipefileextension, ""));
                    this._setLoadedrecipefilepath(this._convertPath(rowData.parent));

                    this.activerecipe = rowData.name.replace(this.recipefileextension, "");
                    this.activerecipefilepath = this._convertPath(rowData.parent);
                }
                //this._setLoadedrecipefilepath(this._convertPath(recipeFilePath));

                // load recipe successful
                result.status = 0;
                return result;

            // result of load recipe invalid
            } else {
                // update loaded recipe
                this._setLoadedrecipe("");
                this._setLoadedrecipefilepath("");

                // load recipe failed
                result.status = -5;
                result.statustext = "Load recipe result invalid!";
                return result;
            }
        }
    }

    /**
     * Deletes a recipe 
     *
     * @param {*} activeRow
     * @param {*} progress
     * @param {*} begin
     * @param {*} end
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _deleteRecipe(activeRow, progress, begin, end){
        let recipeFilePath = null;

        // get active row
        const row = activeRow;

        // init result
        let result = {
            status: -255,
            statustext: "Unexpected error occured!",
            result: null
        };

        if(row === null){
            result.status = -1;
            return result;
        }

        const rowData = JSON.parse(activeRow.dataset.data);
        // specify recipe path
        if(this.usesimplemode){
            recipeFilePath = this._convertPath(rowData.path);
        }else{
            recipeFilePath = this._convertPath(rowData.parent + rowData.name);
        }
        // need confirmation of user 
        if(this.confirmdelete === true){

            // open custom ConfirmWindow
            // ########################################################################################################################
            // ########################################################################################################################
            if(Object.entries(this.confirmdeletewnd).length !== 0){

                const confirmResult = await this._openCustomConfirmWindow(this.confirmdeletewnd).then(result => {
                    return result;
                }).catch(error => {
                    return error;
                });

                // check if result is valid so we can evaluate it
                if (confirmResult && confirmResult !== null) {

                    // check for error
                    if(confirmResult instanceof Error){
                        // confirm failed
                        result.status = -2;
                        result.statustext = "Confirm failed! See result for specific error information.";
                        result.result = confirmResult;
                        return result;
                    }

                    // if cancel was pressed
                    if(confirmResult === false){
                        // Delete was cancelled, not an error
                        result.status = 1;
                        result.statustext = "Delete cancelled!";
                        return result;
                    }

                // result of confirm invalid
                } else{
                    // confirm failed
                    result.status = -3;
                    result.statustext = "Confirm result invalid!";
                    return result;
                }

                // delete recipe
                const deleteRecipeResult = await this.fmApi.remove(recipeFilePath, progress, begin, end, this.utilsApi.getUID()).catch((error) => {
                    return error;
                });
                // check if result is valid so we can evaluate it
                if(deleteRecipeResult && deleteRecipeResult !== null){
                    // Reset the active recipe file in case the deleted file is the active file
                    if(!this.usesimplemode){
                        // Check if the renamed recipe is the active one
                        if (this.activerecipe === rowData.name) {
                            // Update active recipe and path
                            this.activerecipe = '';
                            this.activerecipefilepath = '';
                        }
                    } else {
                        // Check if the renamed recipe is the active one
                        if (this.activerecipe === rowData.name.replace(this.recipefileextension, "")) {
                            // Update active recipe and path
                            this.activerecipe = '';
                        }
                    }
                    // Delete recipe successful
                    this._deleteRecipeEnd();
                    result.status = 0;
                    return result;

                } 
            }
            // open system ConfirmWindow
            // ########################################################################################################################
            // ########################################################################################################################
            else{

                const systemConfirmResult = await this._showConfirm("Do you want to delete this recipe?", "Delete recipe").then(result => {
                    return true;
                }).catch(error => {
                    this._log(error);
                    return false;
                });
                this.windowopened = false;

                // if cancel was pressed
                if(systemConfirmResult === false){
                    // load was cancelled, not an error
                    result.status = 1;
                    result.statustext = "Delete cancelled!";
                    return result;
                }

                // Delete recipe
                const deleteRecipeResult = await this.fmApi.remove(recipeFilePath, progress, begin, end, this.utilsApi.getUID()).catch((error) => {
                    return error;
                });

                // check if result is valid so we can evaluate it
                if(deleteRecipeResult && deleteRecipeResult !== null){
                    // Reset the active recipe file in case the deleted file is the active file
                    if(!this.usesimplemode){
                        // Check if the renamed recipe is the active one
                        if (this.activerecipe === rowData.name) {
                            // Update active recipe and path
                            this.activerecipe = '';
                            this.activerecipefilepath = '';
                        }
                    } else {
                        // Check if the renamed recipe is the active one
                        if (this.activerecipe === rowData.name.replace(this.recipefileextension, "")) {
                            // Update active recipe and path
                            this.activerecipe = '';
                        }
                    }

                    // Deleting recipe successful
                    this._deleteRecipeEnd();
                    result.status = 0;
                    return result;
                    

                } 
            }

        // Delete without confirmation
        }else{
            // Delete recipe
            const deleteRecipeResult = await this.fmApi.remove(recipeFilePath, progress, begin, end, this.utilsApi.getUID()).catch((error) => {
                return error;
            });

            // check if result is valid so we can evaluate it
            if(deleteRecipeResult && deleteRecipeResult !== null){
                // Reset the active recipe file in case the deleted file is the active file
                if(!this.usesimplemode){
                    // Check if the renamed recipe is the active one
                    if (this.activerecipe === rowData.name) {
                        // Update active recipe and path
                        this.activerecipe = '';
                        this.activerecipefilepath = '';
                    }
                } else {
                    // Check if the renamed recipe is the active one
                    if (this.activerecipe === rowData.name.replace(this.recipefileextension, "")) {
                        // Update active recipe and path
                        this.activerecipe = '';
                    }
                }

                // Deleting recipe successful
                this._deleteRecipeEnd();
                result.status = 0;
                return result;

            } 
        }
    }

    /**
     * Copies a recipe 
     *
     * @param {*} path
     * @param {*} newname
     * @param {*} progress
     * @param {*} begin
     * @param {*} end
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _copyRecipe(path, newname, progress, begin, end){
        var actPath = path;

        const actRow = this._getActiveRow();

        // init result
        let result = {
            status: -255,
            statustext: "Unexpected error occured!",
            result: null
        };

        // if NOT Unix convert path -> make it easier for us
        if(path.startsWith('/')){
            actPath = this._convertPath(path);
        }

        // create flag to delete the folder to be overwritten if needed
        let overwriteFolder = false;

        // Check if the name is not used
        if(await this._isRecipeNameAvailable(this.activepath, newname) == false){
            
            overwriteFolder = true;
            // need confirmation of user 
            if(this.confirmcopy === true){

                // open custom ConfirmWindow
                // ########################################################################################################################
                // ########################################################################################################################
                if(Object.entries(this.confirmcopywnd).length !== 0){

                    const confirmResult = await this._openCustomConfirmWindow(this.confirmcopywnd).then(result => {
                        return result;
                    }).catch(error => {
                        return error;
                    });

                    // check if result is valid so we can evaluate it
                    if (confirmResult && confirmResult !== null) {

                        // check for error
                        if(confirmResult instanceof Error){
                            // confirm failed
                            result.status = -2;
                            result.statustext = "Confirm failed! See result for specific error information.";
                            result.result = confirmResult;
                            return result;
                        }

                        // if cancel was pressed
                        if(confirmResult === false){
                            // Copy was cancelled, not an error
                            result.status = 1;
                            result.statustext = "Copy cancelled!";
                            return result;
                        }

                    // result of confirm invalid
                    } else{
                        // confirm failed
                        result.status = -3;
                        result.statustext = "Confirm result invalid!";
                        return result;
                    }

                }
                // open system ConfirmWindow
                // ########################################################################################################################
                // ########################################################################################################################
                else{

                    const systemConfirmResult = await this._showConfirm("Do you want to overwrite this recipe?", "Overwrite recipe").then(result => {
                        return true;
                    }).catch(error => {
                        this._log(error);
                        return false;
                    });
                    this.windowopened = false;

                    // if cancel was pressed
                    if(systemConfirmResult === false){
                        // Copy was cancelled, not an error
                        result.status = 1;
                        result.statustext = "Copy cancelled!";
                        return result;
                    }
                }
            }
        } 


        //
        // Check if it ends with '\' => Folder
        if(actPath[actPath.length-1] === '\\'){    
            
            // Check if a folder is not present => create it
            if (overwriteFolder == false){
                // Create directory old folder
                const createDirResult = await this.fmApi.makeDir(this._getParentPath(actPath) + newname, undefined, undefined, undefined, this.utilsApi.getUID()).catch((error) => {
                    return error;
                });

            }

            const dirInfo = await this.fmApi.readDir(actPath).catch((error) => {
                return error;
            });
            let dirNodes = dirInfo["parent"]["nodes"];
        
            const newFolderPath = this._getParentPath(actPath) + newname;

            let oldFilePath = "";
            let newFilePath = "";
            // Go through the nodes
            for (const counter in dirNodes){
                
                if(dirNodes[counter].dpne != ".."){
                    oldFilePath = actPath + '\\' + dirNodes[counter].dpne;
                    newFilePath = newFolderPath + '\\' + dirNodes[counter].dpne;
    
                    // Copy files
                    const recipeResult = await this.fmApi.copy(newFilePath, oldFilePath, progress, begin, end, this.utilsApi.getUID()).catch((error) => {
                        return error;
                    });  
                }
               
            }

            const oldNamePath = this._getParentPath(newFilePath) + '\\' + this._getLastPathItem(newFilePath);
            const newNamePath = this._getParentPath(newFilePath) + '\\' + newname + this.recipefileextension;
           
            // Rename the recipe file to match the folder name
            const recipeResult = await this.fmApi.rename(newNamePath, oldNamePath, this.utilsApi.getUID()).catch((error) => {
                return error;
            }); 

        // Copy only recipe file
        }else{

            const rowData = JSON.parse(actRow.dataset.data);
            const newRecipePath = rowData.parent + newname + this.recipefileextension;
            const folderResult = await this.fmApi.copy(this._convertPath(newRecipePath), this._convertPath(rowData.path), progress, begin, end, this.utilsApi.getUID()).catch((error) => {
                return error;  
            });

            if (folderResult.status !== undefined) {
                return false;
            }
        }

        return true;

    }


    /**
     * Calls the saveRecipe function and evaluates result.
     * 
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _saveSequence(){
        if(!this.iscmdactive){
            // set flag to avoid unwanted actions like reload
            this.iscmdactive = true;

            // get active path 
            const dirPath = this.activepath;

            // get active row 
            // const row = this.$.recipeslistgrid._getActiveRow();
            const row = this._getActiveRow();

            const result = await this._saveRecipe(dirPath, row, undefined, this.filter1, this.filter2, 
            (progress) => {
                this._updateProgress(progress)

            }, (begin) => {
                this._saveRecipeBegin(begin)

            }, (end) => {
                this._saveRecipeEnd(end)

            }, undefined);

            // set flag to avoid unwanted actions like reload
            this.iscmdactive = false;

            // check if result is valid
            if(result && result !== null) {
                // check if successful
                if(result.status >= 0){
                    switch(result.status){
                        case 0:
                            // save successful
                            if(this.showsavesuccessful){
                                if(Object.entries(this.savesuccessfulwnd).length !== 0) { 
                                    this._openCustomAlertWindow(this.savesuccessfulwnd);
                                }else{
                                    this._showAlert("Saving recipe successful!","Save successful");
                                }
                            }
                        break;
                        case 1:
                            // save cancelled
                        break;
                        default:
                            // successful
                            if(this.showsavesuccessful){
                                if(Object.entries(this.savesuccessfulwnd).length !== 0) { 
                                    this._openCustomAlertWindow(this.savesuccessfulwnd);
                                }else{
                                    this._showAlert("Saving recipe successful!","Save successful");
                                }
                            }
                    }

                    // refresh listview after save or change
                    this._refreshPane(true);

                } else{
                    // save failed
                    if(this.showsavefailed){
                        if(Object.entries(this.savefailedwnd).length !== 0) { 
                            this._openCustomAlertWindow(this.savefailedwnd);
                        }else{
                            this._showAlert("Saving recipe failed!","Save failed");
                        }
                    }
                }
            }
        }
    }


    /**
     * Calls the loadRecipe function and evaluates result.
     *
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _loadSequence(){
        if(!this.iscmdactive){
            // set flag to avoid unwanted actions like reload
            this.iscmdactive = true;

            // get active row 
            // const row = this.$.recipeslistgrid._getActiveRow();
            const row = this._getActiveRow();

            const result = await this._loadRecipe(row, this.filter1, this.filter2, undefined, 
            (progress) => {
                this._updateProgress(progress)

            }, (begin) => {
                this._loadRecipeBegin(begin)

            }, (end) => {
                this._loadRecipeEnd(end)

            }, undefined);

            // set flag to avoid unwanted actions like reload
            this.iscmdactive = false;

            // check if result is valid
            if(result && result !== null) {
                // check if successful
                if(result.status >= 0){
                    switch(result.status){
                        case 0:
                            // load successful
                            if(this.showloadsuccessful){
                                if(Object.entries(this.loadsuccessfulwnd).length !== 0) {
                                    this._openCustomAlertWindow(this.loadsuccessfulwnd);
                                }else{
                                    this._showAlert("Loading recipe successful!","Load successful");
                                }
                            }

                        break;
                        case 1:
                            // load cancelled
                        break;
                        default:
                            // successful
                            if(this.showloadsuccessful){
                                if(Object.entries(this.loadsuccessfulwnd).length !== 0) {
                                    this._openCustomAlertWindow(this.loadsuccessfulwnd);
                                }else{
                                    this._showAlert("Loading recipe successful!","Load successful");
                                }
                            }
                    }

                } else {

                    // load failed
                    if(this.showloadfailed){
                        if(Object.entries(this.loadfailedwnd).length !== 0) {
                            this._openCustomAlertWindow(this.loadfailedwnd);
                        }else{
                            this._showAlert("Loading recipe failed!","Load failed");
                        }
                    }      
                }
            }
        }
    }

    /**
     * Calls the copyRecipe function and evaluates result.
     * 
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _copySequence(){

        const actRow = this._getActiveRow();
        let newName = null;

        if(actRow === null){
            return false;
        }

        const rowData = JSON.parse(actRow.dataset.data);

        // Create a string which is to be shown in prompt; For files it shouldn't contain the extension
        let promptName = "";
        // Check if it is a file with extension
        if(rowData.name.substring(rowData.name.length - this.recipefileextension.length) == this.recipefileextension){
            promptName = rowData.name.slice(0, rowData.name.length - this.recipefileextension.length);
        }else{
            promptName = rowData.name;
        }

        // Add a default sufix to the name (_copy)
        promptName = promptName + "_copy";

        // if custom window is set
        if(Object.entries(this.copywnd).length !== 0){
            const customPromptResult = await this._openCustomPromptWindow(this.copywnd, promptName).then(result => {
                return result;
            }).catch(error => {
                return error;
            });

            // check if result is valid so we can evaluate it
            if (customPromptResult && customPromptResult !== null) {

                // check for error
                if(customPromptResult instanceof Error){
                    // prompt failed
                    return false;
                }

                // if cancel was pressed
                if(!customPromptResult.result){
                    // Copy was cancelled, not an error
                    return true;
                }

                // if ok was pressed
                // check if name is valid for a recipe
                if(!this._isRecipeNameValid(customPromptResult.data)){
                    // recipe name invalid
                    return false;
                }

            // result of prompt invalid
            } else{
                // prompt failed
                return false;
            }
            
            newName = customPromptResult.data;
        
        // open system window
        }else{
            const systemPromptResult = await this._showPrompt("Enter new name of the copy of the recipe: ", "Copy recipe", promptName).then(result => {
                return result;
            }).catch(error => {
                return false;
            });
            this.windowopened = false;

            // if cancel was pressed
            if(systemPromptResult === false){
                return true;
            }

            newName = systemPromptResult;
        }

        let result = false;

        if(!this.iscmdactive){
            // set flag to avoid unwanted actions like reload
            this.iscmdactive = true;


            result = await this._copyRecipe(this._convertPath(rowData.path), newName, 
            (progress) => {
                this._updateProgress(progress)

            }, (begin) => {
                this._copyRecipeBegin(begin)

            }, (end) => {
                this._copyRecipeEnd(end)

            });

            // set flag to avoid unwanted actions like reload
            this.iscmdactive = false;
        }       
        
        // Check if the result was good
        if (result == true){

            // refresh listview after renamee
            this._refreshPane(true);
        }
        
    }    

    /**
     * Calls the renameRecipe function and evaluates result.
     * 
     * @memberof LasalRuntimeSigControlRecipeHandling
     */   
    async _renameSequence(){

        const actRow = this._getActiveRow();
        let newName = null;

        if(actRow === null){
            return false;
        }

        const rowData = JSON.parse(actRow.dataset.data);

        // Create a string which is to be shown in prompt; For files it shouldn't contain the extension
        let promptName = "";
        // Check if it is a file with extension
        if(rowData.name.substring(rowData.name.length - this.recipefileextension.length) == this.recipefileextension){
            promptName = rowData.name.slice(0, rowData.name.length - this.recipefileextension.length);
        }else{
            promptName = rowData.name;
        }

        
        // if custom window is set
        if(Object.entries(this.renamewnd).length !== 0){

            const customPromptResult = await this._openCustomPromptWindow(this.renamewnd, promptName).then(result => {
                return result;
            }).catch(error => {
                return error;
            });

            // check if result is valid so we can evaluate it
            if (customPromptResult && customPromptResult !== null) {

                // check for error
                if(customPromptResult instanceof Error){
                    // prompt failed
                    return false;
                }

                // if cancel was pressed
                if(!customPromptResult.result){
                    // save was cancelled, not an error
                    return true;
                }

                // if ok was pressed
                // check if name is valid for a recipe
                if(!this._isRecipeNameValid(customPromptResult.data)){
                    // recipe name invalid
                    return false;
                }

            // result of prompt invalid
            } else{
                // prompt failed
                return false;
            }

            newName = customPromptResult.data;
        
        // open system window
        }else{
            const systemPromptResult = await this._showPrompt("Enter new name of recipe: ", "Rename recipe", promptName).then(result => {
                return result;
            }).catch(error => {
                return false;
            });
            this.windowopened = false;

            // if cancel was pressed
            if(systemPromptResult === false){
                return true;
            }

            newName = systemPromptResult;
        }
        
        const renameResult = await this._renameRecipe(this._convertPath(rowData.path), newName);
        
        // Check if the result was good
        if (renameResult == true){

            if(!this.usesimplemode){
                // Check if the renamed recipe is the active one
                if (this.activerecipe === rowData.name) {
                    // Update active recipe and path
                    this.activerecipe = newName;
                    this.activerecipefilepath = this._convertPath(rowData.parent + newName + '/');
                }
            } else {
                // Check if the renamed recipe is the active one
                if (this.activerecipe === rowData.name.replace(this.recipefileextension, "")) {
                    // Update active recipe and path
                    this.activerecipe = newName;
                }
            }
            // refresh listview after renamee
            this._refreshPane(true);
        }

    }


    /**
     * Calls the deleteRecipe function and evaluates result.
     * 
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _deleteSequence(){
        if(!this.iscmdactive){
            // set flag to avoid unwanted actions like reload
            this.iscmdactive = true;

            // get active row 
            // const row = this.$.recipeslistgrid._getActiveRow();
            const row = this._getActiveRow();

            const result = await this._deleteRecipe(row, 
            (progress) => {
                this._updateProgress(progress)

            }, (begin) => {
                this._deleteRecipeBegin(begin)

            }, (end) => {
                this._deleteRecipeEnd(end)

            });

            // set flag to avoid unwanted actions like reload
            this.iscmdactive = false;

            // check if result is valid
            if(result && result !== null) {
                // check if successful
                if(result.status >= 0){
                    switch(result.status){
                        case 0:
                            // Delete successful
                            if(this.showdeletesuccessful){
                                if(Object.entries(this.deletesuccessfulwnd).length !== 0) {
                                    this._openCustomAlertWindow(this.deletesuccessfulwnd);
                                }else{
                                    this._showAlert("Deleting recipe successful!","Delete successful");
                                }
                            }

                            // refresh listview after save or change
                            this._refreshPane(true);

                        break;
                        case 1:
                            // Delete cancelled
                        break;
                        default:
                            // successful
                            if(this.showdeletesuccessful){
                                if(Object.entries(this.deletesuccessfulwnd).length !== 0) {
                                    this._openCustomAlertWindow(this.deletesuccessfulwnd);
                                }else{
                                    this._showAlert("Deleting recipe successful!","Delete successful");
                                }
                            }
                    }

                } else{
                    // Delete failed
                    if(this.showdeletefailed){
                        if(Object.entries(this.deletefailedwnd).length !== 0) {
                            this._openCustomAlertWindow(this.deletefailedwnd);
                        }else{
                            this._showAlert("Deleting recipe failed!","Delete failed");
                        }
                    }
                }
            }
        }

    } 

    /**
     * Updates the listviewdata after recipe was changed or created.
     *
     * @param {*} recipeFolder
     * @param {*} metafileName
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _updateListviewDataOfRecipe(recipeFolder, metafileName){
        let actPath = null;

        // check parameter
        if(recipeFolder === undefined || recipeFolder === null || metafileName === undefined || metafileName === null){
            return false;
        }

        // if NOT Unix convert path
        if(!recipeFolder.startsWith('/')){
            actPath = this._convertPath(recipeFolder + '\\' + metafileName);
        }else{
            actPath = recipeFolder + metafileName;
        }

        const metaData = await this._readMetafile(actPath);

        this._updateRecipesMetaData(actPath, metaData);
    }

    /**
     * Sets the recipesMetaData variable with the given data.
     *
     * @param {*} data
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _setRecipesMetaData(data){
        if(data !== undefined && data !== null){
            this.recipesMetaData = data;
        }
    }


    /**
     * Updates the metadata of a specific element stored in recipesMetaData.
     * If the element doesnt exist it is added to the array. 
     * 
     * @param {string} path The path of the metafile that changed.
     * @param {object} metadata The metadata of the metafile.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _updateRecipesMetaData(path, metadata){
        let result = false;

        // if path is not valid
        if(!path.includes(this.activepath)){
            return result;
        }

        // update the metadata
        this.recipesMetaData.forEach(elem => {
            if(elem.path === path){
                elem.metadata = (metadata && metadata !== null) ? metadata : {};
                result = true;
            }  
        });

        // if the path doesnt exist in our array
        // -> create the element and push it to the array
        if(!result){
            const newElem = this._createObject_metadata(path, metadata);
            this.recipesMetaData.push(newElem);
            result = true;
        }

        return result;
    }


    /**
     * Test function: simulate progress
     *
     * @param {String} text
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _testUpdateProgress(text){
        this.test_showprogress = true;
        this.iswaiting = true;

        if(this.test_showprogressInterval !== null){
            this.sigClearInterval(this.test_showprogressInterval);
            this.test_showprogressInterval = null;
        }else{
            this.test_showprogressInterval = this.sigSetInterval(this._updateProgress.bind(this, undefined, text), 100);
        }
    }


    /**
     * Callback for the load() & save() function of the RecipeManagerApi.
     * Will be called during loading/saving and displays the actual progress.
     * Can be overwritten.
     * 
     * @param {Object} status
     * @param {String} text
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _updateProgress(status, text){
        // current action will be displayed in progress window
        this.currentaction = text;

        if(!this.test_showprogress){
            const progress = status.progress / 10;
            this.progress = progress;

        // for testing to simulate progress dialog
        } else{
            if(this.progress == 100) {
                if(this.test_showprogressInterval !== null){
                    this.progress = 0;
                    this.sigClearInterval(this.test_showprogressInterval);
                    this.test_showprogressInterval = null;
                    
                    // hide waiting screeen
                    this.iswaiting = false;
                }
            }else{
                this.progress += 1;
            }
        }

        this._updateCustomProgressWnd();
    }


    /**
     * Callback for the load() function of the RecipeManagerApi.
     * Will be called when loading begins.
     * @param {Object} uidObj
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _loadRecipeBegin(uidObj){
        // freezes control for user interaction
        this.iswaiting = true;
    }


    /**
     * Callback for the load() function of the RecipeManagerApi.
     * Will be called when loading ended.
     * @param {Object} uidObj
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _loadRecipeEnd(uidObj){
        // releases control for user interaction
        this.iswaiting = false;
    }


    /**
     * Callback for the save() function of the RecipeManagerApi.
     * Will be called when saving begins.
     * @param {Object} uidObj
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _saveRecipeBegin(uidObj){
        // can be overwritten
        this.iswaiting = true;
    }


    /**
     * Callback for the save() function of the RecipeManagerApi.
     * Will be called when saving ended.
     * @param {Object} uidObj
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _saveRecipeEnd(uidObj){
        // can be overwritten
        this.iswaiting = false;
    }


    /**
     * Callback for the delete() function of the FileManagerApi.
     * Will be called when deleting begins.
     * @param {Object} uidObj
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _deleteRecipeBegin(uidObj){
        // freezes control for user interaction
        this.iswaiting = true;
    }


    /**
     * Callback for the delete() function of the FileManagerApi.
     * Will be called when deleting ended.
     * @param {Object} uidObj
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _deleteRecipeEnd(uidObj){
        // releases control for user interaction
        this.iswaiting = false;
    }

    /**
     * Callback for the copy() function of the FileManagerApi.
     * Will be called when copying begins.
     * @param {Object} uidObj
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _copyRecipeBegin(uidObj){
        // freezes control for user interaction
        this.iswaiting = true;
    }


    /**
     * Callback for the copy() function of the FileManagerApi.
     * Will be called when copying ended.
     * @param {Object} uidObj
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _copyRecipeEnd(uidObj){
        // releases control for user interaction
        this.iswaiting = false;
    }


    /**
     * Converts the native filesystem object returned by the filemanager API
     * to a format which can be rendered by the listview.
     *
     * @private
     * @param {Object} fs The native filesystem object returned by the filemanager API.
     * @returns {Object | null} The converted filesystem object, which can be rendered by the listview or null.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _buildNodeFromFilesystem(fs) {
        const nodeMetaConfig = this._getItemConfig();

        if (fs.parent instanceof Object) {
            const node = {
                parent: '/',
                path: '/',
                name: 'root',
                type: '',
                size: 0,
                created: 0,
                modified: 0,
                attributes: {},
                nodes: []
            };
            const childs = [];
            let filter;

            try {
                filter = new RegExp(this.recipefileextension, 'g');
            } catch (error) {
                filter = new RegExp('', 'g');
                this._log(error.message, 'WARN');
            }

            // fs.parent.nodes.forEach(node => {
            await this.asyncForEach(fs.parent.nodes, async (node) => {
                let type, parent, path, push = false;
                const attributes = this.fmApi.getFileAttributesAsArray(node.attribute);
                if (node.dpne !== '.') {
                    if (attributes.directory) {
                        if (node.dpne !== '..') {
                            type = 'folder'
                            parent = fs.parent.dpne;
                            path = fs.parent.dpne + node.dpne + '\\';

                            if (this.usesimplemode) {
                                push = false;
                            }
                            else {
                                push = true;
                            }
                        }
                    } else if(this.usesimplemode && filter.test(node.dpne)) {
                        filter.lastIndex = 0;
                        type = 'file';
                        parent = fs.parent.dpne;
                        path = fs.parent.dpne + node.dpne;
                        push = true;
                    }

                    // if (push && ((!attributes.hidden || this.showhiddennodes) && (!attributes.system || this.showsystemnodes))) {
                    if (push && ((!attributes.hidden || this.showhiddennodes) && (!attributes.system || this.showsystemnodes))) {
                    
                        let recipeAttributes, recipeModified = null;

                        if(type === 'folder'){
                            // get info of recipe file to display it instead of the info from the folder
                            recipeAttributes = await this.fmApi.info(path + '\\' + node.dpne + this.recipefileextension).catch((error) => {
                                return error;
                            });

                            // if getting info failed -> not a valid recipe
                            if(recipeAttributes.status !== undefined){
                                push = false;
                                return;
                            }

                            // set min for datepicker
                            if(!this.minMax.date.min || this.minMax.date.min > recipeAttributes.AccessDate){
                                this.minMax.date.min = recipeAttributes.AccessDate;
                            }
                            
                            // set max for datepicker
                            if(!this.minMax.date.max || this.minMax.date.max < recipeAttributes.AccessDate){
                                this.minMax.date.max = recipeAttributes.AccessDate;
                            }
    
                            recipeModified = this._getFulldate(recipeAttributes.AccessDate, recipeAttributes.AccessTime, true);
                        }else{
                            recipeModified = this._getFulldate(node.adate, node.atime, true);

                            // set min for datepicker
                            if(!this.minMax.date.min || this.minMax.date.min > node.adate){
                                this.minMax.date.min = node.adate;
                            }

                            // set max for datepicker
                            if(!this.minMax.date.max || this.minMax.date.max < node.adate){
                                this.minMax.date.max = node.adate;
                            }
                        }

                        let item = {
                            parent: this._convertPath(parent),
                            path: this._convertPath(path),
                            name: node.dpne,
                            modified: (type !== 'parent' && type !== 'volume') ? recipeModified : '',
                            type: type
                        };

                        if(nodeMetaConfig !== null){
                            const actPath = this._convertPath(path);
                            const actParent = this._convertPath(parent);
                            // assign config defined in metafile
                            Object.assign(item, nodeMetaConfig);

                            this.recipesMetaData.forEach(metafileData => {
                                if(type == 'folder'){                                    
                                    // make sure it is the metafile of this recipe folder
                                    if(metafileData.path.includes(actPath)){
                                        const keys = Object.keys(metafileData.metadata);
        
                                        for(let key of keys){
                                            item[key] = metafileData.metadata[key];
                                        }
                                    }
                                }else{
                                    // make sure it is the metafile of this recipe file
                                    if(metafileData.path.includes(actParent)){
                                        const keys = Object.keys(metafileData.metadata);
        
                                        for(let key of keys){
                                            item[key] = metafileData.metadata[key];
                                        }
                                    }
                                }
                            });
                        }
                        childs.push(item);
                    }
                }
            });
            node.nodes = childs.sort((a, b) => {
                return this._doSort(a,b);
                // return this._sort(a.type, b.type) || this._sort(a.name, b.name, false);
            });
            return node;
        }
        return null;
    }


    /**
     * Returns an object which contains the metadata of the listview as properties. 
     * This object is needed to fill the columns of the folder and file of a recipe with the metadata.
     *
     * @returns {Object} An object for the listview. Null is returned if no data is available. 
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _getItemConfig(){
        let itemConfig = {};

        if(this.listviewConfigOfMetafile !== null){
            const listviewConfig = this.listviewConfigOfMetafile.map(a => a.id);
    
            for(let i=0; i < listviewConfig.length; i++){
                const property = listviewConfig[i];
                itemConfig[listviewConfig[i]] = "";
            }
        }
        
        return (Object.keys(itemConfig).length !== 0) ? itemConfig : null;
    }


    /**
     * Returns the metadata of a row, in the format specified with config parameter.
     *
     * @param {*} data The row data
     * @param {*} config The config of the metadata
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _getMetaDataOfRow(data, config){
        let metadata = {};

        // check parameter
        if(config === null || data === null) return null;

        Object.assign(metadata, config);

        const keysMeta = Object.keys(metadata);
        // const keysRow = Object.keys(data);
      
        for (let key of keysMeta) {
            metadata[key] = data[key];
        }

        return metadata;
    }


    /**
     * Returns paths of all additional files in a specific folder. 
     *
     * @param {String} path The path in unix or windows format.
     * @param {boolean} [unix=true] If true (default) paths are stored in unix format. 
     * @returns {*} An array of paths or null if no additional files found or an error occured.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _getAddFilePathsOfFolder(path, unix = true){
        let addFiles = [];
        let actPath = null;

        // check parameter
        if(path === undefined || path === null) return null;

        // if Unix convert path
        if(path.startsWith('/')){
            actPath = this._convertPath(path);
        }else{
            actPath = path;
        }

        // read dir content
        const recipeDir = await this.fmApi.readDir(actPath);

        if(recipeDir.status !== undefined || recipeDir.status === ""){
            return null;
        }

        // set recipe- & metafile-name to exclude from files
        // const recipeFile = recipeDir.parent.dpne;
        const recipeFile = this._getLastPathItem(recipeDir.parent.dpne) + this.recipefileextension;
        const metafile = this.metafileforrecipe;

        // loop through content of folder
        await this.asyncForEach(recipeDir.parent.nodes, async (node) => {
            const attributes = this.fmApi.getFileAttributesAsArray(node.attribute);

            if (node.dpne !== '.') {
                if (!attributes.directory && !attributes.archive) {
                    // found our file
                    if(node.dpne !== recipeFile && node.dpne !== metafile){
                        if(unix){
                            addFiles.push(this._convertPath(recipeDir.parent.dpne + '\\' + node.dpne));
                        }else{
                            addFiles.push(recipeDir.parent.dpne + '\\' + node.dpne);
                        }
                    }
                }
            }
        });

        // return array of paths
        return (addFiles !== []) ? addFiles : null;    
    }


    /**
     * Renames the selected recipe.
     * After action metadata array must be updated.
     *
     * @param {*} newname
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _renameRecipe(path, newname){
        var actPath = path;

        // init result
        let result = {
            status: -255,
            statustext: "Unexpected error occured!",
            result: null
        };

        // if NOT Unix convert path -> make it easier for us
        if(path.startsWith('/')){
            actPath = this._convertPath(path);
        }

        // Check if the new name is the same as the old one => Do nothing then
        if(actPath[actPath.length-1] === '\\'){
            if(this._getLastPathItem(path) == newname){
                result.status = 0;
                result.statustext = "Same folder name given.";
                result.result = null;
                return result;
            }
        }else{
            if(this._getLastPathItem(path) == newname + this.recipefileextension){
                result.status = 0;
                result.statustext = "Same file name given.";
                result.result = null;
                return result;
            }
        }

        // create flag to delete the folder to be overwritten if needed
        let overwriteFolder = false;

        // Check if the name is not used
        if(await this._isRecipeNameAvailable(this.activepath, newname) == false){
            
            overwriteFolder = true;
            // need confirmation of user 
            if(this.confirmrename === true){

                // open custom ConfirmWindow
                // ########################################################################################################################
                // ########################################################################################################################
                if(Object.entries(this.confirmrenamewnd).length !== 0){

                    const confirmResult = await this._openCustomConfirmWindow(this.confirmrenamewnd).then(result => {
                        return result;
                    }).catch(error => {
                        return error;
                    });

                    // check if result is valid so we can evaluate it
                    if (confirmResult && confirmResult !== null) {

                        // check for error
                        if(confirmResult instanceof Error){
                            // confirm failed
                            result.status = -2;
                            result.statustext = "Confirm failed! See result for specific error information.";
                            result.result = confirmResult;
                            return result;
                        }

                        // if cancel was pressed
                        if(confirmResult === false){
                            // Rename was cancelled, not an error
                            result.status = 1;
                            result.statustext = "Rename cancelled!";
                            return result;
                        }

                    // result of confirm invalid
                    } else{
                        // confirm failed
                        result.status = -3;
                        result.statustext = "Confirm result invalid!";
                        return result;
                    }

                }
                // open system ConfirmWindow
                // ########################################################################################################################
                // ########################################################################################################################
                else{

                    const systemConfirmResult = await this._showConfirm("Do you want to overwrite this recipe?", "Overwrite recipe").then(result => {
                        return true;
                    }).catch(error => {
                        this._log(error);
                        return false;
                    });
                    this.windowopened = false;

                    // if cancel was pressed
                    if(systemConfirmResult === false){
                        // rename was cancelled, not an error
                        result.status = 1;
                        result.statustext = "Rename cancelled!";
                        return result;
                    }
                }
            }
        } 

        //
        // Check if it ends with '\' => Folder
        if(actPath[actPath.length-1] === '\\'){    
            
            // Check if a folder is to be overwitten
            if (overwriteFolder == true){
                // delete old folder
                const deleteRecipeResult = await this.fmApi.remove(this._getParentPath(actPath) + newname, undefined, undefined, undefined, this.utilsApi.getUID()).catch((error) => {
                    return error;
                });

            }

            const oldFolderPath = actPath;
            const newFolderPath = this._getParentPath(actPath) + newname;
            const oldRecipePath = oldFolderPath + '\\' + this._getLastPathItem(actPath) + this.recipefileextension;
            const newRecipePath = oldFolderPath + '\\' + newname + this.recipefileextension;
            
            // rename file
            const recipeResult = await this.fmApi.rename(newRecipePath, oldRecipePath).catch((error) => {
                return error;
            }); 

            if (recipeResult.status !== undefined) {
                return false;
            }

            // rename folder
            const folderResult = await this.fmApi.rename(newFolderPath, oldFolderPath).catch((error) => {
                return error;
            });

            if (folderResult.status !== undefined) {
                return false;
            }

        // rename recipe file
        }else{

            const actRow = this._getActiveRow();
            const rowData = JSON.parse(actRow.dataset.data);
            const newRecipePath = rowData.parent + newname + this.recipefileextension;
            const folderResult = await this.fmApi.rename(this._convertPath(newRecipePath), this._convertPath(rowData.path)).catch((error) => {
                return error;  
            });

            if (folderResult.status !== undefined) {
                return false;
            }
        }

        return true;
        
    }


    /**
     * Extracts the last item of a path and returns it.
     *
     * @param {String} path The path in unix or windows format.
     * @returns {String} Extracted item.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _getLastPathItem(path){
        let actPath = null;

        // if NOT Unix convert path -> make it easier for us
        if(!path.startsWith('/')){
            actPath = this._convertPath(path);
        }else{
            actPath = path;
        }

        // delete last character if trailing slash
        if(actPath[actPath.length-1] === '/'){
            actPath = actPath.slice(0, -1);
        }
        
        const pathItem = actPath.substring(actPath.lastIndexOf('/') + 1, actPath.length);

        return pathItem;
    }


    /**
     * Converts a given filesystem path to and from windows to unix.
     *
     * @private
     * @param {String} [path=''] The path which should be converted.
     * @returns {String} The converted path.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _convertPath(path = '') {
        let converted = path;
        if (path.startsWith('/')) { // UNIX => WIN
            converted = converted.substring(1).replace(/\//g, '\\');
            converted = converted.substr(0, 1) + ":" + converted.substr(1);
        } else { // WIN => UNIX
            const prefix = (path === '\\') ? '' : '/';
            converted = prefix + converted.replace(/\\/g, '/');
            converted = converted.replace(/:/g, '');
        }
        return converted;
    }

    /**
     * Extracts the parent path of a given filesystem path.
     *
     * @private
     * @param {String} path The path to extract the parent from.
     * @returns {Sting} The extracted parent path.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _getParentPath(path) {
        let converted = path;
        let delimiter = '\\';
        if (path.startsWith('/')) {
            delimiter = '/';
        }
        if (converted.substring(converted.length - 1) === delimiter) {
            converted = converted.substring(0, converted.length - 1);
        }

        converted = converted.slice(0, converted.lastIndexOf(delimiter)) + delimiter;

        if (converted.length <= 2) {
            return delimiter;
        } else {
            return converted;
        }
    }


    /**
     *
     * @private
     * @param {String} newval The new path to switch to.
     * @param {String} oldval The old (current) path.
     * @param {Boolean} restorerow Set true if the active selected row of the listview should be restored.
     * @returns {Undefined} Undefined
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _updatePath(newval, oldval, restorerow) {
        if (!this.isdesignmode) {
            if (newval /* && oldval */) {
                let fileSystem = null;

                this._toggleRestoreRow(restorerow);

                // if path changed 
                // -> reset current metadata and load again
                // if(oldval !== null && newval !== oldval){
                    this.listviewConfigOfMetafile = null;
                    this.recipesMetaData = [];

                    // load the metadata of the control
                    await this._loadMetaDataOfControl(newval);
                // }

                const data = {
                    cols: this._getColumnsConfig(),
                    oldpath: oldval
                };

                if (newval === '/') {
                    fileSystem = await this.fmApi.getRoot().catch((error) => {
                        return error;
                    });
                } else {
                    const convertedPath = this._convertPath(newval);
                    fileSystem = await this.fmApi.readDir(convertedPath, undefined, undefined, undefined).catch((error) => {
                        return error;
                    });
                }
        
                if (!fileSystem.status) {
                    data.data = await this._buildNodeFromFilesystem(fileSystem);
                    if (data.data !== null) {
                        // save current nodes by cloning array
                        this.currentFileSystem = Array.from(data.data.nodes);

                        if (newval !== oldval) this.$.recipeslistgrid.config = Object.assign({ name: 'recipes', data: data }, this.gridConfig);
                        else this.$.recipeslistgrid.configdata = Object.assign({}, data);
                    }
                    this.refreshCount = 0;
                } else { // restore old path or fall back to root before ending up in a refresh loop!
                    let path;
                    if (this.refreshCount < this.refreshCountMax) {
                        path = oldval;
                        this.refreshCount += 1;
                    } else {
                        path = '/';
                        this._log('Invalid Path detected falling back to root!');
                    }
        
                    this.activepath = path;
                }

                // reset filter 
                this.filter = null;
                this.$.recipeslistgrid.filter = null;

                // update data of listview
                this._updateData();
            }
        }
    }


    /**
     * Converts a given date & time into a full date/time string.
     *
     * @private
     * @param {Number} date The date timestamp
     * @param {Number} time The time timestamp
     * @param {boolean} [noSeconds=false] If true, the seconds are not returned.
     * @returns {String} The full date and time string 
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _getFulldate(date, time, noSeconds = false) {
        const fulldate = this.utilsApi.getSigDateTimeAsString(date, time, '.', ':');
        if (noSeconds) {
            return fulldate.substring(0, fulldate.length - 3);
        } else {
            return fulldate;
        }
    }


    /**
     * This event handler handles the LANGUAGE_CHG event of the runtime and switches the language.
     *
     * @private
     * @param {Number} majorId The major id of the runtime event.
     * @param {Number} minorId The minor id of the runtime event.
     * @param {Object} msg The payload of the runtime event.
     * @returns {Undefined} Undefined
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _switchLang(majorId, minorId, msg) {
        if (this.tmApi) {
            this.texts = this._getTextsObject()[this.controlTextList];
            this._updateColumns();
        }
    }


    /**
     * Generates the preview in the designer.
     * 
     * @private
     * @returns {Undefined} Undefined
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _renderPreview() {
        this.texts = this._getTextsObject()[this.controlTextList];
        // this.isactive = true;
        const demodata = {
            cols: this._getColumnsConfig(),
            data: {
                parent: '/',
                path: '/',
                name: "root",
                type: "",
                size: 0,
                created: 0,
                modified: 0,
                attributes: {},
                nodes: [{
                    parent: "/C/",
                    path: "/C/recipes/",
                    name: "recipe_01",
                    type: "folder",
                    size: "",
                    created: "05.06.2019 09:12",
                    modified: "05.06.2019 09:12",
                    accessed: "05.06.2019 09:12",
                    attributes: 16,
                    active: true,
                }, {
                    parent: "/C/",
                    path: "/C/recipes/",
                    name: "recipe_02",
                    type: "folder",
                    size: "",
                    created: "11.06.2018 08:06",
                    modified: "11.06.2018 08:06",
                    accessed: "11.06.2018 08:06",
                    attributes: 16
                }, {
                    parent: "/C/",
                    path: "/C/recipes/",
                    name: "recipe_03",
                    type: "folder",
                    size: "",
                    created: "12.07.2018 10:45",
                    modified: "12.07.2018 10:45",
                    accessed: "12.07.2018 10:45",
                    attributes: 16
                },
                {
                    parent: "/C/",
                    path: "/C/recipes/",
                    name: "recipe_04",
                    type: "folder",
                    size: "",
                    created: "14.02.2018 12:23",
                    modified: "14.02.2018 12:23",
                    accessed: "14.02.2018 12:23",
                    attributes: 16
                },
                {
                    parent: "/C/",
                    path: "/C/recipes/",
                    name: "recipe_05",
                    type: "folder",
                    size: "",
                    created: "20.01.2020 06:06",
                    modified: "20.01.2020 06:06",
                    accessed: "20.01.2020 06:06",
                    attributes: 16
                },
                {
                    parent: "/C/",
                    path: "/C/recipes/",
                    name: "recipe_06",
                    type: "folder",
                    size: "",
                    created: "21.02.2020 16:37",
                    modified: "21.02.2020 16:37",
                    accessed: "21.02.2020 16:37",
                    attributes: 16
                },
            ]
            }
        };

        this.$.recipeslistgrid.config = Object.assign({ name: 'recipes', data: demodata }, this.gridConfig);

        this.sigSetTimeout(() => {
            this.$.recipeslistgrid.activerow = 2;
            this.$.recipeslistgrid.isactive = true;
        }, 20);
    }


    /**
     * Returns the configuration of the cols rendered by the listview.
     *
     * @private
     * @returns {Undefined} Undefined
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _getColumnsConfig() {
        let colsConfig = [{
            id: 'name',
            name: this.nametitle,
            type: 'text',
            default: '&nbsp;',
            visible: this.namevisible,
            sortable: this.namesortable,
            filterable: this.namefilterable,
            filtervalue: null,
            css: {
                'width': parseFloat(this.namewidth) + 'px',
            },
            order: this.nameorder
        }, 
        // {
        //     id: 'info',
        //     name: 'symbol',
        //     type: 'symbol',
        //     default: "<img src='../../res/resources/images/info-03.svg' width='25px' height='auto'>",
        //     visible: true,
        //     sortable: false,
        //     filterable: false,
        //     filtervalue: null,
        //     css: {
        //         'width': '25px',
        //     },
        //     order: 60
        // },
        {
            id: 'modified',
            name: this.modifiedtitle,
            type: 'text',
            default: '&nbsp;',
            visible: this.modifiedvisible,
            sortable: this.modifiedsortable,
            filterable: this.modifiedfilterable,
            filtervalue: null,
            css: {
                'width': parseFloat(this.modifiedwidth) + 'px',
            },
            formatDataCallback: (dateTimeString, row, col) => this._formatDateTimeString(dateTimeString),
            order: this.modifiedorder
        }];

        // check if listview config of metafile is not empty
        if(this.listviewConfigOfMetafile !== null && this.listviewConfigOfMetafile.length > 0){
            let counter = 0;
            this.listviewConfigOfMetafile.forEach( column => {
                
                // Get the text from LVD
                let langIndex = this.tmApi.getCurrentLanguage().languageIndex;
                let textListLVD = this.listviewConfigOfMetafile[counter].nametextlist;
                let textLVD = this.listviewConfigOfMetafile[counter].text;
                // Generate the name from the LVD texts
                this.listviewConfigOfMetafile[counter].name = this.tmApi.getText(textListLVD, textLVD, langIndex);
                colsConfig.push(column);
                counter = counter + 1;
            });
            return colsConfig.sort((c1, c2) => {
                return c1.order - c2.order;
            });

        }else{
            return colsConfig.sort((c1, c2) => {
                return c1.order - c2.order;
            });
        }
    }


    /**
     * Formats the given date/time into the correct format.
     *
     * @param {*} dateTimeString
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _formatDateTimeString(dateTimeString) {
        const datetime = moment(dateTimeString, this.sigDateTimeFormat);
        return datetime.isValid() ? datetime.format(this.datetimeformat) : '';
    }


    /**
     * Returns a text object.
     * Find better solution...
     * 
     * @private
     * @returns {Object} The text list object.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _getTextsObject() {
        // return german texts
        if(!this.isdesignmode && this.tmApi.getCurrentLanguage().languageCode.startsWith("de-")){
            return {
                [this.controlTextList]: {
                    caption: 'Rezeptverwaltung',
                    load: 'Laden',
                    save: 'Speichern',
                    copy: 'Kopieren',
                    rename: 'Umbenennen',
                    delete: 'Löschen'
                }
            }
        // english texts
        } else{
            return {
                [this.controlTextList]: {
                    caption: 'Recipe Handling',
                    load: 'Load',
                    save: 'Save',
                    copy: 'Copy',
                    rename: 'Rename',
                    delete: 'Delete'
                }
            }
        }

    }


    /**
     * This observer toggles if the row cursor can be moved using the keyboard.
     *
     * @private
     * @param {Boolean} newval The new value of the observer polymer property.
     * @param {Boolean} oldval The old value of the observer polymer property.
     * @returns {Undefined} Undefined 
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _toggleDisableKeyMovement(newval, oldval) {
        if (!this.disablekeymovement) {
            this.$.recipeslistgrid.disablekeys = newval;
        } else {
            this.$.recipeslistgrid.disablekeys = true;
        }
    }


    /**
    * This observer toggles if the system dialogs support keyboard control.
    *
    * @private
    * @param {Boolean} newval The new value of the observer polymer property.
    * @param {Boolean} oldval The old value of the observer polymer property.
    * @returns {Undefined} Undefined
    * @memberof LasalRuntimeSigControlRecipeHandling
    * @todo Needs implementation
    */
    _toggleDisableKeysInDialogs(newval, oldval) {
        // Needs implementation after system windows support keyboard shortcuts
    }


    /**
    * This observer deactivates the keyboard support of the listview if a dialog is opened.
    *
    * @private
    * @param {Boolean} newval The new value of the observer polymer property.
    * @param {Boolean} oldval The old value of the observer polymer property.
    * @returns {Undefined} Undefined
    * @memberof LasalRuntimeSigControlRecipeHandling
    */
   _toggleWindowOpened(newval, oldval) {
        if (!this.disablekeyactions) {
            if (newval) {
                this._toggleDisableKeyMovement(true);
            } else {
                this._toggleDisableKeyMovement(false);
            }
        }
    }


    /**
     * Opens a custom alert window.
     *
     * @param {*} windowName
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _openCustomAlertWindow(windowName){
        const openWindowResult = await this.wmApi.openWindow(windowName).then(result => {
            return true;
        }).catch(error => {
            this._log(error);
            return false;
        });  

        // window opened successfully
        if(openWindowResult){
            return true;
        }

        return false;
    }


    /**
     * Opens a custom window and executes given function, 
     * if OK or CANCEL is pressed.
     *
     * @async
     * @private
     * @param {Object} windowName
     * @param {*} checkResult
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _openCustomConfirmWindow(windowName){
        let statustext = "";

        this.windowopened = true;

        const openWindowResult = await this.wmApi.openWindow(windowName).then(result => {
            return true;
        }).catch(error => {
            this._log(error);
            return false;
        });  

        // window opened successfully
        if(openWindowResult){

            const windowObj = this.wmApi.getWindow(windowName);

            // get buttons of window
            const buttonOk = this._getWindowChildRef(windowObj, "ok");
            const buttonCancel = this._getWindowChildRef(windowObj, "cancel");

            // if window has ok and cancel button
            if(buttonOk && buttonCancel){
                const promiseOk = this.waitForOk(buttonOk);
                const promiseCancel = this.waitForCancel(buttonCancel);
                const windowClosed = this.waitForWindowClosed(windowObj);

                const resolved = await Promise.race([promiseOk, promiseCancel, windowClosed]);

                buttonOk.sigRemoveAllEventListeners();
                buttonCancel.sigRemoveAllEventListeners();

                if(windowObj.isOpen()) this.wmApi.closeWindow(windowName);
                this.windowopened = false;

                if(resolved){
                    // resolve promise -> ok
                    return new Promise((resolve, reject) => {
                        resolve(true);
                    });
                }else{
                    // resolve promise -> cancel
                    return new Promise((resolve, reject) => {
                        resolve(false);
                    });
                }

            // needed controls not found
            }else{
                if(windowObj.isOpen()) this.wmApi.closeWindow(windowName);
                this.windowopened = false;

                // set statustext
                statustext = "Needed controls not found!";
            }
        }else{
            // set statustext
            statustext = "Window could not be opened!";
        }

        // reject promise
        return new Promise((resolve, reject) => {
            reject(new Error(statustext));
        });
    }


    /**
     * Opens a custom prompt window for saving a recipe.
     * If ok is pressed and name is not empty promise is resolved, if cancel is pressed promise is resolved, otherwise rejected.
     * 
     * @param {*} windowName
     * @param {*} checkResult
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _openCustomCreationWindow(windowName, name, metadata){
        let inputValues = {
            name: "",
            metavalues: []
        };
        let metaValues = {};
        let statustext = "";

        this.windowopened = true;
        const openWindowResult = await this.wmApi.openWindow(windowName).then(result => {
            return true;
        }).catch(error => {
            this._log(error);
            return false;
        });  

        // window opened successfully
        if(openWindowResult){
            const windowObj = this.wmApi.getWindow(windowName);

            // get buttons of window
            const buttonOk = this._getWindowChildRef(windowObj, "ok");
            const buttonCancel = this._getWindowChildRef(windowObj, "cancel");

            // set default values of inputs
            let windowChildren = this._getWindowChildren(windowObj);
            windowChildren.forEach(child => {
                if(child.domRef.localName === "sig-control-input"){
                    const strParts = child.name.split("_");

                    for(let part = 0; part < strParts.length; part++){
                        // search if this is a meta input
                        if(strParts[part] === 'meta' && metadata !== undefined && metadata !== null){
                            const metaName = strParts[strParts.length-1];
                            child.domRef.value = metadata[metaName];

                        // check if it defines the name of the recipe
                        } else if(strParts[part] === 'name'){
                            child.domRef.value = name;
                        }
                    }
                }
            });

            // if window has ok and cancel button
            if(buttonOk && buttonCancel){
                const promiseOk = this.waitForOk(buttonOk);
                const promiseCancel = this.waitForCancel(buttonCancel);
                const windowClosed = this.waitForWindowClosed(windowObj);

                const resolved = await Promise.race([promiseOk, promiseCancel, windowClosed]);

                if(resolved){
                    // get the object array of the children
                    let windowChildren = this._getWindowChildren(windowObj);
                    windowChildren.forEach(child => {
                        if(child.domRef.localName === "sig-control-input"){
                            const strParts = child.name.split("_");
        
                            for(let part = 0; part < strParts.length; part++){
                                // search if input has meta information
                                if(strParts[part] === 'meta'){
                                    const inputName = strParts[strParts.length-1];
                                    metaValues[inputName] = child.domRef.value;
        
                                // check if it defines the name of the recipe
                                } else if(strParts[part] === 'name'){
                                    inputValues.name = child.domRef.value;
                                }
                            }
                        }
                    });
                    inputValues.metavalues.push(metaValues);

                    buttonOk.sigRemoveAllEventListeners();
                    buttonCancel.sigRemoveAllEventListeners();    

                    if(windowObj.isOpen()) this.wmApi.closeWindow(windowName);
                    this.windowopened = false;

                    // if name valid
                    // -> otherwise reject promise
                    if(inputValues.name !== ""){
                        // resolve promise
                        return new Promise((resolve, reject) => {
                            resolve({
                                result: true,
                                data: inputValues
                            });
                        });
                    }else{
                        // set statustext
                        statustext = "Input name is invalid!";
                    }
                }else{
                    buttonOk.sigRemoveAllEventListeners();
                    buttonCancel.sigRemoveAllEventListeners();
    
                    if(windowObj.isOpen()) this.wmApi.closeWindow(windowName);
                    this.windowopened = false;
    
                    // if cancelled 
                    return new Promise((resolve, reject) => {
                        resolve({
                            result: false,
                            data: null
                        });
                    });
                }

            // needed controls not found
            }else{
                if(windowObj.isOpen()) this.wmApi.closeWindow(windowName);
                this.windowopened = false;

                // set statustext
                statustext = "Needed controls not found!";
            }
        }else{
            // set statustext
            statustext = "Window could not be opened!";
        }

        // reject promise
        return new Promise((resolve, reject) => {
            reject(new Error(statustext));
        });
    }


    /** 
     * Opens a custom prompt window and returns the input value.
     *
     * @param {*} windowName
     * @param {*} defaultValue
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _openCustomPromptWindow(windowName, defaultValue){
        let statustext = "";

        this.windowopened = true;
        const openWindowResult = await this.wmApi.openWindow(windowName).then(result => {
            return true;
        }).catch(error => {
            this._log(error);
            return false;
        });  

        // window opened successfully
        if(openWindowResult){
            const windowObj = this.wmApi.getWindow(windowName);

            // get buttons of window
            const buttonOk = this._getWindowChildRef(windowObj, "ok");
            const buttonCancel = this._getWindowChildRef(windowObj, "cancel");

            // get input of window
            const input = this._getWindowChildRef(windowObj, "input");
            // if window has ok and cancel button
            if(buttonOk && buttonCancel && input){
                const promiseOk = this.waitForOk(buttonOk);
                const promiseCancel = this.waitForCancel(buttonCancel);
                const windowClosed = this.waitForWindowClosed(windowObj);

                // set default value
                input.value = defaultValue;

                const resolved = await Promise.race([promiseOk, promiseCancel, windowClosed]);

                buttonOk.sigRemoveAllEventListeners();
                buttonCancel.sigRemoveAllEventListeners();

                if(windowObj.isOpen()) this.wmApi.closeWindow(windowName);
                this.windowopened = false;

                if(resolved){
                    // resolve promise -> ok
                    return new Promise((resolve, reject) => {
                        resolve({
                            result: true,
                            data: input.value
                        });
                    });
                }else{
                    // resolve promise -> cancel
                    return new Promise((resolve, reject) => {
                        resolve({
                            result: false,
                            data: null
                        });
                    });
                }

            // needed controls not found
            }else{
                if(windowObj.isOpen()) this.wmApi.closeWindow(windowName);
                this.windowopened = false;

                // set statustext
                statustext = "Needed controls not found!";
            }
        }else{
            // set statustext
            statustext = "Window could not be opened!";
        }
        // reject promise
        return new Promise((resolve, reject) => {
            reject(new Error(statustext));
        });
    }


    /**
     * Return promise after button pressed.
     *
     * @param {*} buttonOk
     * @returns {*} resolve(true)
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    waitForOk(buttonOk) {
        return new Promise((resolve) => {
          buttonOk.sigAddEventListener('mousedown',function(e) {
              resolve(true);
          });
        });
    }


    /**
     * Return promise after button pressed.
     *
     * @param {*} buttonCancel
     * @returns {*} resolve(false)
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    waitForCancel(buttonCancel) {
        return new Promise((resolve) => {
            buttonCancel.sigAddEventListener('mousedown',function(e) {
              resolve(false);
          });
        });
    }


    /**
     * Return promise after window closed.
     *
     * @param {*} windowObj
     * @returns {*} resolve(false)
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    waitForWindowClosed(windowObj){
        return new Promise((resolve) => {
            let handleClose = (e) => {
                if(e.detail == windowObj.getDomRef().id){
                    this.sigRemoveEventListener('jspanelclosed', handleClose, 'jsEvent', document);
                    resolve(false);
                }
            }            
            this.sigAddEventListener('jspanelclosed', handleClose, 'jsEvent', document);
        });
    }

    /**
     * sets the value of the element
     * the exception is if the prop is set, then the css style is set directly
     *
     * @param {*} element
     * @param {*} [content=null]
     * @param {string} [prop='value']
     */
    _setWindowChildContent(element, content = null, prop = 'value') {
        if((element) && (content !== null) && prop === 'unittext') {
            element.unitText = content;
        } else if ((element) && (content !== null)) {
            element[prop] = content;
        }
    }


    /**
     * Returns the data of the active selected row of the active pane.
     *
     * @private
     * @returns {Object} The data object of the row.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _getActiveRow() {
        let row = null;
        row = this.$.recipeslistgrid.getActiveRow();

        return this._cleanRow(row);
    }


    /**
     * Cleans the given row. If the row data object is of type parent or volume, it will be set to null.
     *
     * @private
     * @param {Object} [rows=null] Array of row data objects which should be cleaned.
     * @returns {Object | null} The given data object or null.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _cleanRow(row = null) {
        let cleanedRow = null;
        if (row instanceof HTMLElement && row.dataset.data) {
            const data = JSON.parse(row.dataset.data);
            if (data.type !== 'parent' && data.type !== 'volume') {
                cleanedRow = row;
            }
        }
        return cleanedRow;
    }


    /**
     * Loads the metadata of the control.
     *
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _loadMetaDataOfControl(path){
        // check if storage is available
        if(this.recipeHandlingStorage !== undefined) 
            await this._checkMetaStorageOfControl(this.recipeHandlingStorage, path);

        // fallback
        // -> load metafiles because storage is not accessible
        else{
            // look for metafile for listview configuration
            this.listviewConfigOfMetafile = await this._readMetafile(path + this.metafileforlistview);

            // metafile of listview invalid 
            if(!this.listviewConfigOfMetafile || this.listviewConfigOfMetafile === null){
                // -> cannot display metadata of recipes 
                // -> return
                return false;
            }

            this.recipesMetaFiles = await this._searchForFile(path, this.metafileforrecipe, -1, false);

            if(!this.recipesMetaFiles && this.recipesMetaFiles !== null){
                for(let i=0; i < this.recipesMetaFiles.length; i++){
                    const pathOfMetafile = this._convertPath(this.recipesMetaFiles[i].toString());
                    const metaValues = await this._readMetafile(pathOfMetafile);
                    const dataObject = this._createObject_metadata(pathOfMetafile, metaValues);
                    this.recipesMetaData.push(dataObject);
                }
            }
        }
    }


    /**
     * Checks the storage for metadata of the control.
     *
     * @param {*} storage The storage type e.g. localStorage or sessionStorage
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _checkMetaStorageOfControl(storage, path){
        if(!path || path === null){
            return false;
        }

        // const activeMetaData = this._getMetaStorageOfControl(storage, path);
        // load always new
        const activeMetaData = null;

        // check if value is valid
        if(activeMetaData !== undefined && activeMetaData !== null){    
            const convertedData = JSON.parse(activeMetaData);

            this.listviewConfigOfMetafile = convertedData.metalistview;
            this.recipesMetaData = convertedData.metarecipes;

            return true;

        }else{
            // read metafile of listview
            this.listviewConfigOfMetafile = await this._readMetafile(path + this.metafileforlistview);

            // metafile of listview invalid 
            if(!this.listviewConfigOfMetafile || this.listviewConfigOfMetafile === null){
                // -> cannot display metadata of recipes 
                // -> return
                return false;
            }

            // read metafiles of recipes
            this.recipesMetaFiles = await this._searchForFile(path, this.metafileforrecipe, -1, false);

            // found metafiles
            if(this.recipesMetaFiles && this.recipesMetaFiles !== null){
                for(let i=0; i < this.recipesMetaFiles.length; i++){
                    const pathOfMetafile = this._convertPath(this.recipesMetaFiles[i].toString());
                    const metaValues = await this._readMetafile(pathOfMetafile);
                    const dataObject = this._createObject_metadata(pathOfMetafile, metaValues);
                    this.recipesMetaData.push(dataObject);
                }
            }

            const metadata = {
                path: path,
                metalistview: this.listviewConfigOfMetafile,
                metarecipes: this.recipesMetaData
            }    

            const res = this._setMetaStorageOfControl(storage, path, metadata);
            return res;
        }
    }

    /**
     * Sets the metadata to the storage.
     *
     * @param {*} storage The storage type e.g. localStorage or sessionStorage
     * @param {*} key
     * @param {*} data
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _setMetaStorageOfControl(storage, key, data){
        // check parameter
        if(!storage || storage === null || 
            !key || key === null){
            return false;
        }
    
        // set key
        const activeMetaStorageKey = "metadata_" + key;

        // convert for storage
        const value = JSON.stringify(data);

        // set item to storage
        const res = this._setStorageItem(storage, activeMetaStorageKey, value);

        return (res === 0) ? true : false;
    }


    /**
     * Gets the metadata from the storage.
     *
     * @param {*} storage The storage type e.g. localStorage or sessionStorage
     * @param {*} key
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _getMetaStorageOfControl(storage, key){
        // check parameter
        if(!storage || storage === null || 
            !key || key === null){
            return null;
        }

        const activeMetaStorageKey = "metadata_" + key;
        const data = this._getStorageItem(storage, activeMetaStorageKey)

        return data;
    }

    /**
     * Update the metadata stored in storage. 
     *
     * @param {*} storage The storage type e.g. localStorage or sessionStorage
     * @param {String} key Which data of the storage should be changed.
     * @param {String} entryKey Which entry of the data should be changed.
     * @param {object} data The new data of the entry.
     * @returns {*} Returns true if successful, false if not.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _updateMetaStorageOfControl(storage, key, entryKey, data){
        // check parameter
        if(!storage || storage === null ||
            !key || key === null ||
                !entryKey || entryKey === null ||
                    !data || data === null ||
                        !data.path || data.path === null || data.path == ""){
            return false;
        }

        // get data
        let currentMeta = JSON.parse(this._getMetaStorageOfControl(storage, key));

        // meta storage invalid or not existing for this directory
        // -> maybe simplemode is active
        if(currentMeta === null){
            return false;
        }

        // find changed entry
        const entryToUpdate = currentMeta.metarecipes.findIndex(elem => elem.path == entryKey);

        // entry not found
        // -> add it 
        if(entryToUpdate < 0){
            // const newMetaEntry = this._createObject_metadata(data.path, data.metadata);

            // add entry to array
            currentMeta.metarecipes.push(data);

        }else{
            // update entry
            currentMeta.metarecipes[entryToUpdate] = data;
        }

        // save updated data to storage
        const res = this._setMetaStorageOfControl(storage, key, currentMeta);
        return res;
    }

    _createObject_metadata(path, metadata){
        const metaConfig = this._getItemConfig();

        // path or ""
        const actPath = (path && path !== null) ? path : "";

        // metadata or itemConfig or {}
        const actMetadata = (metadata && metadata !== null) ? metadata : ((metaConfig !== null) ? metaConfig : {});

        const object =  {
            path: actPath,
            metadata: actMetadata
        }

        return object;
    }


    /**
     * Checks if a recipe name is valid.
     *
     * @param {*} name
     * @returns {*}  {boolean}
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _isRecipeNameValid(name){
        // const isValid = /^[a-z0-9_.@()-]+\.[^.]+$/i.test(name);
        // const isValid = /^[0-9a-zA-Z ... ]+$/i.test(name);
        var rg1=/^[^\\/:\*\?"<>\|]+$/; // forbidden characters \ / : * ? " < > |
        var rg2=/^\./; // cannot start with dot (.)
        var rg3=/^(nul|prn|con|lpt[0-9]|com[0-9])(\.|$)/i; // forbidden file names

        const isValid = rg1.test(name)&&!rg2.test(name)&&!rg3.test(name);
        return isValid; 
    }


    /**
     * Check if name is already used for recipe.
     *
     * @param {string} path Path where the search should start.
     * @param {string} name Name of the file without fileextension.
     * @returns {boolean}  True if available, false if not.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _isRecipeNameAvailable(path, name){
        const fullName = name + this.recipefileextension;
        let maxDepth = -1;

        // search for recipe name
        // -> depending if it is simplemode we set the maximal depth of the search
        // -> 0 means we search in start path, 1 we also "go one folder down"
        maxDepth = (this.usesimplemode) ? 0 : 1; 

        const searchResult = await this._searchForFile(path, fullName, maxDepth, true);

        return (searchResult === null) ? true : false;
    }


    /**
     * Creates a new recipe with folder, file etc.
     * For additional files the function _createRecipe_AddFiles() is executed and can be overwritten.
     *
     * @param {*} name
     * @param {*} path
     * @param {*} simplemode
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _createNewRecipe(name, path, simplemode){
        // create recipe file, folder, metafile and additional files
        if(!simplemode){
            // create folder
            const createRecipeFolderResult = await this._createRecipe_Folder(path, name);

            if(createRecipeFolderResult.status !== undefined){
                return false;
            }

            const dirPath = createRecipeFolderResult.dir;

            // create recipe file
            const createRecipeFileResult = await this._createRecipe_File(dirPath, name, this.recipefileextension);

            if(createRecipeFileResult.status !== undefined){
                return false;
            }

            // create metafile if defined
            if(this.metafileforrecipe !== ""){
                const createRecipeMetafileResult = await this._createRecipe_Metafile(dirPath, this.metafileforrecipe);

                if(createRecipeMetafileResult.status !== undefined){
                    return false;
                }
            }

            // create additional files for recipe structure
            // this function must be overwritten to actually do something
            const createAddFile = await this._createRecipe_AddFiles(dirPath);

            if(createAddFile.status !== undefined){
                return false;
            }

            // this._refreshPane(true);

            return true;

        // only create recipe file
        } else{
            const createRecipeFileResult = await this._createRecipe_File(path, name, this.recipefileextension);

            if(createRecipeFileResult.status !== undefined){
                return false;
            }

            return true;
        }
    }

    /**
     * Creates a new recipe folder.
     *
     * @param {String} path
     * @param {String} name
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _createRecipe_Folder(path, name){
        let actPath = null;

        // if Unix convert path
        if(path.startsWith('/')){
            actPath = this._convertPath(path + name);
        }else{
            actPath = path + name;
        }

        const result = await this.fmApi.makeDir(actPath).catch((error) => {
            return error;
        });

        return result;
    }

    /**
     * Creates a new recipe file.
     *
     * @param {String} path
     * @param {String} name
     * @param {String} fileextension
     * @returns {*}  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _createRecipe_File(path, name, fileextension){
        let actPath = null;

        // if Unix convert path
        if(path.startsWith('/')){
            actPath = this._convertPath(path + "/" + name + fileextension);
        }else{
            actPath = path + "\\" + name + fileextension;
        }

        const blob = new Blob([""], {type : 'text/plain'});
        const result = await this.fmApi.writeFile(actPath, blob).catch((error) => {
            return error;
        });

        return result;
    }

    /**
     * Creates a new metafile for a recipe.
     *
     * @param {String} path
     * @param {String} name
     * @returns {*} 
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _createRecipe_Metafile(path, name){
        let actPath = null;

        // if Unix convert path
        if(path.startsWith('/')){
            actPath = this._convertPath(path + "/" + name);
        }else{
            actPath = path + "\\" + name;
        }

        const blob = new Blob([JSON.stringify({})], {type : 'application/json'});
        const result = await this.fmApi.writeFile(actPath, blob).catch((error) => {
            return error;
        });

        return result;
    }

    /**
     * Creates additional files for a new recipe structure.
     * @param {String} path
     * @returns {*} 
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    async _createRecipe_AddFiles(path){
        // must be overwritten
        let result = {};
        result.status === undefined;
        return result;
    }

    /**
     * Refreshes pane and reloads the current path of the pane.
     *
     * @private
     * @param {boolean} [restorerow=false] If true the row curser will be restored to the current active position if possible.
     * @returns {Undefined} Undefined
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _refreshPane(restorerow = false) {
        if (!this.iscmdactive) {
            this._updatePath(this.activepath, this.oldPath, restorerow);
        }
    }


    /**
     * Updates data of the listview if filter changed.
     *
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _handleFilterUpdate(){
        this._updateData();
    }


    /**
     * Observer of the recipefileextension property.
     * Makes a valid fileextension for the recipe.
     *
     * @param {*} newval
     * @param {*} oldval
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _updateRecipeFileExtension(newval, oldval){
        if(newval && newval !== oldval){
            // if delimiter is not included, add it
            if(newval[0] !== '.'){
                newval = '.' + newval;
            }

            this.recipefileextension = newval;
        }
    }

    /**
     * Observer of the metafileforlistview property.
     * Makes a valid metafilename (name plus fileextension).
     *
     * @param {*} newval
     * @param {*} oldval
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _updateMetafileForListview(newval, oldval){
        if(newval && newval !== oldval){
            // if filename has no valid fileextension
            if(this._getFileExtension(newval) !== this.metafileextension){
                newval = newval + '.' + this.metafileextension;
            }

            this.metafileforlistview = newval;
        }
    }


    /**
     * Observer of the metafileforrecipe property.
     * Makes a valid metafilename (name plus fileextension).
     *
     * @param {*} newval
     * @param {*} oldval
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _updateMetafileForRecipe(newval, oldval){
        if(newval && newval !== oldval){
            // if filename has no valid fileextension
            if(this._getFileExtension(newval) !== this.metafileextension){
                newval = newval + '.' + this.metafileextension;
            }

            this.metafileforrecipe = newval;
        }
    }


    /**
     * Returns the file extension of a given filename.
     * https://stackoverflow.com/questions/190852/how-can-i-get-file-extensions-with-javascript/1203361#1203361
     * 
     * Examples:
     * 
     * ""                            -->   ""
     * "name"                        -->   ""
     * "name.txt"                    -->   "txt"
     * ".htpasswd"                   -->   ""
     * "name.with.many.dots.myext"   -->   "myext"
     * 
     * @param {String} name
     * @returns {String} The fileextension of the file.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _getFileExtension(name){
        return name.slice((name.lastIndexOf(".") - 1 >>> 0) + 2)
    }


    /**
     * Returns the name of a file without file extension.
     *
     * @param {String} fullName
     * @returns {String} The name of the file.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _getFileName(fullName){
        const fileName = fullName.replace(/\.[^/.]+$/, "");
        return fileName;
    }


    /**
     * Compares the values of two objects.
     *
     * @param {Object} obj1
     * @param {Object} obj2
     * @returns {Boolean} Returns true if values are the same, false if not.  
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _shallowEqualCheck(obj1, obj2){
        if(obj1 === null || obj2 === null){
            return false;
        }

        const keys1 = Object.keys(obj1);
        const keys2 = Object.keys(obj2);

        // check if amount of keys is different
        // -> no need to continue check
        if(keys1.length !== keys2.length){
            return false;
        }

        // check if properties have the same values
        for(let key of keys1){
            if(obj1[key] !== obj2[key]) return false;
        }

        return true;
    }


    /**
     * Shows the overlay in the design mode
     *
     * @param {*} data
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _designModeShowOverlay(data) {
        if (this.isdesignmode === true) {
            const _background = this.$.overlayBackground;
            if (data === true) {
                const _wrapper    = this.$.overlayWrapper;
                // const _content    = this.$.longText;
                _wrapper.setAttribute('overlaytype', 'filter');

                // this.$.overlayTitlebar.value = this.detail_overlaytitle;
                this.$.overlayTitlebar.value = "Filter";
                // _content.innerHTML = "Dummy Recipe Filter";

                this.sigSetTimeout (() => {
                    const computedTitleBarStyles = getComputedStyle(this.$.overlayTitlebar);
                    this.overlayCloseBtnSettings.color = computedTitleBarStyles.getPropertyValue('--theme-sig-control-titlebar-color');
                    this.style.setProperty('--theme-sig-control-recipe-handling-overlay-header-text-color', this.overlayCloseBtnSettings.color);
                    // Get the titlebar height
                    this.overlayCloseBtnSettings.height = computedTitleBarStyles.getPropertyValue('height');
                    this.style.setProperty('--theme-sig-control-recipe-handling-overlay-header-height', this.overlayCloseBtnSettings.height);
                });
                _background.classList.add("visible");
            } else {
                _background.classList.remove("visible");
            }
        }
    }


    /**
     * Generates a human readable progress message.
     *
     * @private
     * @param {Number} progress Progress in percentage.
     * @returns {String} The progress message or an empty sting.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _computeProgress(progress) {
        this.$.progressbar.style.width = progress + '%';
        if (progress > 0)
            return progress + '% ' + this.texts.progressCompleted;
        else {
            return '';
        }
    }


    /**
     * This observer toggles the waiting layer, which blocks the recipehandling UI on long running tasks.
     *
     * @private
     * @param {Boolean} newval The new value of the observed polymer property.
     * @param {Boolean} oldval The old value of the observed polymer property.
     * @returns {Undefined} Undefined
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _toggleIsWaiting(newval, oldval) {
        if (newval) {
            this.showiswaiting = true;
            this.iswaitingTimeout = this.sigSetTimeout(() => {
                this.showprogress = true;
            }, this.iswaitingDelay);

        } else {
            if (this.iswaitingTimeout !== null) {
                this.sigClearTimeout(this.iswaitingTimeout);
            }
            this.showprogress = false;
            this.showiswaiting = false;
        }
    }


    /**
     * This observer toggles if the inputs are enabled or disabled.
     *
     * @param {Boolean} newval The new value of th observed polymer property.
     * @param {Boolean} oldval The old value of th observed polymer property.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _disableInputs(newval, oldval){
        if(newval){
            this.savebtndisabled = true;
            this.loadbtndisabled = true;
            this.copybtndisabled = true;
            this.renamebtndisabled = true;
            this.deletebtndisabled = true;

        }else{
            this.savebtndisabled = false;
            this.loadbtndisabled = false;
            this.copybtndisabled = false;
            this.renamebtndisabled = false;
            this.deletebtndisabled = false;
        }
    }


    /**
     * This observer updates the state of the progresswindowset variable.
     *
     * @private
     * @param {Object} newval The new value of th observed polymer property.
     * @param {Object} oldval The old value of th observed polymer property.
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _checkCustomProgressWindow(newval, oldval){
        if(this.wmApi.getWindow(newval) !== null){
            this.progresswindowset = true;
        }else{
            this.progresswindowset = false;
        }
    }


    /**
    * This observer opens the custom progress window, if defined and deactivates the keyboard support of the listview if the progress dialog is opened.
    *
    * @private
    * @param {Boolean} newval The new value of the observer polymer property.
    * @param {Boolean} oldval The old value of the observer polymer property.
    * @returns {Undefined} Undefined
    * @memberof LasalRuntimeSigControlRecipeHandling
    */
   _toggleShowProgress(newval, oldval) {
        // check if custom window is set 
        if(Object.entries(this.progresswindow).length !== 0){
            if (newval) {
                const windowObj = this.wmApi.getWindow(this.progresswindow);
                this.wmApi.openWindow(this.progresswindow);

                this.progressWindowContent = { 
                    barprogress: this._getWindowChildRef(windowObj, "progress"),
                    currentaction: this._getWindowChildRef(windowObj, "action"),
                };     

                if(this.progressWindowContent.barProgress){ 
                    this._setWindowChildContent(this.progressWindowContent.barprogress, this.progress); 
                    this._setWindowChildContent(this.progressWindowContent.barprogress, '%', 'unittext'); 
                }

                if(this.progressWindowContent.currentaction){ this._setWindowChildContent(this.progressWindowContent.currentaction, this.currentaction, 'sigtext'); }

            } else {
                this.wmApi.closeWindow(this.progresswindow);
            }
        }

        if (!this.disablekeyactions) {
            if (newval) {
                this._toggleDisableKeyMovement(true);
            } else {
                this._toggleDisableKeyMovement(false);
            }
        }
    }


    /**
     * Updates the values and states inside the custom progress window.
     *
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _updateCustomProgressWnd(){
        this.debouncerProgress = SigPolymer.debouncer.debounce(
            this.debouncerProgress,
            SigPolymer.timeOut.after(50),
            () => {
                if(this.progresswindowset && this.showprogress){   

                    // update progress bar
                    if(this.progressWindowContent.barprogress){ 
                        this._setWindowChildContent(this.progressWindowContent.barprogress, this.progress); 
                        this._setWindowChildContent(this.progressWindowContent.barprogress, '%', 'unittext'); 
                    }
        
                    // update current action
                    if(this.progressWindowContent.currentaction){ this._setWindowChildContent(this.progressWindowContent.currentaction, this.currentaction, 'sigtext'); }
                }
            }   
        );
    }


    /**
     * This observer toggles the preview of the progress dialog in designmode. 
     *
     * @private
     * @param {Boolean} newval The new value of the observer polymer property.
     * @param {Boolean} oldval The old value of the observer polymer property.
     * @returns {Undefined} Undefined
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _designtimeshowprogress(newval, oldval) {
        if (this.isdesignmode) {
            if (newval) {
                this.showprogress = true;
                this.iswaiting = true;
                // this.queuestats.count = 50;
                this.progress = 50;
                this.currentaction = "Current action..."
            } else {
                this.showprogress = false;
                this.iswaiting = false
                // this.queuestats.count = 50;
                this.progress = 0;
                this.currentaction = null;
            }
        }
    }


    /**
     * This event handler handles the 'WS_STATE_CHG' runtime event.
     * If the connection is lost, the UI of the control is blocked.
     * After the connection is established again, the UI is released.
     *
     * @private
     * @param {Number} majorId The major id of the runtime event.
     * @param {Number} minorId The minor id of the runtime event.
     * @param {Object} msg The payload of the runtime event.
     * @returns {Undefined} Undefined
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _onWSStateChg(majorId, minorId, msg) {
        switch (msg.state) {
            case this.constApi.STAT_WS_OPEN:
                this.isonline = true;
                this.iswaiting = false;
                break;
            default:
                this.isonline = false;
                this.iswaiting = true;
                break;
        }
    }


    /**
     * Applies the font objects stored in the font object properties fontObjHeader, fontObjBody and fontObjTitle.
     * @private
     * @returns {Undefined} Undefined
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _applFontObjects() {
        this.fontDebouncer = SigPolymer.debouncer.debounce(
            this.fontDebouncer,
            SigPolymer.timeOut.after(50),
            () => {
                this._applyFontObjectToNode(this.$["overlayContent"], this.overlaycontentfont);

                const _listView = this.$.recipeslistgrid;
                const _titleBar = this.$.recipestitle;

                if (this.fontObjHeader) _listView.fontobjectheader = this.fontObjHeader;
                else _listView.fontobjectheader = "{}";
                if (this.fontObjBody) _listView.fontobjectbody = this.fontObjBody;
                else _listView.fontobjectbody = "{}";
                if (this.fontObjTitle) _titleBar.fontobjecttitle = this.fontObjTitle;
                else _titleBar.fontobjecttitle = "{}";
            }
        )
        return;
    }


    /**
     * Handles double click on row element.
     * Can be overwritten. 
     *
     * @param {*} rowData
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _handleDblClick(rowData){
        return null;
    }


    /**
     * Handles longpress on row element.
     * Can be overwritten. 
     *
     * @param {*} rowData
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _handleLongPress(rowData){
        return null;
    }


    /**
     * Handles enter on row elenment.
     * Can be overwritten. 
     * 
     * @memberof LasalRuntimeSigControlRecipeHandling
     */
    _handleEnter(){
        return null;
    }


    /**
    * Handles the 'evtHandlerReloadPane' event defined in the JSON description of the component
    * and executes it, if the runtime fires the defined event.
    *
    * @returns {Undefined} Undefined
    * @memberof LasalRuntimeSigControlRecipeHandling
    */
    evtHandlerReloadPane() {
        this._refreshPane();
    }


    /**
    * Handles the 'evtHandlerLoadRecipe' event defined in the JSON description of the component
    * and executes it, if the runtime fires the defined event.
    *
    * @returns {Undefined} Undefined
    * @memberof LasalRuntimeSigControlRecipeHandling
    */
    evtHandlerLoadRecipe() {
        this._loadSequence();
    }


    /**
    * Handles the 'evtHandlerSaveRecipe' event defined in the JSON description of the component
    * and executes it, if the runtime fires the defined event.
    *
    * @returns {Undefined} Undefined
    * @memberof LasalRuntimeSigControlRecipeHandling
    */
    evtHandlerSaveRecipe() {
        this._saveSequence();
    }

    /**
    * Handles the 'evtHandlerCopyRecipe' event defined in the JSON description of the component
    * and executes it, if the runtime fires the defined event.
    *
    * @returns {Undefined} Undefined
    * @memberof LasalRuntimeSigControlRecipeHandling
    */
    evtHandlerCopyRecipe() {
        this._copySequence();
        }


    /**
    * Handles the 'evtHandlerRenameRecipe' event defined in the JSON description of the component
    * and executes it, if the runtime fires the defined event.
    *
    * @returns {Undefined} Undefined
    * @memberof LasalRuntimeSigControlRecipeHandling
    */
    evtHandlerRenameRecipe() {
        this._renameSequence();
    }


    /**
    * Handles the 'evtHandlerDeleteRecipe' event defined in the JSON description of the component
    * and executes it, if the runtime fires the defined event.
    *
    * @returns {Undefined} Undefined
    * @memberof LasalRuntimeSigControlRecipeHandling
    */
    evtHandlerDeleteRecipe() {
        this._deleteSequence();
    }


    /**
    * Handles the 'evtHandlerMoveCursorDown' event defined in the JSON description of the component
    * and executes it, if the runtime fires the defined event.
    *
    * @returns {Undefined} Undefined
    * @memberof LasalRuntimeSigControlRecipeHandling
    */
    evtHandlerMoveCursorDown() {
        this.$.recipeslistgrid.moveCursorDown();
    }


    /**
    * Handles the 'evtHandlerMoveCursorUp' event defined in the JSON description of the component
    * and executes it, if the runtime fires the defined event.
    *
    * @returns {Undefined} Undefined
    * @memberof LasalRuntimeSigControlRecipeHandling
    */
    evtHandlerMoveCursorUp() {
        this.$.recipeslistgrid.moveCursorUp();
    }


    /**
    * Handles the 'evtHandlerMoveCursorTop' event defined in the JSON description of the component
    * and executes it, if the runtime fires the defined event.
    *
    * @returns {Undefined} Undefined
    * @memberof LasalRuntimeSigControlRecipeHandling
    */
    evtHandlerMoveCursorTop() {
        this.$.recipeslistgrid.moveCursorTop();
    }


    /**
    * Handles the 'evtHandlerMoveCursorEnd' event defined in the JSON description of the component
    * and executes it, if the runtime fires the defined event.
    *
    * @returns {Undefined} Undefined
    * @memberof LasalRuntimeSigControlRecipeHandling
    */
    evtHandlerMoveCursorEnd() {
        this.$.recipeslistgrid.moveCursorBottom();
    }


    /**
    * Handles the 'evtHandlerPageUp' event defined in the JSON description of the component
    * and executes it, if the runtime fires the defined event.
    *
    * @returns {Undefined} Undefined
    * @memberof LasalRuntimeSigControlRecipeHandling
    */
    evtHandlerPageUp() {
        this.$.recipeslistgrid.moveCursorPageUp();
    }


    /**
    * Handles the 'evtHandlerPageDown' event defined in the JSON description of the component
    * and executes it, if the runtime fires the defined event.
    *
    * @returns {Undefined} Undefined
    * @memberof LasalRuntimeSigControlRecipeHandling
    */
    evtHandlerPageDown() {
        this.$.recipeslistgrid.moveCursorPageDown();
    }

    /****************************************************************************************************
     * Reads a blob and returns a string
     * 
     * @param {Blob} data - the data of a read recipe file
     * @return {String} - The parsed blob in string form
     ****************************************************************************************************/
    _readFileAsText(data) {
        return new Promise((resolve, reject) => {
            const fileReader = new FileReader();
            fileReader.onerror = () => {
                fileReader.abort();
                reject(new Error('Error occured reading file'));
            }

            fileReader.onload = () => {
                resolve(fileReader.result);
            }
            fileReader.readAsText(data);
        })
    }

    /****************************************************************************************************
     * Gets the CRC from the header of a recipe and checks it with the user CRC. Returns the CRC to use for the load call.
     * @param {String} filePath - path name where to find the recipe
     * @param {Number} usercrcparameter - default crc to return if the user CRC doesn't match the CRC in the file
     ****************************************************************************************************/
     async _getCRCToLoadRecipe(filePath, usercrcparameter) {
        try {

            //Read the file
            const fileData = await this.fmApi.fileContent(filePath);

            //Get the file content as string
            const fileContent = await this._readFileAsText(fileData.data);

            if ((fileContent) && (fileContent.length > 0) && (typeof fileContent === 'string')) {
                //Spliting the file content up into 3 elements: Headertext before the CRC value, CRC value and the rest of the file
                //First group (Headertext before CRC):  everything until "crc = 16#"
                //Second group (CRC value):             CRC value (non-whitespace characters until the next ";")
                //Third group (rest):                   everything after the second group
                const fileElements = fileContent.match(/([\s\S]*crc = 16#)([\S]*)(;[\s\S]*)/m);
                
                if ((fileElements) && (fileElements.length >= 2)) {
                    //remember the header text in case the loaded recipe should be saved again before applying the values to the system (save manipulated recipe)
                    const crcValueString = fileElements[2];
                    const crcValue = parseInt('0x' + crcValueString,16);

                    // the >>> 0 will actually do nothing but it is used to get an unsigned integer
                    // bit operations in js are always 32 bit therefore it makes sure the result is between 0 and 0xFFFFFFFF
                    this.userdefinedcrc = this.userdefinedcrc >>> 0;  

                    if (crcValue === this.userdefinedcrc) {
                        return crcValue;
                    }                     
                }
                else {
                    this._log('_getCRCToLoadRecipe(): failed to match crc', 'ERROR');
                }
            }
            else {
                this._log('_getCRCToLoadRecipe(): failed to read file as text', 'ERROR');
            }
            return usercrcparameter;
        } catch (e) {
            this._log(`_getCRCToLoadRecipe(): Error getting the recipe CRC: ${e.message}`, 'ERROR');
            return usercrcparameter;
        }
    }
}
customElements.define(LasalRuntimeSigControlRecipeHandling.is, LasalRuntimeSigControlRecipeHandling);