HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
File: D:/HostingSpaces/SBogers10/douven.komma.pro/wwwroot/js/kms/kms.js
'use strict';
//inject directives and services.


var app = angular.module('fileUpload', ['ngFileUpload']);

app.controller('fileUploadController', ['$scope', 'Upload', '$timeout', function ($scope, Upload, $timeout) {
    console.log('fileuploadcontroller loading');

    //The files list
    //Todo, extend with the files form the database
    $scope.files = [];
    //These are the fileIds, used for saving

    $scope.dynamic = false;

    $scope.uploadSizes = '';

    $scope.maxImages;

    $scope.errorMsg = {};

    $scope.subFolder = false;

    $scope.attributeKey = '';

    $scope.forModel;
    $scope.forModelId;

    $scope.canUploadNewImage;

    /**
     * This function uploads the new files
     * @param files
     * @param errFiles
     * @param maximages
     * @param blockId
     * @param tabId optional
     */
    $scope.uploadFiles = function (files, errFiles, maximages, blockId, tabId) {
        console.log("upload for block with blockId: "+blockId+" and tabKey: "+tabId);
        console.log("uploadedFiles files:");
        console.log(files);

        //Get the model and the id where we are uploading images for. Should flow via a service but due to bad design and an upcoming newer angular version does not. todo: improve me
        var htmlElement = document.querySelector("[ng-controller=KmsAttributeDynamicController]");
        $scope.forModel = htmlElement.dataset.forModel;
        $scope.forModelId = htmlElement.dataset.forModelId;
        console.log("fileUploadController uploading files For model: "+$scope.forModel+" with id: "+$scope.forModelId+" and blockId: "+blockId);


        if (typeof $scope.files == 'undefined') $scope.files = [];
        if ($scope.files.length >= maximages) {
            console.log("File upload not allowed. "+$scope.files.length+"/"+maximages+" uploaded");
            return false;
        }
        //Set the files
        $scope.errFiles = errFiles;

        //Foreach each file
        angular.forEach(files, function (file) {

            //Push to true adds image at the back, false at the front
            var push = true;

            //Add the placeholder
            var loadingImage = {loading: true, thumb_image_url: '/images/kms/loading.gif'};

            if (push == true) {
                $scope.files.push(loadingImage);
                var imageKey = $scope.files.length - 1
            } else {
                //Add at the front
                $scope.files.unshift(loadingImage);
                var imageKey = 0
            }

            //Upload to the server
            file.upload = Upload.upload({
                url: '/kms/upload',
                data: {
                    //Sent the data and the options
                    file: file,
                    uploadSizes: $scope.uploadSizes,
                    dynamic: $scope.dynamic,
                    subFolder: $scope.subFolder,
                    attribute_key: $scope.attributeKey,
                    forModelName: $scope.forModel,
                    forModelId: $scope.forModelId,
                    forBlockId: blockId,
                    forTabId: tabId
                }
            });
            //After uploading
            file.upload.then(function (response) {
                $timeout(function () {
                    // delete $scope.files.loading
                    console.log(response);

                    //Set the response data

                    // file.result = response.data;
                    // file.result = response.data.uploaded[0];

                    // console.log("Thumb: "+response.data.uploaded.[0].thumb);
                    console.log("Image Id: "+response.data.uploaded[0].id);

                    //Add the file to the current files
                    $scope.files[imageKey] = response.data.uploaded[0];

                    console.log("$scope.files["+ imageKey +"] =");
                    console.log($scope.files[imageKey]);

                    //Reverse push
                    //Add the file to the current files
                    // $scope.files.unshift(file.result);
                    // $scope.setFileIds()

                    //Update fileids
                    var forBlockId = response.data.uploaded[0].forBlockId;

                    var blocksDataObject = $scope.getBlocksData(); //Defined in the KmsAttributeDynamicController

                    for(var key in blocksDataObject)
                    {
                        console.log("Looping over blocks to find a block with id: "+forBlockId+". Current block id: "+blocksDataObject[key].blockId);
                        // console.log(blocksDataObject[key]);

                        if(blocksDataObject.hasOwnProperty(key) === false || blocksDataObject[key].hasOwnProperty("blockId") === false) {
                            console.warn("Detected block data without a block id. Because of this, it is not possible to upload a file for that block. This does not mean that the uploaded image can be linked to the block your uploading currently for");
                            continue;
                        }

                        if(blocksDataObject[key].blockId == forBlockId) {
                            console.log("found block with id: "+forBlockId);

                            var data = [];
                            if(response.data.uploaded[0].forTabId) {
                                console.log("Uploaded an image for tab: "+response.data.uploaded[0].forTabId);
                                data = response.data.uploaded[0].id+"#"+response.data.uploaded[0].forTabId; //imageid#multipleimagesslot
                            } else {
                                console.log("Uploaded an image without tab: "+response.data.uploaded[0].id);
                                data = response.data.uploaded[0].id;
                            }

                            var fileIds = [];
                            if(blocksDataObject[key].hasOwnProperty('fileIds')) {
                                var length = blocksDataObject[key]['fileIds'].length;

                                //Determine if it is a single image or multiple images.
                                blocksDataObject[key]['fileIds'][length] = data;
                                console.log("Updating fileIds with image id: "+data);
                            } else {
                                console.log("Setting fileIds to a new array with image id: "+data);
                                blocksDataObject[key].fileIds = [data];
                            }

                            console.log("files.length = "+blocksDataObject[key].fileIds.length+" maxImages = "+$scope.maxImages);
                            $scope.canUploadNewImage = blocksDataObject[key].fileIds.length < $scope.maxImages;
                        }

                        $scope.setBlocksData(blocksDataObject);
                    }
                });
            }, function (response) {
                if (response.status > 0)
                    $scope.errorMsg = response.status + ': ' + response.data;
                    console.error("Error: "+$scope.errorMsg);
            }, function (evt) {
                file.progress = Math.min(100, parseInt(100.0 *
                    evt.loaded / evt.total));
            });
        });

    };

    $scope.deleteImage = function (index, blockId, tab_key) {
        var idToDelete = $scope.files[index].id;
        console.log("idToDelete: "+idToDelete+" from blockId: "+blockId);

        //Delete the image
        $scope.files.splice(index, 1);
        console.log("Current image amount: "+$scope.files.length);

        //Delete the image id
        // $scope.setFileIds()

        var blocksDataObject = $scope.getBlocksData(); //Defined in the KmsAttributeDynamicController

        for(var blockIndex in blocksDataObject)
        {
            if(blocksDataObject.hasOwnProperty(blockIndex) === false || blocksDataObject[blockIndex].hasOwnProperty("blockId") === false) {
                console.warn("Detected block data without a block id. Because of this, it is not possible to upload a file for that block. This does not mean that the uploaded image can be linked to the block your uploading currently for");
                continue;
            }

            if(blocksDataObject[blockIndex].blockId == blockId) {
                console.log("found block with id: " + blockId);

                if(blocksDataObject[blockIndex].hasOwnProperty('fileIdsToDelete') === false) blocksDataObject[blockIndex].fileIdsToDelete = [];

                console.log("Current files to delete: "+blocksDataObject[blockIndex].fileIdsToDelete);
                idToDelete = (typeof tab_key !== 'undefined') ? idToDelete+"#"+tab_key : idToDelete ;

                for(var index in blocksDataObject[blockIndex].fileIds)
                {
                    var currentId = blocksDataObject[blockIndex].fileIds[index];
                    if(typeof tab_key !== 'undefined') {
                        console.log("Looping over the file ids to find an image with id: "+idToDelete+" for tab: "+tab_key+" ("+currentId+")");
                    } else {
                        console.log("Looping over the file ids to find an image with id: "+idToDelete);
                    }

                    if(currentId == idToDelete) {
                        console.log("Image marked to delete on save: "+idToDelete);
                        blocksDataObject[blockIndex].fileIdsToDelete[blocksDataObject[blockIndex].fileIdsToDelete.length] = idToDelete;

                        blocksDataObject[blockIndex].fileIds.splice(index, 1);

                        if(typeof tab_key === 'undefined') {
                            $scope.canUploadNewImage = blocksDataObject[blockIndex].fileIds.length < $scope.maxImages;
                        } else {
                            var count = 0;
                            for(var index in blocksDataObject[blockIndex].fileIds) {
                                if(blocksDataObject[blockIndex].fileIds[index].split("#")[1] == tab_key) {
                                    count++;
                                }
                            }

                            $scope.canUploadNewImage = count < $scope.maxImages;
                        }
                        break;
                    }
                }
            }
        }

        $scope.setBlocksData(blocksDataObject);

        delete $scope.errorMsg.toMany;
    };

    // $scope.moveImage = function (index, direction) {
    //     var currentFile = $scope.files[index]
    //     var currentFileId = $scope.fileIds[index]
    //
    //     if (direction == 'left') {
    //         $scope.files[index] = $scope.files[index - 1];
    //         $scope.files[index - 1] = currentFile;
    //     } else {
    //         $scope.files[index] = $scope.files[index + 1];
    //         $scope.files[index + 1] = currentFile;
    //     }
    //     $scope.setFileIds()
    // };

    $scope.initFiles = function (files, maxImages, tab_key) {
        console.log("fileupload:initFiles: "+files+" maxImages: "+maxImages);

        if(files) {
            // //Strip out the image ids that do not belong to the tab we are processing for
            var filesToUse = [];
            if(typeof tab_key !== 'undefined') {
                for(var index in files) {
                    var split = files[index].split("#");
                    if(split.length !== 2) continue; //The imageId did not contain a #

                    if(split[1] != tab_key) {
                        console.log("fileupload:initFiles: Ignoring image with id: "+split[0]+" because it belongs to tab "+split[1]+" and we are processing images for tab "+tab_key);
                    } else {
                        filesToUse[filesToUse.length] = files[index];
                    }
                }
            } else {
                filesToUse = files;
            }

            if(filesToUse.length > 0) {
                $.ajax({
                    type: "POST",
                    url: "/kms/getimagedata",
                    data: {
                        fileIds: filesToUse
                    },
                    success: function (response) {
                        console.log("Retrieved files: ");
                        console.log(response);

                        // files must look like this files[index].id files[index].sizes
                        $scope.files = response;

                        // $scope.setFileIds();
                        if (typeof maxImages != 'undefined') {
                            $scope.maxImages = maxImages
                        }

                        console.log("files.length < $scope.maxImages: " + filesToUse.length < $scope.maxImages);
                        $scope.canUploadNewImage = filesToUse.length < $scope.maxImages;

                        $scope.$apply();
                    },
                    error: function () {
                        console.log("An error occured while retrieving image(s)")
                    },
                    headers: {
                        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                    },
                    dataType: 'json'
                });
            } else {
                $scope.maxImages = maxImages;
                $scope.canUploadNewImage = true;
                $scope.files = [];
            }
        } else {
            $scope.maxImages = maxImages;
            $scope.canUploadNewImage = true;
            $scope.files = [];
        }
    };

    $scope.updateFiles = function(files) {
        console.log("Updated files");
        $scope.files = files;
    };


    // $scope.setFileIds = function () {
    //     var local_fileIds = [];
    //
    //     angular.forEach($scope.files, function (file) {
    //         local_fileIds.push(file.id);
    //     });
    //
    //     console.log("set file ids ($scope.fileIds):");
    //     console.log(local_fileIds);
    //     $scope.fileIds = local_fileIds
    // };


    // $scope.getFileCount = function () {
    //     return $scope.fileIds.length;
    // };

    $scope.sortableOptions = {
        update: function (e, ui) {
            ui.item.sortable.cancel();
            var originalIndex = ui.item.sortable.index;
            var newIndex = ui.item.sortable.dropindex

            var oldImage = $scope.files[originalIndex]
            $scope.files[originalIndex] = $scope.files[newIndex]
            $scope.files[newIndex] = oldImage
            $scope.setFileIds()

        }

    };

    console.log('fileuploadcontroller initialized');
}]);
'use strict';

