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/netwerkbrabant/netwerkbrabant.nl/resources/assets/js/kms/attributes/multiselect.js
class MultiSelectController {
    /**
     * @param multiSelectElement {HTMLInputElement}
     */

    constructor(multiSelectElement)
    {
        if(multiSelectElement.tagName !== 'INPUT' || multiSelectElement.type !== 'text') {
            console.error('The given parameter must be an instance of HTMLInputElement');
            return;
        }
        this.multiSelectSelectField = multiSelectElement; //The input element that must multiSelect what you type

        let input = this.multiSelectSelectField.parentElement.querySelector('input[type="hidden"]'); //The input element that wil store the real value for the multiSelect Attribute
        if(!input) {
            console.error('Expected an HTMLInputElement as a sibling of the supplied multiSelect element (HTMLInputElement) to store the multiSelect\'s real value');
            return;
        }
        this.input = input;

        if(!'attributeKey' in this.multiSelectSelectField.dataset) {
            console.error('attributeKey dataset value not set');
            return;
        }
        this.attributeKey = this.multiSelectSelectField.dataset.attributeKey;

        if(!'autosaveUrl' in this.multiSelectSelectField.dataset) {
            console.error('autosaveUrl dataset value not set');
            return;
        }
        this.autosaveUrl = this.multiSelectSelectField.dataset.autosaveUrl;

        if(!'maxItemsToSelect' in this.multiSelectSelectField.dataset) {
            console.error('maxItemsToSelect dataset value not set');
            return;
        }
        this.maxItemsToSelect = this.multiSelectSelectField.dataset.maxItemsToSelect;

        if(!'itemData' in this.multiSelectSelectField.dataset) {
            console.error('itemData dataset value not set');
            return;
        }
        this.dataSet = JSON.parse(this.multiSelectSelectField.dataset.itemdata);


        if(!'values' in this.multiSelectSelectField.dataset) {
            console.error('values dataset value not set');
            return;
        }
        this.valuesFromAttribute = this.multiSelectSelectField.dataset.values.split(',');


        let openElement = this.input.parentElement.querySelector('.open');
        if(!openElement) {
            console.error('Could not find multiSelect open element. It should be a sibling of the input')
        }

        this.openbutton = openElement;

        let itemsWrapper = multiSelectElement.parentElement.parentElement.querySelector('div.items');
        if(!itemsWrapper) {
            console.error('Could not find a div HTMLElement with class "items" inside the multiSelectsInputElement\'s parent parent element')
        }
        this.itemsWrapper = itemsWrapper;

        this.sortable = this.multiSelectSelectField.dataset.sortable;

        if(this.sortable) {

            let self = this;
            this.dragElement = null;
            // Sorting starts
            itemsWrapper.addEventListener('dragstart', function (evt){
                self.dragElement = evt.target; // Remembering an element that will be moved

                // Limiting the movement type
                evt.dataTransfer.effectAllowed = 'move';
                evt.dataTransfer.setData('Text', self.dragElement.textContent);

                // Subscribing to the events at dnd
                self.itemsWrapper.addEventListener('dragover', self.onDragOver.bind(self), false);
                self.itemsWrapper.addEventListener('dragend', self.onDragEnd.bind(self), false);

                setTimeout(function () {
                    // If this action is performed without setTimeout, then
                    // the moved object will be of this class.
                    self.dragElement.classList.add('ghost');
                }, 0)
            }, false);
        }

        this.listenersEnabled = true;

        this.constructedSuccessfully = true;
        this.initialize();
    };

    initialize() {
        if(this.constructedSuccessfully === false) {
            console.error('MultiSelectController was not constructed successfully. Could not initialize because of it');
            return;
        }

        this.makeMultiSelect();
        this.addItems();
        this.updateRealInput();
        this.enableListeners(true);
        this.enableMultiSelectOpenButton();
    }

    addItems() {
        const dataSetLength = this.dataSet.length;
        const valueFromAttributeLength = this.valuesFromAttribute.length;

        for (let valuesIndex = 0; valuesIndex < valueFromAttributeLength; valuesIndex++) {
            const idFromAttribute = parseInt(this.valuesFromAttribute[valuesIndex]);

            for (let itemIndex = 0; itemIndex < dataSetLength; itemIndex++) {
                const item = this.dataSet[itemIndex];

                if (item.id === idFromAttribute) {
                    this.addItem(item.id, item.value);
                    break;
                }

            }
        }
    }

    /**
     * Makes the fake input field and multiSelect input field.
     * Focus method is triggered when you hover over items in the menu
     * select method is triggered when you click an item in the list.
     */
    makeMultiSelect() {
        let self = this;
        let jqueryElement =  $(this.multiSelectSelectField);

        return jqueryElement.autocomplete({
            source: this.dataSet,
            minLength: 0,

            focus: function (event, ui) {
                return false;
            },
            select: function (event, ui) {
                $(this.multiSelectSelectField).val("");
                self.addItem(ui.item.id, ui.item.value);
                self.updateRealInput();
                self.autosaveIfNeeded();
                self.enableListeners(true);
                return false;
            },
        });
    }

