sbgg.jetzt/page/accordion.js
2024-07-28 17:38:40 +02:00

209 lines
4.7 KiB
JavaScript

"use strict";
let accordion_view_list = [];
const accordion_resize_observer = new ResizeObserver((changed_item_list) => {
for(let one_changed_item of changed_item_list){
accordion_body_resize(one_changed_item.target);
}
});
window.addEventListener("load", function(event){
// COLLECT ACCORDION VIEWS //
let accordion_root_list = document.getElementsByClassName("accordion");
for(let one_accordion_root of accordion_root_list){
// collect this view
let accordion_view = accordion_collect_view(one_accordion_root);
accordion_view_list.push(accordion_view);
// initialized this view
accordion_init(accordion_view);
}
});
/**
* HELPER: Collect data for one accordion view.
*
* @param root Root element.
*
* @return Accordion view object.
*/
function accordion_collect_view(root){
// COLLECT ITEMS //
let item_list = [];
for(let one_child of root.children){
// maybe unwrap
if(one_child.classList.contains("wrapper")) one_child = one_child.children[0] ?? null;
// validate child as item
if(!one_child.classList.contains("item")) continue;
// find head element
let head;
for(let one_item_child of one_child.children){
if(one_item_child.classList.contains("head")){
head = one_item_child;
break;
}
}
if(head === undefined) throw "Unable to find accordion item head";
// find icon
let icon;
for(let one_head_child of head.children){
if(one_head_child.classList.contains("icon")){
icon = one_head_child;
break;
}
}
if(icon === undefined) throw "Unable to find accordion item icon";
// find title
let title;
for(let one_head_child of head.children){
if(one_head_child.classList.contains("title")){
title = one_head_child;
break;
}
}
if(title === undefined) throw "Unable to find accordion item title";
// find body container
let bodyContainer;
for(let one_item_child of one_child.children){
if(one_item_child.classList.contains("body-container")){
bodyContainer = one_item_child;
break;
}
}
if(bodyContainer === undefined) throw "Unable to find accordion body container";
// add to item list
item_list.push({
element: one_child,
head: head,
icon: icon,
title: title,
bodyContainer: bodyContainer,
});
}
// BUILD OBJECT //
return {
root: root,
item_list: item_list,
};
}
/**
* HELPER: Initialize accordion view.
*
* @param accordion Accordion view object.
*/
function accordion_init(accordion){
// REGISTER ONCLICK HANDLERS //
for(let one_item of accordion.item_list){
// head
one_item.head.onclick = function(event){
event.stopPropagation();
accordion_click(one_item, accordion);
};
// title
one_item.title.tabIndex = 0;
one_item.title.onclick = function(event){
event.stopPropagation();
accordion_click(one_item, accordion);
};
one_item.title.onkeypress = function(event){
if(event.key === "Enter"){
event.preventDefault();
event.stopPropagation();
accordion_click(one_item, accordion);
}
};
}
// RESET OPENED STATE //
for(let one_item of accordion.item_list){
accordion_state_set(one_item, accordion_state_get(one_item));
}
}
/**
* CALLBACK: Item head was clicked.
*
* @param item Accordion item.
* @param accordion This item's accordion view object.
*/
function accordion_click(item, accordion){
// get our old state
let old_state = accordion_state_get(item);
// close all other items //
for(let one_item of accordion.item_list){
accordion_state_set(one_item, false);
}
// set our new state
accordion_state_set(item, !old_state);
}
/**
* GETTER: Get state of one accordion item.
*
* @param item Accordion item.
*/
function accordion_state_get(item){
return item.element.classList.contains("open");
}
/**
* SETTER: Set state of one accordion item.
*
* @param item Accordion item.
* @param state New state.
*/
function accordion_state_set(item, state){
// set observe state
if(state) accordion_resize_observer.observe(item.bodyContainer);
else accordion_resize_observer.unobserve(item.bodyContainer);
// update class
item.element.classList.remove("open");
if(state) item.element.classList.add("open");
// update icon direction
item.icon.classList.remove("ti-chevron-up", "ti-chevron-right", "ti-chevron-down", "ti-chevron-left");
if(state) item.icon.classList.add("ti-chevron-down");
else item.icon.classList.add("ti-chevron-right");
// set body height
if(state) accordion_body_resize(item.bodyContainer);
else item.bodyContainer.style.maxHeight = 0;
}
/**
* HELPER: Resize body height.
*
* @param bodyContainer Accordion body container.
*/
function accordion_body_resize(bodyContainer){
bodyContainer.style.maxHeight = bodyContainer.scrollHeight + "px";
}