angular.element(document).ready(function () {
    // angular.element(document.querySelector('[ng-controller]')).scope().debug();
    // //console.log('page loading completed');
});


angular.module('kms.dynamic-attribute', [])
    .controller('KmsAttributeDynamicController', function ($scope, $element, $compile, $timeout) {
        //console.log('KmsAttributeDynamicController initializing');

        // var htmlElement = document.querySelector("[ng-controller=KmsAttributeDynamicController]");
        //console.log("Controller element");
        //console.log($($element)[0]);
        var htmlElement = $($element)[0];


        //For which model and id of the model the dynamic attribute works
        $scope.forModel = htmlElement.dataset.forModel;
        $scope.forModelId = htmlElement.dataset.forModelId;
        //console.log("KmsAttributeDynamicController further initializing For model: "+$scope.forModel+" with id: "+$scope.forModelId);

        $scope.key = htmlElement.dataset.key;
        //console.log("Controller key: "+$scope.key);

        var removeBlocks = function () {
            // Remove the block list for re-render
            angular.element('#' + $scope.key + '_blocks .inner').remove();
        };

        // Check if any blocks exists to prevent JSON.parse error
        // Default is an empty array
        $scope.blocks = [];


        var html = $('#' + $scope.key + '_data', $element).html();
        //console.log("JSON to parse (in element: #"+$scope.key+"_data): "+html);
        if (html != '' && html != '""' && html != undefined) {
            $scope.blocks = JSON.parse(html);
        } else {
            //console.log("Element called: " + "#" + $scope.key + "_data does not have json data set");
        }

        //console.log('scope blocks');
        //console.log($scope.blocks);

        $scope.blockSettings = {};
        var blockSettingsElement = document.getElementById('blockSettings');
        if (blockSettingsElement) {
            var block_settings = blockSettingsElement.value;
            if (block_settings != '' && block_settings != '""') {
                $scope.blockSettings = JSON.parse(block_settings);
            }
        } else {
            //console.log("No blocksettings element");
        }

        //console.log("blocksettings");
        //console.log($scope.blockSettings);

        $scope.blockTypes = getBlockTypes($scope.blockSettings);

        $scope.addBlock = function (blockType) {
            if (!angular.isUndefined(blockType)) {
                $scope.blocks.push(angular.copy(blockType));
            }

        };

        $scope.removeBlock = function (block) {
            startToMove();
            var index = $scope.blocks.indexOf(block);
            $scope.blocks.splice(index, 1);
            stopToMove()
        };

        $scope.upBlock = function (block) {
            //load index from the block
            var index = $scope.blocks.indexOf(block);
            //if the index is 0 return
            if (index < 1) return;

            //Remove the editor
            startToMove()

            //Set the block index-1 to current index
            $scope.blocks[index] = $scope.blocks[index - 1];
            //set the triggerd block to index-1
            $scope.blocks[index - 1] = block;


            //enable the editor
            stopToMove()

        };

        $scope.downBlock = function (block) {
            //load the index from the block
            var index = $scope.blocks.indexOf(block);
            //If the block is the last return false
            if (index >= $scope.blocks.length - 1) return;

            //Remove the editor
            startToMove()


            //set the block +1 to the current index
            $scope.blocks[index] = $scope.blocks[index + 1];
            //set the triggerd block to index +1
            $scope.blocks[index + 1] = block;


            //enable the editor
            stopToMove()
        };

        $scope.deleteImage = function (direction, block_key, image_key, tab_key) {
            if (tab_key == null) {
                $scope.blocks[block_key].images.splice(image_key, 1)
            } else {
                $scope.blocks[block_key].tab[tab_key].images.splice(image_key, 1)
            }
        }

        $scope.deleteFile = function (block_key) {
            delete $scope.blocks[block_key].file
        }


        $scope.changeOrder = function (direction, block_key, image_key, tab_key) {
            //load the index from the block
            var index = image_key
            if (tab_key == null) {

                var temp_image = $scope.blocks[block_key].images[image_key];

                switch (direction) {
                    case 'left':
                        if (index == 0) return;
                        $scope.blocks[block_key].images[image_key] = $scope.blocks[block_key].images[image_key - 1];
                        $scope.blocks[block_key].images[image_key - 1] = temp_image;
                        break;
                    case 'right' :
                        if (index + 1 == $scope.blocks[block_key].images.length) return;
                        $scope.blocks[block_key].images[image_key] = $scope.blocks[block_key].images[image_key + 1];
                        $scope.blocks[block_key].images[image_key + 1] = temp_image;
                        break;
                }
            } else {

                var temp_image = $scope.blocks[block_key].tab[tab_key].images[image_key];

                switch (direction) {
                    case 'left':
                        if (index == 0) return;
                        $scope.blocks[block_key].tab[tab_key].images[image_key] = $scope.blocks[block_key].tab[tab_key].images[image_key - 1];
                        $scope.blocks[block_key].tab[tab_key].images[image_key - 1] = temp_image
                        ;
                        break;
                    case 'right' :
                        if (index + 1 == $scope.blocks[block_key].tab[tab_key].images.length) return;
                        $scope.blocks[block_key].tab[tab_key].images[image_key] = $scope.blocks[block_key].tab[tab_key].images[image_key + 1];
                        $scope.blocks[block_key].tab[tab_key].images[image_key + 1] = temp_image;
                        break;
                }

            }
        }

        /**
         * Add new image tab
         * @param block_index
         */
        $scope.addImageTab = function (block_index) {
            $scope.blocks[block_index].tab.push({title: ''})

            var new_index = $scope.blocks[block_index].tab.length

            //set the new tab as active (-1) arrays start at 0
            $scope.setActiveTab(block_index, new_index - 1)
        };

        /**
         * Remove image tab
         * @param block_index
         */
        $scope.removeImageTab = function (block_index, tab_index) {
            $scope.blocks[block_index].tab.splice(tab_index, 1);

            //set the active tab to 0
            $scope.setActiveTab(block_index, 0)
        };

        $scope.setActiveTab = function (index, tab) {
            $scope.blocks[index].active_tab = tab;
        };
        $scope.moveTab = function (direction, index, tab_index) {

            var temptab = $scope.blocks[index].tab[tab_index]
            switch (direction) {
                case 'left':
                    if (tab_index == 0) return;
                    $scope.blocks[index].tab[tab_index] = $scope.blocks[index].tab[tab_index - 1]
                    $scope.blocks[index].tab[tab_index - 1] = temptab
                    break;
                case 'right':
                    if (tab_index + 1 == $scope.blocks[index].tab.length) return;
                    $scope.blocks[index].tab[tab_index] = $scope.blocks[index].tab[tab_index + 1]
                    $scope.blocks[index].tab[tab_index + 1] = temptab
                    break;
            }
        };


        $scope.setUniqueId = function (block_index) {
            if (typeof $scope.blocks[block_index].blockId == 'undefined') {
                var stamp = new Date().valueOf();
                $scope.blocks[block_index].blockId = Math.round(stamp + Math.random() * 1000);
            }
        };

        $scope.debug = function () {
            //console.log('key: ' + $scope.key);
        };

        //console.log('KmsAttributeDynamicController initialized');
    })

    .directive('block', function ($compile) {
        return {
            restrict: 'E',
            template: '<div class="header">' +
            '<div class="start">' +
            '<div class="block-title">{{block.typeName}}</div>' +
            '<div class="buttons">' +
            '<div class="position-button up" ng-click="upBlock(block)"></div>' +
            '<div class="position-button down" ng-click="downBlock(block)"></div>' +
            '</div></div>' +
            '<div class="end">' +
            '<div class="btn btn-danger btn-trash" ng-click="removeBlock(block)">&nbsp</div>' +
            '</div></div>',

            link: function (scope, element, attr) {
                switch (scope.block.typeSlug) {
                    case 'content-block':
                        // //console.log('rendering a content block with this data: '+scope.block);
                        scope.imageBoxId = scope.key + '-image-box-' + scope.$index;
                        scope.textBoxId = scope.key + '-text-box-' + scope.$index;
                        //console.log("Rendering content-block (afbeelding en tekst)");
                        //console.log(scope.block);
                        element.append($compile("<content-block></content-block>")(scope));
                        break;
                    case 'view-block':
                        element.append($compile("<view-block></view-block>")(scope));
                        break;
                    case 'page-link-block':
                        element.append($compile("<page-link-block></page-link-block>")(scope));
                        break;
                    case 'image-slider-block':
                        element.append($compile("<image-slider-block></image-slider-block>")(scope));
                        break;
                    case 'video-block':
                        element.append($compile("<video-block></video-block>")(scope));
                        break;
                    case 'file-block':
                        element.append($compile("<file-block></file-block>")(scope));
                        break;
                    case  'two-column-block':
                        scope.textBoxIds = [];
                        scope.textBoxIds.left = scope.key + '-text-box-' + scope.$index + '-left';
                        scope.textBoxIds.right = scope.key + '-text-box-' + scope.$index + '-right';
                        element.append($compile("<two-column-block></two-column-block>")(scope));
                        break;
                    case 'full-text-block':
                        scope.textBoxId = scope.key + '-text-box-' + scope.$index;
                        element.append($compile("<full-text-block></full-text-block>")(scope));
                        break;
                    case 'full-image-block':
                        element.append($compile("<full-image-block></full-image-block>")(scope));
                        break;
                    case 'multiple-images-block':
                        element.append($compile("<multiple-images-block></multiple-images-block>")(scope));
                        break;
                }


                // //console.log("block data element: "+attr.dataElement);
                scope.dataElement = document.querySelector(attr.dataElement);
                scope.getBlocksData = function() {
                    return JSON.parse(scope.dataElement.getAttribute("value"));
                };

                scope.setBlocksData = function(data) {
                    scope.dataElement.setAttribute("value", JSON.stringify(data));
                }
            }
        }
    })


    .directive('contentBlock', function ($timeout) {
        var count = 0;
        return {
            restrict: 'E',
            templateUrl: '/kms/api/template/dynamic-page-content-block',
            link: function (scope, element, attrs) {
                scope.image = 'imageHolder';
                scope.loopCount = count;
                count++;
                scope.loopCount = scope.key;
                // //console.log("getting content block data");
                // //console.log(scope.getBlocksData();

                //initiate the tinyMce editor
                initiateTinyMceWithDelay(scope, $timeout, 100);

                scope.openBox = function (imageBoxId) {
                    $.colorbox({
                        href: '/kms/elfinder/standalonepopup/' + imageBoxId,
                        fastIframe: true,
                        iframe: true,
                        width: '70%',
                        height: '520px'
                    });
                };
            }
        };
    })

    .directive('viewBlock', function ($timeout) {
        var count = 0;
        return {
            restrict: 'E',
            templateUrl: '/kms/api/template/dynamic-view',
            link: function (scope) {

            }
        };
    })

    .directive('twoColumnBlock', function ($timeout) {
        var count = 0;
        return {
            restrict: 'E',
            templateUrl: '/kms/api/template/two-column-block',
            link: function (scope, element, attrs) {
                scope.image = 'imageHolder',
                    scope.loopCount = count
                count++;
                scope.loopCount = scope.key

                //initiate the tinyMce editor
                initiateTinyMceWithDelay(scope, $timeout, 100)

                scope.openBox = function (imageBoxId) {
                    $.colorbox({
                        href: '/kms/elfinder/standalonepopup/' + imageBoxId,
                        fastIframe: true,
                        iframe: true,
                        width: '70%',
                        height: '520px'
                    });
                };
            }
        };
    })

    .directive('imageSliderBlock', function () {

        return {
            restrict: 'E',
            templateUrl: '/kms/api/template/dynamic-image-slider-block',
            link: function (node) {
                ////console.log(node);
            }
        }

    })

    .directive('fullImageBlock', function () {

        return {
            restrict: 'E',
            templateUrl: '/kms/api/template/dynamic-full-image-block',
            link: function (node) {
                ////console.log(node);
            }
        }
    })

    .directive('fullTextBlock', function ($timeout) {

        return {
            restrict: 'E',
            templateUrl: '/kms/api/template/dynamic-full-text-block',
            link: function (scope) {

                //initiate the tinyMce editor
                initiateTinyMceWithDelay(scope, $timeout, 100)
            }
        }
    })

    .directive('multipleImagesBlock', function () {
        return {
            restrict: 'E',
            templateUrl: '/kms/api/template/dynamic-multiple-images-block',
            link: function (node) {
                ////console.log(node);
            }
        }

    })

    .directive('pageLinkBlock', function ($http) {
        return {
            restrict: 'E',
            templateUrl: '/kms/api/template/dynamic-page-link-block',
            link: function (scope, element, attr) {
                scope.pages = [];
                $http.get('/kms/api/pages').then(function (response) {
                    // Adopt data
                    var pages = [];
                    var getChildren = function (node) {
                        for (var child in node) {
                            if (node[child].title) {
                                pages.push({
                                    value: node[child].id,
                                    label: node[child].title
                                });
                            }
                            getChildren(node[child].children);
                        }
                    };
                    getChildren(response.data);
                    // Set the data
                    scope.pages = pages;
                });
            }
        };
    })
    .directive('videoBlock', function () {
        return {
            restrict: 'E',
            templateUrl: '/kms/api/template/dynamic-video-block',
            link: function (node) {
                ////console.log(node);
            }
        }

    })
    .directive('fileBlock', function () {
        return {
            restrict: 'E',
            templateUrl: '/kms/api/template/dynamic-file-block',
            link: function (node) {
                ////console.log(node);
            }
        }

    });


