262 lines
5.9 KiB
JavaScript
262 lines
5.9 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);
|
|
}
|
|
|
|
|
|
// HANDLE STATE CHANGE BY URL //
|
|
accordion_open_from_url_hash();
|
|
|
|
|
|
// INITIALIZE VIEWS //
|
|
for(let one_accordion of accordion_view_list){
|
|
accordion_init(one_accordion);
|
|
}
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
* 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";
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* HELPER: Maybe open a accordion item that is referenced in url.
|
|
*/
|
|
function accordion_open_from_url_hash(){
|
|
// FIND REFERENCED ITEM ELEMENT //
|
|
// get its id
|
|
let hash = window.location.hash;
|
|
if(hash.length < 2 || !hash.startsWith("#")) return;
|
|
let openId = hash.substring(1);
|
|
|
|
// find item element
|
|
let itemElement = document.getElementById(openId);
|
|
if(itemElement === null) return;
|
|
|
|
// maybe unwrap
|
|
if(itemElement.classList.contains("wrapper")) itemElement = itemElement.children[0] ?? null;
|
|
if(itemElement === null) return;
|
|
|
|
|
|
// SET TO OPEN //
|
|
// find item in list of accordion views
|
|
let foundAccordion = null;
|
|
let foundItem = null;
|
|
findLoop:
|
|
for(let one_accordion of accordion_view_list){
|
|
for(let one_item of one_accordion.item_list){
|
|
if(one_item.element === itemElement){
|
|
foundAccordion = one_accordion;
|
|
foundItem = one_item;
|
|
break findLoop;
|
|
}
|
|
}
|
|
}
|
|
if(foundAccordion === null || foundItem === null) return;
|
|
|
|
// close all items in this accordion
|
|
for(let one_item of foundAccordion.item_list){
|
|
accordion_state_set(one_item, false);
|
|
}
|
|
|
|
// set state to open
|
|
accordion_state_set(foundItem, true);
|
|
}
|