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/egovers/edwingovers.nl/resources/assets/js/kms/entities/searchable.js
/**
 * 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 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));
        }

        //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="${status}">
                &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;
    }
}