/**
 * Callback function for ElFinder (hard to implement in angular?)
 *
 * @param filePath
 * @param requestingElementId
 */
function processSelectedFile(filePath, requestingElementId) {
    // Get the scope of the the element that is passed into ElFinder
    var elementScope = $('#' + requestingElementId).parent().parent().scope();
    elementScope.block.image = filePath;
    elementScope.$apply();
}

/**
 * This function will initiate the tinyMce editor on an block
 * It the initiation will be delayed because of the angular
 *
 * @param $scope
 * @param $timeout
 */

function initiateTinyMceWithDelay($scope, $timeout, delay) {
    //if (tinymce) tinymce.remove();
    $timeout(function () {
        initiateTinyMce($scope, $scope.textBoxId)
        if (typeof $scope.textBoxIds !== 'undefined') {
            if ($scope.textBoxIds.left != null) initiateTinyMce($scope, $scope.textBoxIds.left, 'description', 'left')
            if ($scope.textBoxIds.right != null) initiateTinyMce($scope, $scope.textBoxIds.right, 'description', 'right')
        }

    }, delay);


}

function initiateTinyMce($scope, field, blockElement, blockSubElement) {
    if (typeof field === 'undefined') {
        field = $scope.textboxId;
    }
    if (typeof blockElement === 'undefined') {
        blockElement = 'description';
    }

    //textboxIds[] =
    tinymce.init({
        elements: field,
        mode: 'exact',
        skin: 'kms',
        menubar: false,
        statusbar: false,
        plugins: ['code', 'paste', 'link', 'table'],
        toolbar: 'styleselect | bold italic underline | bullist numlist table | link | code',
        height: '200',
        default_link_target: "_blank",
        paste_as_text: true,
        style_formats: [{title: 'Titel', block: 'h2'}],
        link_list: "/kms/file-list?key=komma_kms",
        setup: function (editor) {
            editor.on('change', function (e) {
                editor.save();
                if (typeof blockSubElement === 'undefined') {
                    $scope.block[blockElement] = editor.getContent();
                } else {
                    $scope.block[blockElement][blockSubElement] = editor.getContent();
                }


                if (!$scope.$$phase) {
                    $scope.$apply();
                }
            });
            // Update model on keypress
            editor.on('KeyUp', function (e) {
                editor.save();
                if (typeof blockSubElement === 'undefined') {
                    $scope.block[blockElement] = editor.getContent();
                } else {
                    $scope.block[blockElement][blockSubElement] = editor.getContent();
                }

                if (!$scope.$$phase) {
                    $scope.$apply();
                }
            });
        }
    });
}

function startToMove() {
    $(document).find('textarea').each(function () {
        tinyMCE.execCommand('mceFocus', false, $(this).attr('id'));
        tinyMCE.execCommand('mceRemoveEditor', false, $(this).attr('id'));
    });
}

function stopToMove() {
    //todo: Reanbling ONLY works after a timeout.
    setTimeout(function () {
        $(document).find('textarea').each(function () {
            tinymce.execCommand('mceAddEditor', true, $(this).attr('id'));
        });
    }, 100);
}

function getBlockTypes(blockSettings) {

    var blocks = {
        two_column_block: {
            typeSlug: 'two-column-block',
            code_name: '',
            view: '',
            typeName: 'Twee tekstkolommen',
            status: true,
            description: {left: '', right: ''},
            textWidth: '50',
        },
        content_block: {
            typeSlug: 'content-block',
            code_name: '',
            view: '',
            typeName: 'Afbeelding en tekst',
            status: true,
            location: 'left',
            textWidth: '66',
            description: '',
            subFolder: 'dynamic',
            image: null,
            link: '',
            link_text: 'Lees meer',
        },
        view_block: {
            typeSlug: 'view-block',
            code_name: '',
            view: '',
            typeName: 'View',
            status: true,
        },
        full_image_block: {
            typeSlug: 'full-image-block',
            code_name: '',
            view: '',
            typeName: '100% Afbeelding',
            subFolder: 'dynamic',
            max_images: 1,
            status: true,
            image: null
        },
        full_text_block: {
            typeSlug: 'full-text-block',
            code_name: '',
            view: '',
            typeName: 'Een tekstkolom',
            description: '',
            status: true,
            link: '',
            link_text: 'Lees meer'
        },
        multiple_images_block: {
            typeSlug: 'multiple-images-block',
            code_name: '',
            view: '',
            typeName: 'Meerdere afbeeldingen',
            tab: [
                {
                    title: 'Afbeelding 1', max_images: 1}, {
                    title: 'Afbeelding 2a',
                    max_images: 2
                },
                {
                    title: 'Afbeelding 2b (optioneel)',
                    max_images: 1}
            ],
            status: true,
            location: 'left',
            subFolder: 'dynamic',
            textWidth: '66',
        },
        page_link_block: {
            typeSlug: 'page-link-block',
            code_name: '',
            view: '',
            typeName: 'Page-link block',
            link_text: 'Lees meer',
            special: 0,
            status: true,
            pageId: null
        },
        image_slider_block: {
            typeSlug: 'image-slider-block',
            code_name: '',
            view: '',
            typeName: 'Image gallery',
            tab: [{title: ''}],
            subFolder: 'dynamic',
            status: true,
            pageId: null
        },
        video_block: {
            typeSlug: 'video-block',
            code_name: '',
            view: '',
            typeName: 'Video',
            youtube: '',
            status: true,
            pageId: null
        },
        file_block: {
            typeSlug: 'file-block',
            code_name: '',
            view: '',
            typeName: 'Bestand block',
            title: '',
            special: 0,
            download_text: 'Download',
            file: '',
            status: true,
            pageId: null
        }
    };

    var blockTypes = {};

    angular.forEach(blockSettings, function (block, blockName) {
        blockTypes[blockName] = angular.merge(blocks[blockName], block);
    });

    return blockTypes;
}

'use strict';
var attributes = {};