    /**
     * Make sure we can open the multiSelect field dropdown with the open button
     */
    enableMultiSelectOpenButton() {
        let self = this;

        this.openbutton.addEventListener('click', function(event) {
            $(self.multiSelectSelectField).autocomplete("search", "");
        });
    }

    /**
     * Adds a span tag containing the value given and the data-id property set to id given to
     * the p tag with class items.
     */
    addItem(id, value) {
        let self = this;
        if(this.itemsCount() > self.maxItemsToSelect) return;

        let itemElement = document.createElement('p');
        itemElement.classList.add('item');

        if(this.sortable) {
            itemElement.draggable = true;
            value = '<span class="drag-icon"></span><span class="text">'+value+'</span>';
        }

        itemElement.innerHTML = value;

        let removeButton = document.createElement('span');
        removeButton.classList.add('remove');
        itemElement.dataset.id = id;


        itemElement.dataset.sort_order = this.itemsCount();

        removeButton.addEventListener('click', (function (itemToRemove) {
            return function() {
                if(!self.listenersEnabled) return;
                self.removeItem(itemToRemove);
            }
        })(id));

        this.itemsWrapper.appendChild(itemElement);
        itemElement.appendChild(removeButton);
    }

    itemsCount()
    {
        return this.itemsWrapper.childNodes.length;
    };

    /**
     * Iterates over the child elements of the p element with class items and removes the element that has
     * data-id set to the specified id. Then calls updateRealInput to update the hidden input
     */
    removeItem(id) {
        let items = this.itemsWrapper.children;
        const itemsLength = this.itemsWrapper.children.length;
        for (let index = 0; index < itemsLength; index++) {
            const item = items[index];

            if (parseInt(item.dataset.id) === id) {
                this.itemsWrapper.removeChild(item);
                break;
            }
        }
        this.updateRealInput();
        this.autosaveIfNeeded();
    };

    /**
     * Iterates over the child elements of the p element with class items and concatenates their data-id
     * values into a string. After that it sets the hidden input with the id string
     */
    updateRealInput() {
        let ids = [];
        let items = this.itemsWrapper.children;
        const itemsLength = items.length;

        for (let i = 0; i < itemsLength; i++) {
            const item = items[i];
            ids[ids.length] = item.dataset.id;
        }
        let idString = ids.join(',');

        this.input.setAttribute('value', idString);

        let changeEvent = document.createEvent("Event");
        changeEvent.initEvent("change", false, true);
        // args: string type, boolean bubbles, boolean cancelable
        this.input.dispatchEvent(changeEvent)
    };


    autosaveIfNeeded() {
        if (!this.autosaveUrl) return;
        let itemIds = this.input.getAttribute('value');
        let self = this;

        $.ajax({
            type: "POST",
            url: this.autosaveUrl,
            data: {
                'itemIds': itemIds,
            },
            success: function (response, textStatus, xhr) {
                if (xhr.status === 200) {
                    console.log('autosave successfull to url: ' + self.autosaveUrl)
                } else {
                    console.error('autosave failure to url (no code 200): ' + self.autosaveUrl + '');
                    console.error(response)
                }
            },
            error: function (message) {
                console.error('autosave failure to url: ' + self.autosaveUrl + '');
                console.error(message)
            },
            headers: {
                'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
            },
            dataType: 'json'
        });
    }

    enableListeners(enable) {
        this.listenersEnabled = enable;

        let length = this.itemsWrapper.children.length;

        //Add / remove listeners to items
        for (let i = 0; i < length; i++) {
            let item = this.itemsWrapper.children[i];
            let id = item.dataset.id;

            item.classList.remove('readonly');
            this.multiSelectSelectField.classList.remove('hidden');

            if(!enable) {
                item.classList.add('readonly');
                this.multiSelectSelectField.classList.add('hidden');
                this.openbutton.classList.add('hidden');
            }
        }
    }

    onDragOver(evt) {
        evt.preventDefault();
        evt.dataTransfer.dropEffect = 'move';

        let target = evt.target;
        if (target && target !== this.dragElement && target.nodeName === 'P') {
            // Sorting
            this.itemsWrapper.insertBefore(this.dragElement, target.nextSibling || target);
        }
    }

    onDragEnd(evt){
        evt.preventDefault();

        this.dragElement.classList.remove('ghost');
        this.itemsWrapper.removeEventListener('dragover', this.onDragOver, false);
        this.itemsWrapper.removeEventListener('dragend', this.onDragEnd, false);
        console.log("dragEnd");
        this.updateRealInput();
    }
}