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}">
</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;
}
}