var app = angular.module('kms', [
    'ui.bootstrap',
    'ngSanitize',
    'kms.dynamic-attribute',
    'fileUpload'

    // 'ui.select',
    // 'ui.date',
    // 'ui.tree',
    // 'ui.sortable',

    // 'ui.utils.masks',

]);

app.controller('KmsEntities',[ '$scope', '$element',function ($scope, $element) {

    $scope.entities = [];
    $scope.paginatedEntities = [];

    $scope.isSorting = false;

    $scope.isSearching = function () {
        return $scope.searchEntitiesText.text.length > 1;
    };

    $scope.currentPage = 1;
    $scope.numPerPage = 5;
    $scope.maxSize = 5;

    var entityList = $('.entities-list-items .entities-list-item', $element);
    for (var i = 0; i < entityList.length; i++) {
        var dataElement = entityList[i];
        var dataObject = {
            thumbHtml: $('.entities-item-pre', dataElement).html(),
            text: $('.entities-item-text', dataElement).html(),
            status: $('.entities-item-status', dataElement).html(),
            link: $('a', dataElement).first().attr('href'),
            active: $(dataElement).hasClass('active')
        };
        $scope.entities.push(dataObject);
    }

    $scope.activateSorting = function () {
        $scope.isSorting = true;
    };

    $scope.deactivateSorting = function () {
        $scope.isSorting = false;
    };

    $scope.$watch("currentPage + numPerPage", function () {
        var begin = (($scope.currentPage - 1) * $scope.numPerPage)
            , end = begin + $scope.numPerPage;

        $scope.paginatedEntities = $scope.entities.slice(begin, end);
    });



}]);

app.controller('KmsEntity',[ '$scope', '$element',function ($scope, $element) {
    // console.log("attaching save to scope");
    $scope.save = function () {
        var button = document.querySelector('input[value="submit-cloaked"]');
        // console.log(button);
        button.click();
    };
}]);

app.controller('SortableTree',['$scope', '$http', '$attrs' ,function ($scope, $http, $attrs) {

    $http.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";

    $scope.data = [];

    $scope.treeOptions = {
        dropped: function (event) {
            $http({
                method: 'POST',
                url: '/kms/api/' + $attrs.siteslug + '/' + $attrs.slug,
                data: {"tree": angular.toJson($scope.data)}
            }).then(function (response) {
            });
        }
    };

    $scope.openLink = function (id) {
        window.location = $scope.url + '/' + id;
    };

    $scope.toggle = function (scope) {
        scope.toggle();
    };

    var getRootNodesScope = function () {
        return angular.element(document.getElementById('tree-root').scope());
    };

    $scope.collapseAll = function () {
        var scope = getRootNodesScope();
        scope.collapseAll();
    };

    $scope.expandAll = function () {
        var scope = getRootNodesScope();
        scope.expandAll();
    };

    // console.log('/kms/api/' + $attrs.siteslug + '/' + $attrs.slug);
    $http.get('/kms/api/' + $attrs.siteslug + '/' + $attrs.slug)
        .then(function (response) {
            // console.log("Kms.js SortableTree controller got data from '"+'/kms/api/' + $attrs.siteslug + '/' + $attrs.slug+"':");
            // console.log(angular.toJson(response.data));
            $scope.data = response.data[0]['children'];
        });
}]);

app.controller('KmsAttributes',['$scope', function ($scope) {
    $scope.attributes = attributes;

    // console.log("$scope.attributes (via KmsAttributes)");
    // console.log($scope.attributes);

    // $scope.createModelString = function (modelKey, $itemKey) {
    //
    //
    //     //Check if the modelKey is an array structure (field_name[x])
    //     var match = modelKey.match(/(.*)\[(.*)]/)
    //
    //     //There is no match, so a basic key
    //     if (match == null) {
    //         //SEt the modelKey as modelString
    //         $scope.modelString = modelKey
    //         //ItemKy is null
    //         $scope.itemKey = null;
    //         //return and exit
    //         return $scope.modelString
    //     }
    //
    //     //Set the match[2] as the itemKey
    //     var itemCounter = match[2];
    //
    //     //If the itemKey is not undefined and itemkey is the string itemKey
    //     if (typeof($itemKey) != "undefined" && match[2] == 'itemKey') {
    //         //Set the $itemKey to the scope itemKey
    //         itemCounter = $itemKey
    //     }
    //     //Glue the parts together
    //     var modelString = match[1] + '_' + itemCounter
    //
    //     //Set to the global modelString
    //     $scope.modelString = modelString;
    //     $scope.itemCounter = itemCounter;
    //
    //     //Return the string
    //     return modelString
    // }

    /**
     * Wrapper for Json.parse()
     * So i can parse Json in blade
     * @param value
     */
    $scope.parse = function (value) {
        //If there is no value return;
        if (!value) {
            console.log("Parse without a value");
            return;
        }
        //If it is already an object, return the value
        if (typeof(value) == 'object') {
            console.log("Parse value: "+value);
            return value;
        }

        //Parse the string to a Json object
        console.log("Parse string to Json: "+JSON.parse(value));
        return JSON.parse(value);
    };

    $scope.convertToSlug = function (str) {
        str = str.replace(/^\s+|\s+$/g, ''); // trim
        str = str.toLowerCase();

        // remove accents, swap ñ for n, etc
        var from = "ãàáäâẽèéëêìíïîõòóöôùúüûñç·/_,:;";
        var to = "aaaaaeeeeeiiiiooooouuuunc------";
        for (var i = 0, l = from.length; i < l; i++) {
            str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
        }

        str = str.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
            .replace(/\s+/g, '-') // collapse whitespace and replace by -
            .replace(/-+/g, '-'); // collapse dashes

        return str;
    };
}]);
app.controller('KmsAttributeTextFieldController',['$scope', '$element' ,function ($scope, $element) {

    $scope.attributes = attributes;
}]);

app.controller('KmsAttributePasswordController',['$scope', '$element' ,function ($scope, $element) {
    $scope.attributes = attributes;

    //Set enabler true (show enable bottom)
    $scope.enabler = true
    //Change the password field to a hidden field
    $('.passwordField', $element).attr('type', 'hidden')

    //Method called when clikced on the enable button
    $scope.enablePasswordField = function () {
        //Change the type of the password field to password
        $('.passwordField', $element).attr('type', 'password')
        //Disable the enable buttom
        $scope.enabler = false
    }
}]);


app.controller('KmsAttributeDateController',['$scope', '$element', function ($scope, $element) {
    $scope.attributes = attributes;

    $('input[ui-date]', $element).datepicker({
        changeYear: true,
        changeMonth: true,
        firstDay: 1,
        dateFormat: 'dd-mm-yy',
    });

}]);

app.controller('KmsAttributeCurrencyFieldController',['$scope', '$element','$locale', function ($scope, $element, $locale) {
    $scope.attributes = attributes;


    $scope.initCurrencyField = function (dec_value, currency) {

        if (typeof(currency) !== 'undefined') {
            //Set currencySymbol
            $locale.NUMBER_FORMATS.CURRENCY_SYM = currency;
        }

        //Set the value an de dec_value
        attributes[$scope.modelString] = {"value": dec_value / 100, "dec_value": dec_value}

        $scope.$watch('attributes.' + $scope.modelString + '.value', function (newValue, oldValue) {
            if (newValue === oldValue) return;

            attributes[$scope.modelString].dec_value = newValue * 100

        });
    }


}]);

app.controller('KmsAttributePercentageFieldController',['$scope', '$element','$timeout', function ($scope, $element, $timeout) {
    $scope.attributes = attributes;

}]);

app.controller('KmsAttributeTextFieldCurrencyController',['$scope', '$element','$timeout', function ($scope, $element, $timeout) {

    $scope.attributes = attributes;
    var id = $('input', $element).attr('id'); // get the id of the hidden field

    var taxSelectFieldId = $('[data-kms-tax-field]', $element).attr('data-kms-tax-field');
    var taxFieldId = id + '_tax';
    var noTaxFieldId = id + '_no_tax';
    var parseCurrencyStringToCents = function (currencyString) {

        var val = currencyString.toString();
        val = val.replace(/[.]/g, '');
        val = val.replace(/[,]/g, '.');
        val = val.replace(/[^0-9\.]+/g, '');
        val = val * 100;
        return val;
    };

    var addTax = function (value) {
        if (attributes[taxSelectFieldId].selected) {
            var factor = 1 + (attributes[taxSelectFieldId].selected.fullValue.rate / 100);
            return value * factor;
        }
    };
    var subtractTax = function (value) {
        if (attributes[taxSelectFieldId].selected) {
            var factor = 1 + (attributes[taxSelectFieldId].selected.fullValue.rate / 100);
            return value / factor;
        }
    };
    $scope.$watch('attributes.' + taxFieldId, function (newValue, oldValue) {
        if (newValue !== oldValue) {
            $scope.attributes[id] = parseCurrencyStringToCents(newValue);
        }
    });

    $scope.$watch('attributes.' + noTaxFieldId, function (newValue, oldValue) {
        if (newValue !== oldValue) {
            $scope.attributes[id] = Math.round(addTax(parseCurrencyStringToCents(newValue)));
        }
    });

    var watcherMain = function (newValue, oldVal) {
        if ($.isNumeric($scope.attributes[id])) {
            var $taxField = $('#' + taxFieldId);
            var $noTaxField = $('#' + noTaxFieldId);
            if (!$taxField.is(':focus')) {
                $taxField.val($scope.attributes[id] / 100)
            }
            if (!$noTaxField.is(':focus')) {
                $noTaxField.val(Math.round(subtractTax($scope.attributes[id])) / 100)
            }
        }
    };

    // Clear fields on blur if no value
    $('#' + taxFieldId + ', #' + noTaxFieldId).blur(function (e) {
        if ($scope.attributes[id] == 0) {
            $('#' + taxFieldId + ', #' + noTaxFieldId).val(0);
        }
    });

    $scope.$watch('attributes.' + id, watcherMain);
    $scope.$watch('attributes.' + taxSelectFieldId + '.selected', watcherMain);
    $scope.attributes[id] = $('input', $element).attr('value'); // get the value of the hidden field
    $timeout(watcherMain, 500);
}]);


