File: D:/HostingSpaces/SBogers10/edwingovers.komma.pro/resources/assets/js/kms/entities/sortable.js
/**
* 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;
//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));
}
//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-json='${elementJson}' class="${this.listItemClass} ${ id == activeId ? 'active': '' }">
<a href="${this.editEntitiesUrl}/${id}">
${colorStatusHtml}
${iconHtml}
<p>${title}</p>
</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;
}
}