File: D:/HostingSpaces/SBogers10/farmfun.komma.pro/resources/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);
if(!rootUl) {
console.error('SearchController: Could not find the root ul by using the selector "'+this.selector+'"')
}
this.siteSlug = rootUl.dataset.siteSlug;
this.slug = rootUl.dataset.slug;
this.activeId = rootUl.dataset.activeId;
axios.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest";
}
/**
* 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 || response.data.length == 0) {
reject('The searchable did not get any data from the api');
return;
}
// console.log(response);
/** @var Array[] menuItemArrays*/
var childrenLength = response.data.children.length;
for(let i = 0; i < childrenLength; i++){
/** @var array htmlElements **/
let htmlElements = self.createHtmlElement(response.data.children[i]);
const htmlElementsLength = htmlElements.length;
for(let j = 0; j < htmlElementsLength; j++){
document.querySelector(self.selector).appendChild(htmlElements[j]);
}
}
// 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.children);
/** @var Array[] menuItemArrays*/
var childrenLength = self.dataToLoad.data.children.length;
for(let i = 0; i < childrenLength; i++){
/** @var array htmlElements **/
let htmlElements = self.createHtmlElement(self.dataToLoad.data.children[i]);
const htmlElementsLength = htmlElements.length;
for(let j = 0; j < htmlElementsLength; j++){
document.querySelector(self.selector).appendChild(htmlElements[j]);
}
}
self.initializeSearch();
resolve(document.querySelector(self.selector));
}
});
}
/**
* Initialize search functionality on the ul this searchable does its job for
*/
/**
* 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();
let itemModel = JSON.parse(listItems[i].dataset.model);
let foundInTitle = itemValue.indexOf(filterValue) > -1 && noSearchValue === false;
let foundInModel = false;
if(!foundInTitle && filterValue.length >= 3) { //Only search when the length of the filter value is 3 or more to filter out crap
for (let property in itemModel) {
if(!itemModel.hasOwnProperty(property)) continue;
if(String(itemModel[property]).toLowerCase().indexOf(filterValue.toLowerCase()) !== -1) {
foundInModel = true;
break;
}
}
}
if(foundInTitle || foundInModel) {
//item found
resultsCount++;
listItems[i].classList.add(this.visibleClass);
listItems[i].setAttribute('dusk', 'found_search_item');
} else {
//item not found
listItems[i].classList.remove(this.visibleClass);
listItems[i].removeAttribute('dusk');
}
}
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 model = data.model;
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}" data-model='${JSON.stringify(model) || JSON.stringify({})}' 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
var childrenLength = childItems.length;
if(childrenLength > 0)
{
for(let i = 0; i < childrenLength; i++){
let child = childItems[i];
items.push(child[0]);
}
}
// console.log('items result:');
// console.log(items);
//and return it
return items;
}
}