app.controller('KmsAttributeMultiSelectController',['$scope', '$element', function ($scope, $element) {

    $scope.attributes = attributes;
    $scope.selectData = [];

    /**
     * Initialize the choices for ui-select
     *
     * @param choices | string
     * @returns {*}
     */
    $scope.initChoices = function (choices) {
        //If there are no choices;
        if (!choices) return;

        //If it is not already an object, make one from json
        if (typeof(choices) != 'object') choices = JSON.parse(choices)

        //Set the choices to the selectData
        $scope.selectData = choices
        return choices
    };

    $scope.setValue = function (values) {

        if (!values) return;
        // compare "values" array and "$scope.selectData" array
        var result = [];
        for (var i = 0; i < $scope.selectData.length; i++) {
            var valueInElement = false;
            for (var j = 0; j < values.length; j++) {
                if ($scope.selectData[i].value == values[j]) {
                    valueInElement = true;
                    break;
                }
            }
            if (valueInElement) result.push($scope.selectData[i]);
        }

        // $scope.attributes[id] = angular.toJson(values);
        // $scope.attributes[id + '_select'] = result;
        return result;
    };


}]);

app.controller('KmsAttributeSlugController',['$scope', '$element', function ($scope, $element) {
    $scope.attributes = attributes

    //Get the slugField key
    var slugField = $('[data-kms-slug-field]', $element).attr('data-kms-slug-field');
    //Change [[itemCounter]] with the $scope.itemCounter (fieldGroup)
    slugField = slugField.replace('[[itemCounter]]', $scope.itemCounter);

    //Watch the slugField
    $scope.$watch('attributes.' + slugField, function (newValue, oldValue) {
        //When nothing changes, do nothing
        if (newValue === oldValue) return;
        //Set the string converted to Slug to the field
        attributes[$scope.modelString] = $scope.convertToSlug(attributes[slugField]);
    });

}]);


app.controller('KmsAttributeFieldGroupController', ['$scope', '$element',function ($scope, $element) {
    $scope.number = 0;
    $scope.itemKey = 1;
    $scope.maxFieldGroups = null;

    $scope.getNumber = function () {
        return new Array($scope.number);
    }

    $scope.addRow = function () {

        if ($scope.itemKey >= $scope.maxFieldGroups) return
        //Add one to the itemKey
        $scope.itemKey++;
        //Addd one to the numbers
        $scope.number++;

    }
    /**
     * Initialize the given values
     * @param itemKey | Amount (-1) of  items fieldGroups
     * @param maxFieldGroups | max fieldgroups
     */
    $scope.init = function (itemKey, maxFieldGroups) {

        //Items in the fieldGroup
        $scope.itemKey = itemKey
        //Max items in fieldGroup
        $scope.maxFieldGroups = maxFieldGroups
        //If it is a new, add a row
        if (itemKey == 0) $scope.addRow();
    }
}]);

app.controller('KmsAttributeRouteController',['$scope', '$element', function ($scope, $element) {

    $scope.attributes = attributes;

    var id = $('input', $element).attr('id');
    attributes[id] = $('input', $element).attr('value');

    //Get the name of the slugField
    var slugField = $('[data-kms-slug-field]', $element).attr('data-kms-slug-field');
    //Change [[itemCounter]] with the $scope.itemCounter (fieldGroup)
    slugField = slugField.replace('[[itemCounter]]', $scope.itemCounter)
    //Get the name of the parentField
    var parentField = $('[data-kms-parent-field]', $element).attr('data-kms-parent-field');

    //Set the languageId
    var languageId = $('[data-kms-language-id]', $element).attr('data-kms-language-id');
    //Set the structure
    var structure = $('[data-kms-structure]', $element).attr('data-kms-structure');


    /**
     * This function generates the correct Route
     * when a watched field is changed
     *
     * @param newValue
     * @param oldValue
     */
    var parseStructure = function (newValue, oldValue) {
        //If the value is the same, stop and return
        if (newValue === oldValue) return;

        //Create an empty parentSlug string
        var parentSlug = '';

        // todo: make this work
        //Check if the parentField exist in then attributes
        if (attributes[parentField]) {
            //Check if the parentField has an selected.routes object
            if (!attributes[parentField].selected.routes) {
                //If not trow an error
                console.error('Routes are not set in the ' + parentField + 'field')
            }
            //Set the routes for the current language to parentSlug
            parentSlug = attributes[parentField].selected.routes[languageId];
        }

        //Create newRoute
        var newRoute = '';

        //Replace {{parentSlugs}} with the parentSlug
        newRoute = structure.replace('{{parentSlugs}}', parentSlug);
        //Replace {{slug}} with the Slug
        newRoute = newRoute.replace('{{slug}}', $scope.convertToSlug(newValue));

        //Set the new route to the attribute of the current $scope.modelString
        attributes[$scope.modelString] = newRoute

    };

    //Set a watch on the slugField, and execute the parseStructure method on a change
    $scope.$watch('attributes.' + slugField, parseStructure);
    //If there is an parentField
    if (attributes[parentField]) {
        //Set a watch on the parentField, and execute the parseStructure method on a change
        $scope.$watch('attributes.' + parentField + '.selected', parseStructure);

    }


}]);


app.controller('KmsAttributeSorterController',['$scope', '$element', function ($scope, $element) {
    $scope.attributes = attributes;

}]);

app.filter("multiWordFilter", ['$filter',  function ($filter) {
    console.log('hoi');
    return function (inputArray, searchText) {
        var wordArray = searchText ? searchText.toLowerCase().split(/\s+/) : [];
        var wordCount = wordArray.length;
        for (var i = 0; i < wordCount; i++) {
            // Hack for filtering on text parameter: {text:wordArray[i]}
            inputArray = $filter('filter')(inputArray, {text: wordArray[i]});
        }
        return inputArray;
    }
}]);

app.filter('valuesToArrayFilter', function () {
    return function (input) {

        if (!input) return;

        var output = input.map(function (a) {
            return a.value;
        });

        return output


    }
});


/**
 * AngularJS default filter with the following expression:
 * "person in people | filter: {name: $select.search, age: $select.search}"
 * performs a AND between 'name: $select.search' and 'age: $select.search'.
 * We want to perform a OR.
 */
app.filter('propsFilter', function () {
    return function (items, props) {
        var out = [];

        if (angular.isArray(items)) {
            items.forEach(function (item) {
                var itemMatches = false;

                var keys = Object.keys(props);
                for (var i = 0; i < keys.length; i++) {
                    var prop = keys[i];
                    var text = props[prop].toLowerCase();
                    if (item[prop].toString().toLowerCase().indexOf(text) !== -1) {
                        itemMatches = true;
                        break;
                    }
                }

                if (itemMatches) {
                    out.push(item);
                }
            });
        } else {
            // Let the output be the input untouched
            out = items;
        }
        return out;
    };
});


var IsJsonString = function (str) {
    try {
        JSON.parse(str);
    } catch (e) {
        return false;
    }
    return true;
};


$(function(){

    var activeListItem = $('#sidebar .navigation li.active');
    var activeListItemParents = activeListItem.parents();
    var activeListItemParentsLength = activeListItemParents.length;

    for(var i = 0; i <= activeListItemParentsLength; i++){
        var node = activeListItemParents[i];
        if(node && node.tagName == 'LI' && node.classList.contains('has-sub-items')){
            node.classList.add('active');
        }
    }

    var siteListItems = document.querySelectorAll('#sidebar .navigation .has-sub-items');
    var siteListItemsLength = siteListItems.length;
    for(var i = 0; i < siteListItemsLength; i++){
        var siteListItem = siteListItems[i];

        siteListItem.addEventListener('click', function () {
           this.classList.toggle('active');
        });
    }

});
(function ($) {

    // Save button
    // $('#save-button').click(function (e) {
    //     $('#entity-form').find('input, textarea').attr('disabled', false);
    //     $('#entity-form').submit();
    // });

    // let saveButton = document.querySelector('#save-button');
    // if(saveButton)
    // {
    //     saveButton.addEventListener('click', function() {
    //         if(!saveButton.classList.contains('disabled')) {
    //             let entityForm = document.querySelector('#entity-form');
    //             if(entityForm) {
    //                 let inputsAndTextAreas = entityForm.querySelector('input, textarea');
    //                 inputsAndTextAreas.forEach(function(element) {
    //                    element.setActive('disabled', false);
    //                 });
    //             } else {
    //                 console.error('There is not #entity-form element present that should contain the form that this button tries to save');
    //             }
    //         } else {
    //             console.log('not saving because the save button is disabled')
    //         }
    //     })
    // }


    // TinyMCE
    tinymce.init({
        selector: 'textarea.tiny-mce',
        skin: 'kms',
        menubar: false,
        statusbar: false,
        plugins: ['code', 'paste', 'link'],
        toolbar: 'styleselect | bold italic underline | bullist numlist | link image | code',
        height: '200',
        paste_as_text: true,
        style_formats: [{title: 'Titel', block: 'h2'}]
    });

    // Hierarchical list
    $('.entities-list-item .dropdown-icon').click(function (e) {
        e.preventDefault();
        if ($(this).parent().parent().hasClass('open')) {
            //$('.animate-to-triangle', this)[0].beginElement();
            $(this).parent().parent().removeClass('open');
        } else {
            //$('.animate-to-minus', this)[0].beginElement();
            $(this).parent().parent().addClass('open');
        }
    });

    var toggled = true;

    // Scroll to active item in list
    $(window).load(function () {


        if ($('#entity-form .lock').hasClass('open')) {
            toggled = false;
            $('#entity-form').find('input, textarea').attr('disabled', toggled);
        }


        var $container = $('#entities .entities-list');
        var $activeListItem = $('.entities-list-item.active', $container).first();
        if ($activeListItem.length == 0) return;
        var top = $activeListItem.position().top;
        $container.scrollTop(top);


        $('.site-brand-name input').attr('placeholder', $('#global_name').val())

    });

    $('#global_name').change(function () {
        $('.site-brand-name input').attr('placeholder', $(this).val())
    });


    //error accordian
    $('.error-accordion .collapsible-ul').hide();
    $('.error-accordion h3').click(function () {
        $(this).parent().find('.collapsible-ul').toggle();
    });

    //$('.order-status.selectize').selectize();


    // Product category selector

    //$('#productCategorySelector').selectize();

    //$('#selectYearAndMonthForm select').selectize();

    $('#selectYearAndMonthForm select').change(function () {
        var location = '/kms/orders/voltooid?month=' + $('#selectYearAndMonthForm select#orderMonthSelector').val() + '&year=' + $('#selectYearAndMonthForm select#orderYearSelector').val();
        window.location = location;
    })

    $('#productCategorySelector').change(function () {
        window.location = '/kms/products?category=' + encodeURIComponent(this.value);
    });

    // Flash messages
    var hideFlashMessage = function () {
        $('#flash-message').fadeOut();
    };
    $('#flash-message').click(function () {
        hideFlashMessage();
    });
    setTimeout(function () {
        hideFlashMessage();
    }, 5000);


    //var toggled = true;
    $('#entity-form .lock').click(function () {
        $(this).toggleClass('open')
        toggled = !toggled;
        $(this).parents('#entity-form').find('input, textarea').attr('disabled', toggled);

    })



    /* grab important elements */
    var sortInput = jQuery('#sort_order');
    var submit = jQuery('#autoSubmit');
    var messageBox = jQuery('#message-box');
    var list = jQuery('.fieldGroupItems');
    /* create requesting function to avoid duplicate code */

    /* worker function */
    var fnSubmit = function(save) {
        var sortOrder = [];
        list.children('li').each(function(){
            sortOrder.push(jQuery(this).data('id'));
        });
        sortInput.val(sortOrder.join(','));
        console.log(sortInput.val());
        if(save) {
            request();
        }
    };
    /* store values */
    list.children('li').each(function() {
        var li = jQuery(this);
        li.data('id',li.attr('title')).attr('title','');
    });
    /* sortables */
    list.sortable({
        opacity: 0.7,
        update: function() {

        }
    });

    /* ajax form submission */
    jQuery('#dd-form').bind('submit',function(e) {
        if(e) e.preventDefault();
        fnSubmit(true);
    });


})(jQuery);

function toggleOnOff($id) {
    let onOffBox = document.getElementById($id);
    let onOffSwitch = document.getElementById($id + '-switch');

    if(onOffBox.checked){
        onOffBox.value = '1';
        onOffSwitch.classList.add('on');
    }
    else{
        onOffBox.value = '0';
        onOffSwitch.classList.remove('on');
    }
}

function toggleOnOffSwitch($id) {
    let onOffBox = document.getElementById($id);
    onOffBox.checked = (onOffBox.checked) ? onOffBox.checked = false : onOffBox.checked = true;
    toggleOnOff($id);
}
class PasswordController {
    /**
     * Validates password fields.
     * The password fields values wil be imploded with a pipe symbol
     *
     * @param firstPasswordInputSelector The css selector for selecting the first password input field
     * @param secondPasswordInputSelector The css selector for selecting the second password input field
     * @param wrapperSelector The css selector for the wrapper div / element that wraps the first and second input fields along the validation helper
     * @param passwordDontMatchErrorText string
     * @param saveButtonId The id of a save button that needs to be disabled when the passwords are not valid
     * @param minPasswordLength int
     * @param wrapperHasTitleAttributeAndErrorClass Must be set to true if the wrapper (selected with the wrapper selector) has the ability to have an error class and title attribute for displaying an error
     */
    constructor(firstPasswordInputSelector, secondPasswordInputSelector, wrapperSelector, passwordDontMatchErrorText, saveButtonId, minPasswordLength = 6, wrapperHasTitleAttributeAndErrorClass = false) {
        this.wrapperHasTitleAttributeAndErrorClass = wrapperHasTitleAttributeAndErrorClass;
        this.wrapper = document.querySelector(wrapperSelector);
        this.saveButton = document.querySelector("#"+saveButtonId);
        console.log(this.saveButton);

        this.firstPasswordInput = this.wrapper.querySelector(firstPasswordInputSelector);
        this.secondPasswordInput = this.wrapper.querySelector(secondPasswordInputSelector);

        this.validationMessageWrapperSelector = '.validationHelper';
        this.validationMessageWrapper = this.wrapper.querySelector(this.validationMessageWrapperSelector);

        this.minPasswordLength = minPasswordLength;

        //Used for validation
        this.letter = this.validationMessageWrapper.querySelector(".letter");
        this.capital = this.validationMessageWrapper.querySelector(".capital"); 
        this.number = this.validationMessageWrapper.querySelector(".number");
        this.length = this.validationMessageWrapper.querySelector(".length");
        this.match = this.validationMessageWrapper.querySelector(".match");
        // this.noPipe = document.querySelector(this.validationMessageWrapperSelector + " .noPipe");

        this.activateListeners(true);
    }

    /**
     * Activates listening for keyup events on the password fields to that the passwordChanged method is triggered
     *
     * @param state
     */
    activateListeners(state) {
        let self = this;
        let validationHelper = this.validationMessageWrapper;

        if (state) {
            this.firstPasswordInput.addEventListener('keyup',
                self.debounce(function () {
                    self.passwordChanged()
                }, 100)
            );

            this.secondPasswordInput.addEventListener('keyup',
                self.debounce(function () {
                    self.passwordChanged()
                }, 100)
            );

            this.firstPasswordInput.addEventListener('focus', function () {
                if (!validationHelper.classList.contains('active')) validationHelper.classList.add('active')
            });
            this.secondPasswordInput.addEventListener('focus', function () {
                if (!validationHelper.classList.contains('active')) validationHelper.classList.add('active')
            });

            this.firstPasswordInput.addEventListener('blur', function () {
                if (validationHelper.classList.contains('active')) validationHelper.classList.remove('active')
            });
            this.secondPasswordInput.addEventListener('blur', function () {
                if (validationHelper.classList.contains('active')) validationHelper.classList.remove('active')
            });
        } else {
            this.firstPasswordInput.removeEventListener('keyup', self.debounce);
            this.secondPasswordInput.removeEventListener('keyup', self.debounce);

            this.firstPasswordInput.removeEventListener('focus');
            this.secondPasswordInput.removeEventListener('focus');

            this.firstPasswordInput.removeEventListener('blur');
            this.secondPasswordInput.removeEventListener('blur');
        }
    }

    /**
     * Triggered when one of the password fields are changed and if the listeners for those fields are active
     */
    passwordChanged() {
        console.log('password changed');

        let value1 = this.firstPasswordInput.value;
        let value2 = this.secondPasswordInput.value;

        let valid = (this.validate(value1, value2));
        console.log(valid);

        this.enableValidMessage(valid);
        this.enableSaveButton(valid);
        this.removeWrapperError();
    }

    enableValidMessage(enable)
    {
        let validationHelper = this.validationMessageWrapper;

        if(enable) {
            if(!validationHelper.classList.contains('valid')) validationHelper.classList.add('valid');
        } else {
            if(validationHelper.classList.contains('valid')) validationHelper.classList.remove('valid');
        }
    }

    enableSaveButton(enable)
    {
        console.log(enable);
        if(enable) {
            if(this.saveButton.classList.contains('disabled')) this.saveButton.classList.remove('disabled');
        } else {
            if(!this.saveButton.classList.contains('disabled')) this.saveButton.classList.add('disabled');
        }
    }

    /**
     * Removes the error that may be set on the wrapper
     */
    removeWrapperError()
    {
        if(!this.wrapperHasTitleAttributeAndErrorClass) return;

        if(this.wrapper.hasAttribute('title')) this.wrapper.setAttribute('title', '');
        if(this.wrapper.classList.contains('error')) this.wrapper.classList.remove('error');
    }

    /**
     * Validate two values and return true or false if respectively valid or not
     *
     * @param value1
     * @param value2
     * @returns {boolean}
     */
    validate(value1, value2)
    {
        let valid = true;

        // Validate lowercase letters
        let lowerCaseLetters = /[a-z]/g;
        if(value1.match(lowerCaseLetters)) {
            this.letter.classList.remove("invalid");
            this.letter.classList.add("valid");
        } else {
            this.letter.classList.remove("valid");
            this.letter.classList.add("invalid");
            valid = false;
        }

        // Validate capital letters
        let upperCaseLetters = /[A-Z]/g;
        if(value1.match(upperCaseLetters)) {
            this.capital.classList.remove("invalid");
            this.capital.classList.add("valid");
        } else {
            this.capital.classList.remove("valid");
            this.capital.classList.add("invalid");
            valid = false;
        }

        // Validate numbers
        let numbers = /[0-9]/g;
        if(value1.match(numbers)) {
            this.number.classList.remove("invalid");
            this.number.classList.add("valid");
        } else {
            this.number.classList.remove("valid");
            this.number.classList.add("invalid");
            valid = false;
        }

        // Validate numbers
        // let pipe = /\|/g;
        // if(value1.match(pipe)) {
        //     this.noPipe.classList.remove("valid");
        //     this.noPipe.classList.add("invalid");
        //     valid = false;
        // } else {
        //     this.noPipe.classList.remove("invalid");
        //     this.noPipe.classList.add("valid");
        // }

        // Validate length
        if(value1.length >= this.minPasswordLength) {
            this.length.classList.remove("invalid");
            this.length.classList.add("valid");
        } else {
            this.length.classList.remove("valid");
            this.length.classList.add("invalid");
            valid = false;
        }

        // Validate password match
        if(value1 === value2 && (value1 !== '' || value2 !== '')) {
            this.match.classList.remove("invalid");
            this.match.classList.add("valid");
        } else {
            this.match.classList.remove("valid");
            this.match.classList.add("invalid");
            valid = false;
        }

        return valid
    }


    /**
     * Triggers the function after the wait time (milliseconds) has expired and only
     * if the function is not triggered again before the wait time expired.
     *
     * @param func
     * @param wait
     * @param immediate
     * @returns {Function}
     */
    debounce(func, wait, immediate) {
        let timeout;

        return function() {
            let context = this, args = arguments;
            let later = function() {
                timeout = null;
                if (!immediate) func.apply(context, args);
            };
            let callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if (callNow) func.apply(context, args);
        };
    };
}







/**
 * Fills an ul element with data retrieved from an api and makes the items searchable.
 /* The data to and from the api has this structure for example.
 *
 * [
 *  {
 *      id:1
 *      title:false
 *      thumbnail:false
 *      status: "active"
 *      routes: []
 *      children: [
 *          {
 *              id: 12
 *              title: "Thuis"
 *              thumbnail: false
 *              routes: [{
 *                  40: "en/Homenew",
 *                  104: "nl/Thuisnew"
 *              }]
 *          }
 *      ]
 *  }
 * ]
 */
class SearchController {
    constructor(sectionId, selector, inputSelector, mainUlId, resultCounterId) {
        this.apiUrl = '';
        this.data = '';
        this.dataToLoad = '';
        this.editEntitiesUrl = '';
        this.initialized = false;
        this.sectionId = sectionId;
        this.selector = selector;
        this.mainUlId = mainUlId;
        this.resultCounterId = resultCounterId;
        this.disabled = true;
        this.inputSelector = inputSelector;

        this.listItemClass = 'entities-list-item';

        //The class that is added to li items that must be visible because they match (a part) of the search value).
        //This is also the class applied to the rootUl if any results are found.
        this.visibleClass = 'active';
        this.invisibleClass = 'hide';

        this.section = document.getElementById(this.sectionId);
        let rootUl = document.querySelector(this.selector);

        this.siteSlug = rootUl.dataset.siteSlug;
        this.slug = rootUl.dataset.slug;
        this.activeId = rootUl.dataset.activeId;
    }

    /**
     * Initializes the controller so that it knows where it can get its data from,
     * where it needs to direct users when they click on an item and from which ul it
     * should create a sortable ul
     *
     * @param dataSource A string|Object
     * @param editEntitiesUrl A string
     *
     * In case of the data source being an object it must look like this:
     * {
     *  data: [{
     *      id: null,
     *      routes: []
     *      status: "",
     *      title: "",
     *      children: [
     *          {
     *              id: "2",
     *              routes: []
     *              status: "",
     *              title: "My username"
     *              children: []
     *          }
     *      ]
     *  }]
     * }
     */
    init(dataSource, editEntitiesUrl) {
        if(typeof dataSource === "string") {
            this.apiUrl = dataSource;
        } else {
            this.dataToLoad = dataSource;
        }
        this.editEntitiesUrl = editEntitiesUrl;

        this.initialized = true;
    }

    /**
     * Returns a promise that resolves with the root ul that then will contain the items retrieved from the api
     */
    load() {
        if (this.initialized === false) { console.error('Please initialize the controller with the init method first.'); return; }

        let self = this;

        return new Promise(function (resolve, reject) {
            if (self.initialized === false) { reject('Please initialize the controller with the init method first.') }

            if(self.apiUrl !== '' && self.dataToLoad === '') {
                axios.get(self.apiUrl).then((response) => {
                    if (!response.data) {
                        reject('The sortable did not get any data from the api');
                        return;
                    }
                    // console.log(response);

                    /** @var Array[] menuItemArrays*/
                    response.data[0].children.forEach(function (itemObject) {
                        /** @var array htmlElements **/
                        let htmlElements = self.createHtmlElement(itemObject);

                        htmlElements.forEach((element) => {
                            document.querySelector(self.selector).append(element);
                        })
                    });

                    // console.log(document.querySelector(self.selector));

                    self.initializeSearch();

                    resolve(document.querySelector(self.selector));
                }).catch((error) => {
                    reject(error);
                });
            } else if(self.apiUrl === '' && self.dataToLoad !== '') {
                // console.log('data children');
                // console.log(self.dataToLoad.data[0].children);

                self.dataToLoad.data[0].children.forEach(function(itemObject) {
                    let htmlElements = self.createHtmlElement(itemObject);

                    htmlElements.forEach((element) => {
                        document.querySelector(self.selector).append(element);
                    });
                });

                self.initializeSearch();

                resolve(document.querySelector(self.selector));
            }
        });
    }

    /**
     * Initialize search functionality on the ul this searchable does its job for
     */
    initializeSearch() {
        let section = this.section;
        let input = document.querySelector(this.inputSelector);
        let searchUl = document.querySelector(this.selector);
        let resultCounter = document.getElementById(this.resultCounterId);

        //handles searching
        input.addEventListener('keyup', (event) => {
            let mainUl = document.getElementById(this.mainUlId);

            let resultsCount = 0;

            let filterValue = input.value.toLowerCase();

            let noSearchValue = (filterValue == '') ? true : false;

            let listItems = searchUl.querySelectorAll('li');

            let listItemsCount = listItems.length;

            // console.log(listItemsCount);
            for (let i = 0; i < listItemsCount; i++) {
                let itemValue = listItems[i].dataset.title.toLowerCase();

                if(itemValue.indexOf(filterValue) > -1 && noSearchValue === false) {
                    //item found
                    resultsCount++;
                    listItems[i].classList.add(this.visibleClass)
                } else {
                    //item not found
                    listItems[i].classList.remove(this.visibleClass)
                }
            }

            if(resultsCount > 0) {
                // searchUl.parentNode.classList.add(this.visibleClass);
                // mainUl.classList.add(this.invisibleClass);
            } else {
                // searchUl.parentNode.classList.remove(this.visibleClass);
                // mainUl.classList.remove(this.invisibleClass);
            }

            resultCounter.innerHTML = resultsCount+"";

            if(!noSearchValue){
                section.classList.add(this.visibleClass);
                mainUl.classList.add(this.invisibleClass);
            }
            else{
                section.classList.remove(this.visibleClass);
                mainUl.classList.remove(this.invisibleClass);
            }

        });

        searchUl.classList.remove(this.visibleClass);
    }

    /**
     * Creates a menu item (HTMLElement) and sub menu items if necessary
     *
     * @param data
     * @param items array Used internally. Humans must not touch this
     * @param currentTreeLevel string Used internally. Humans must not touch this
     * @returns {Array}
     */
    createHtmlElement(data, items = [], currentTreeLevel = '')
    {
        if (this.initialized === false) { console.error('Please initialize the controller with the init method first.'); return; }

        let self = this;
        let activeId = self.activeId;
        let id = data.id;
        let title = data.title;
        let children = data.children;
        let status = data.status;
        let thumbnail = data.thumbnail;
        let statusType = 0;

        let breadcrumb = currentTreeLevel ;
        let treeBreadcrumb = (currentTreeLevel !== "") ? currentTreeLevel + " | "+title : title ;
        // console.log(breadcrumb);

        // console.log(data);

        //Render all child html items first
        let childItems = [];
        for(let object of children)
        {
            childItems.push(this.createHtmlElement(object, items, treeBreadcrumb));
        }

        switch(status){
            //Green
            case 'active':
                statusType = 1;
                break;
            //Transparent
            default:
                break;
        }

        //Displays a red or green line in front of the item depending on if the status (class) is active or not
        let colorStatusHtml = `
            <span class="color-status" data-status-type="${statusType}">
                &nbsp;
            </span>
        `;

        //Displays the items icon thumbnail OR the first character of its title
        let iconHtml = `
            <div class="icon" ${thumbnail ? `style="background-image: url('${thumbnail}');"` : ''} >
                ${thumbnail ? '' : `<span>${title ? title.substring(0,1): ``}</span>`}
            </div>
        `;

        //The main item html that has all other items
        let listIem = `
            <li data-title="${title}" class="${this.listItemClass} ${ id == activeId ? 'active': '' }"> 
                <a href="${this.editEntitiesUrl}/${id}">
                    ${colorStatusHtml}
                    ${iconHtml}
                    <p data-breadcrumb="${breadcrumb}">${title}</p>
                </a>
            </li> 
        `;


        //Render it to a real html element
        let domParser = new DOMParser();
        let document = domParser.parseFromString(listIem, "text/html");

        items.push(document.body.firstChild);
        // console.log('rendered item: ');
        // console.log(items);

        //And then add the children inside
        if(childItems.length > 0)
        {
            // console.log('adding rendered childitems');
            childItems.forEach(function(element) {
                items.push(element[0]);
            });
        }
        // console.log('items result:');
        // console.log(items);

        //and return it
        return items;
    }
}
$( function() {
    $(".select-menu").selectmenu({
        width: '100%',
        create: function ( event, ui ) {
            buttonId = this.id + '-button';
            menuId = this.id + '-menu';
        },
        open: function ( event, ui ) {
            let button = document.getElementById(this.id + '-button');
            let menu = document.getElementById(this.id + '-menu');
            menu.style.width = button.offsetWidth + 'px';
            menu.parentNode.style.width = button.offsetWidth + 'px';
            button.classList.add('dropdown-open');
        },
        change: function( event, ui ) {
            let button = document.getElementById(this.id + '-button');
            let buttonText = button.querySelector('.ui-selectmenu-text').innerHTML;
            button.querySelector('.ui-selectmenu-text').innerHTML = buttonText.replace(/&nbsp;/gi, '');
        },
        close: function ( event, ui) {
            let button = document.getElementById(this.id + '-button');
            button.classList.remove('dropdown-open');
        }
    });
});
/**
 * Fills an ul element with data retrieved from an api and makes the items draggable so that you can sort them.
 * Also updates the api with the new positions of the items. The data to and from the api has this structure for example.
 *
 * [
 *  {
 *      id:1
 *      title:false
 *      thumbnail:false
 *      status: "active"
 *      routes: []
 *      children: [
 *          {
 *              id: 12
 *              title: "Thuis"
 *              thumbnail: false
 *              routes: [{
 *                  40: "en/Homenew",
 *                  104: "nl/Thuisnew"
 *              }]
 *          }
 *      ]
 *  }
 * ]
 */
class SortableController {
    constructor(selector) {
        this.apiUrl = '';
        this.editEntitiesUrl = '';
        this.initialized = false;
        this.selector = selector;
        this.disabled = true;

        this.listItemClass = 'entities-list-item';

        let rootUl = document.querySelector(this.selector);
        this.siteSlug = rootUl.dataset.siteSlug;
        this.slug = rootUl.dataset.slug;
        this.activeId = rootUl.dataset.activeId;
    }

    /**
     * Initializes the controller so that it knows where it can get its data from,
     * where it needs to direct users when they click on an item and from which ul it
     * should create a sortable ul
     *
     * @param apiUrl A string
     * @param editEntitiesUrl A string
     */
    init(apiUrl, editEntitiesUrl) {
        this.apiUrl = apiUrl;
        this.editEntitiesUrl = editEntitiesUrl;

        let itemsThatHaveSubUl = document.querySelectorAll(this.selector+" li ul");
        itemsThatHaveSubUl.forEach((node, index) => {
            // console.log('clicked ul');
            node.style.display = node.style.display !== 'none' ? 'none' : '';
        });

        this.initialized = true;
    }

    updateSortableJavascript()
    {
        // console.log("Updating all sortable elements with these selectors: '" + this.selector+" .sortable' AND '"+this.selector+"'");

        $('.sortable').sortable({
            connectWith: ".sortable",
            placeholder: "sortable-placeholder",
            disabled: this.disabled
        });

        let itemsThatHaveSubUl = document.querySelectorAll(this.selector+" li ul");
        itemsThatHaveSubUl.forEach((node, index) => {
            // console.log('clicked ul');
            // node.style.display = node.style.display !== 'none' ? 'none' : '';
        });
    }

    /**
     * Makes the rootUl sortable
     */
    enableSortable() {
        if (this.initialized === false) { console.error('Please initialize the controller with the init method first.'); return; }

        this.disabled = false;
        $(this.selector).sortable({
            disabled: this.disabled
        }).addClass('sorting');


        this.updateSortableJavascript();
    }

    /**
     * Diables the rootUl so that it cannot be sorted
     */
    disableSortable() {
        // if (this.initialized === false) { console.error('Please initialize the controller with the init method first.'); return; }

        this.disabled = true;
        $(this.selector).sortable({
            disabled: this.disabled
        }).removeClass('sorting');

        this.save();
    }

    /**
     * Saves the item data to the api
     */
    save() {
        if (this.initialized === false) { console.error('Please initialize the controller with the init method first.'); return; }
        let itemsJson = this.itemsToJson(document.querySelector(this.selector));

        itemsJson = JSON.stringify(itemsJson);
        let apiJson = {
          "tree": itemsJson
        };

        axios.post(this.apiUrl, apiJson).then((response) => {
            // console.log('Successfully stored the sort order to the api: ');
            // console.log(response);
        }).catch((error) => {
            // console.error('Could not save sortable sort order to api because an error occured: ');
            console.error(error);
        });
    }

    /**
     * Converts the rootUl back to json for saving it
     *
     * @param htmlElement HTMLElement Root ul
     * @return null|[] if something went wrong null, or an array containing json items if successfull.
     */
    itemsToJson(htmlElement) {
        if (this.initialized === false) { console.error('Please initialize the controller with the init method first.'); return; }

        let jsonArray = [];
        let error = false;

        //find all child li items
        let listItems = htmlElement.querySelectorAll(':scope > li.'+this.listItemClass);
        for(let listItem of listItems)
        {
            let elementJson = listItem.dataset.json;
            if(!elementJson)
            {
                console.error('One or more li HTMLElements with class "'+this.listItemClass+'" did not have data-json attribute set while it should.');
                error = true;
            }

            let currentItemJson = JSON.parse(listItem.dataset.json);
            let childUl = listItem.querySelector('ul');
            if(childUl)
            {
                currentItemJson.children = this.itemsToJson(childUl);
                if(!currentItemJson.children) error = true;
            } else {
                currentItemJson.children = [];
            }

            jsonArray.push(currentItemJson);
        }

        if(error) return null;

        return jsonArray;
    }

    /**
     * Returns a promise that resolves with the root ul that then will contain the items retrieved from the api
     */
    load() {
        if (this.initialized === false) { console.error('Please initialize the controller with the init method first.'); return; }

        self = this;


        return new Promise(function (resolve, reject) {
            if (self.initialized === false) { reject('Please initialize the controller with the init method first.') }

            axios.get(self.apiUrl).then((response) => {
                if (!response.data) {
                    reject('The sortable did not get any data from the api');
                    return;
                }

                /** @var HTMLElement[] menuItems*/
                let menuItems = [];
                response.data[0].children.forEach(function (itemObject) {
                    menuItems.push(self.createHtmlElement(itemObject));
                });

                menuItems.forEach(function (htmlElementItem) {
                    document.querySelector(self.selector).appendChild(htmlElementItem);
                });

                self.updateSortableJavascript();

                resolve(document.querySelector(self.selector));
            }).catch((error) => {
                reject(error);
            });
        });
    }

    /**
     * Removes all children from the ul
     */
    clearRootUl()
    {
        if (this.initialized === false) { console.error('Please initialize the controller with the init method first.'); return; }

        let rootUl = document.querySelector(this.selector);
        while(rootUl.firstChild) rootUl.removeChild(rootUl.firstChild);
    }

    /**
     * Creates a menu item (HTMLElement) and sub menu items if necessary
     *
     * @param data
     * @returns {HTMLElement}
     */
    createHtmlElement(data)
    {
        if (this.initialized === false) { console.error('Please initialize the controller with the init method first.'); return; }

        self = this;

        let activeId = self.activeId;

        let id = data.id;
        let title = data.title;
        let thumbnail = data.thumbnail;
        let children = data.children;
        let status = data.status;
        let routes = data.routes;
        let statusType = 0;

        //Generate json data representing that element
        let routesForElement = {};
        for (let routeProperty in routes) {
            if(!routes.hasOwnProperty(routeProperty)) continue;

            routesForElement[routeProperty] = routes[routeProperty];
        }
        if(routesForElement == {}) routesForElement = [];


        let elementJson = {
            id: data.id,
            title: data.title,
            thumbnail: data.thumbnail,
            routes: routesForElement,
            status: data.status
        };
        elementJson = JSON.stringify(elementJson);

        //Render all child html items first
        let childItems = [];
        for(let object of children)
        {
            childItems.push(this.createHtmlElement(object));
        }

        switch(status){
            //Green
            case 'active':
                statusType = 1;
                break;

            //Transparent
            default:
                break;
        }

        //Displays a red or green line in front of the item depending on if the status (class) is active or not
        let colorStatusHtml = `
            <span class="color-status" data-status-type="${statusType}">
                &nbsp;
            </span>
        `;

        //Displays the items icon thumbnail OR the first character of its title
        let iconHtml = `
            <div class="icon" ${thumbnail ? `style="background-image: url('${thumbnail}');"` : ''} >
                ${thumbnail ? '' : `<span>${title ? title.substring(0,1): ``}</span>`}
            </div>
        `;

        //The main item html that has all other items
        let listIem = `
            <li data-json='${elementJson}' class="${this.listItemClass} ${ id == activeId ? 'active': '' }"> 
                <a href="${this.editEntitiesUrl}/${id}">
                    ${colorStatusHtml}
                    ${iconHtml}
                    ${title}
                </a>
            </li> 
        `;

        //Render it to a real html element
        let domParser = new DOMParser();
        let document = domParser.parseFromString(listIem, "text/html");
        let node = document.body.firstChild;

        //And add the children inside
        if(childItems.length > 0)
        {
            let subList = document.createElement('ul');
            subList.className = 'sortable';

            // console.log('rendered childItems');
            // console.log(childItems);
            childItems.forEach(function(element) {
                subList.appendChild(element);
            });

            node.appendChild(subList);
        }

        //and return it
        return node;
    }
}
class TabsController {

    /**
     * The tab controller can obviously control tabs.
     * You just have to have a couple of divs that hold the content. These divs must all have a class name that
     * corresponds to the value of constructor parameter tabContentIdAndClassAndPrefix. And they all must have an id
     * that also starts with the value of tabContentIdAndClassAndPrefix followed by the tab slug.
     *
     * You can specify if you have an hidden input field that needs to be updated with the curent tab slug. You do that
     * with the tabSlugInputSelector.
     *
     * Content tab divs and buttons will also receive an active state class to make them visible / stand out when present
     * or hidden / to background. You can specify this class name with the activeClass parameter.
     *
     * The tabButtonGroupSelector selects an element that holds the tab buttons to switch tabs. This element must have an
     * ul element that holds li elements containing a elements.
     *
     * The reactToUrlHashChange parameter controls if the controller should react if the hash part of the url did change.
     *
     * @param tabContentIdAndClassAndPrefix stringseelc
     * @param tabButtonGroupSelector string
     * @param activeClass string
     * @param reactToUrlHashChange
     * @param tabSlugInputSelector string
     */
    constructor(tabContentIdAndClassAndPrefix = 'tab', tabButtonGroupSelector = '.tab-buttons', activeClass = 'active', reactToUrlHashChange = false, tabSlugInputSelector = undefined) {
        this.tabContentDivsClassAndIdPrefix = tabContentIdAndClassAndPrefix;
        this.tabSlugInputId = tabSlugInputSelector;
        this.activeClass = activeClass;
        this.reactToUrlHashChange = reactToUrlHashChange;

        if(reactToUrlHashChange) this.addListenerForHashChange(reactToUrlHashChange);
    }

    /**
     * Opens the tab by using the tab slug
     *
     * @param tabSlug
     */
    openTab(tabSlug) {
        //Remove trailing / on the left hand side
        tabSlug = this.removeLeftHandSlashInSlug(tabSlug);
        if (!tabSlug) { console.error("TabsController: Could not open tab with an empty slug"); return; }

        this.updateTabSlugInput(tabSlug);

        this.showTabContentForTabWithSlug(tabSlug);
        this.makeTabButtonActiveForSlug(tabSlug);
    }

    /**
     * Removes a / at the beginning of a tab slug
     *
     * @param tabSlug
     */
    removeLeftHandSlashInSlug(tabSlug) {
        return tabSlug.replace(/^\/(.*)/, '$1');
    }

    /**
     * Updates a usually hidden input field holding the tab slug with a new slug
     *
     * @param newSlug string
     */
    updateTabSlugInput(newSlug)
    {
        if(this.tabSlugInputId === undefined) return;
        let element = document.querySelector(this.tabSlugInputId);
        if(element) element.value = newSlug;
    }

    makeTabButtonActiveForSlug(slug)
    {
        $('.entity-tabs >ul >li').removeClass('active');
        $('.entity-tabs >ul >li a[href="#' + slug + '"]').parent().addClass('active');
    }

    /**
     * Shows the tab content div with the specified slug and hides the other content div tabs.
     * It does this by adding and removing classes (the active class you specified in the constructor)
     *
     * @param slug string
     */
    showTabContentForTabWithSlug(slug)
    {
        //Remove the active class from all tab content classes
        document.querySelectorAll('.'+this.tabContentDivsClassAndIdPrefix).forEach((element) => {
            element.classList.remove(this.activeClass)
        });

        //Add the active class to the tab that has the correct slug appended to the IdPrefix
        let activeTab = document.querySelector("#"+this.tabContentDivsClassAndIdPrefix+"-"+slug);
        if(!activeTab) { console.error("TabsController: Could not make content tab active. It should have an ID with: #"+this.tabContentDivsClassAndIdPrefix+"-"+slug); return; }

        activeTab.classList.add(this.activeClass);
    }

    /**
     * Adds or disables listening for hashchange events to update tabs
     *
     * @param boolean
     */
    addListenerForHashChange(boolean)
    {
        if(boolean) {
            window.addEventListener('hashchange', this.hashChanged.bind(this));
            window.addEventListener('load', this.hashChanged.bind(this));
        } else {
            window.removeEventListener('hashchange', this.hashChanged);
            window.removeEventListener('load', this.hashChanged);
        }
    }

    /**
     * Called automatically by an internal listener (controlled by the addListenerForHashChange method)
     */
    hashChanged(event)
    {
        let tabSlug = window.location.hash.substring(1);
        if (tabSlug) {
            this.openTab(tabSlug);
            return;
        }

        let tabSlugElement = document.querySelector(this.tabSlugInputId);
        if(!tabSlugElement) return;

        this.openTab(tabSlugElement.value);
    }
}