added logic and styles for custom dropdown components based on <details> element
This commit is contained in:
parent
aeb63cd482
commit
dcafefadf6
@ -89,3 +89,41 @@ label:has([type="checkbox"]) {
|
|||||||
#navbar li[aria-current=page]>details>summary>a {
|
#navbar li[aria-current=page]>details>summary>a {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* correct inconsistent margins when using <details> in a form */
|
||||||
|
label > :where(div) {
|
||||||
|
margin-top: calc(var(--pico-spacing) * 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
label:has(details.dropdown) {
|
||||||
|
margin-bottom: calc(var(--pico-spacing) * 1.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* filter out elements by search */
|
||||||
|
.filtered-out {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* placeholder for <summary> */
|
||||||
|
summary:empty::before {
|
||||||
|
content: attr(data-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* badge styles for the selected items of a dropdown select field built with <details> and <summary> */
|
||||||
|
.dropdown-badge {
|
||||||
|
background-color: var(--pico-primary);
|
||||||
|
color: var(--pico-background-color);
|
||||||
|
font-size: .8em;
|
||||||
|
padding: calc(var(--pico-form-element-spacing-vertical) * .5) calc(var(--pico-form-element-spacing-horizontal) * .5);
|
||||||
|
margin: auto calc(var(--pico-spacing) * .25);
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-badge-close {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
form:not([novalidate]) input:user-valid[type="search"] {
|
||||||
|
border-color: inherit;
|
||||||
|
background-image: none;
|
||||||
|
}
|
||||||
@ -1,4 +1,7 @@
|
|||||||
|
// initializes the DataTable fumctionality for the given table
|
||||||
function initDataTable(table_id) {
|
function initDataTable(table_id) {
|
||||||
|
// add # to id if not there already
|
||||||
|
table_id = table_id.slice(0, 1) == "#" ? table_id : "#" + table_id
|
||||||
// initialize table
|
// initialize table
|
||||||
let table = new DataTable(table_id, {
|
let table = new DataTable(table_id, {
|
||||||
paging: false,
|
paging: false,
|
||||||
@ -13,34 +16,149 @@ function initDataTable(table_id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// create "New"-element and append it to the <div> containing the DataTables search field
|
// create "New"-element and append it to the <div> containing the DataTables search field
|
||||||
function initCreateButton(opts) {
|
function initCreateButton(table_id, title, href=null) {
|
||||||
let a = document.createElement("a");
|
// build button element
|
||||||
a.id = "create-button";
|
let button = document.createElement("button");
|
||||||
a.setAttribute("title", opts.title);
|
button.id = "create-button";
|
||||||
a.setAttribute("role", "button");
|
button.setAttribute("title", title);
|
||||||
a.setAttribute("href", opts.href || "#");
|
button.textContent = "Neu …";
|
||||||
a.innerHTML = "Neu …";
|
// wrap button inside an <a> if href is given
|
||||||
document.getElementById(`${opts.table_id.slice(0, 1) == "#" ? opts.table_id.slice(1) : opts.table_id}_wrapper`).firstElementChild.firstElementChild.appendChild(a);
|
if ( href ) {
|
||||||
|
let b = button;
|
||||||
|
button = document.createElement("a");
|
||||||
|
button.setAttribute("href", href);
|
||||||
|
button.appendChild(b);
|
||||||
|
}
|
||||||
|
// insert element
|
||||||
|
document.getElementById(`${table_id.slice(0, 1) == "#" ? table_id.slice(1) : table_id}_wrapper`).firstElementChild.firstElementChild.appendChild(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showDialog(opts) {
|
// adds event handlers that raise a modal
|
||||||
|
function initModal(modal_id, input_ids, headings, form_actions) {
|
||||||
|
// if input_ids is not an array, make it a single-element array
|
||||||
|
input_ids = Array.isArray(input_ids) ? input_ids : [ input_ids ];
|
||||||
|
// add event listener to "New" element
|
||||||
|
document.getElementById("create-button").addEventListener("click", () => showDialog(modal_id, input_ids, headings[0], form_actions[0], new Array(input_ids.length).fill(""), 0));
|
||||||
|
// add event listeners to "update" elements
|
||||||
|
for (const el of document.querySelectorAll('.action-update')) {
|
||||||
|
el.addEventListener("click", (event) => showDialog(modal_id, input_ids, headings[1], form_actions[1], input_ids.map((input) => event.currentTarget.dataset[input.slice(5).toLowerCase()]), event.currentTarget.dataset.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// raises a modal with the given options
|
||||||
|
function showDialog(modal_id, input_ids, heading, form_action, input_values, url_id) {
|
||||||
// if form action includes the string "update", the id at the end of the URL is a dummy and must be replaced with the correct id
|
// if form action includes the string "update", the id at the end of the URL is a dummy and must be replaced with the correct id
|
||||||
if ( opts.url_id && opts.form_action.includes("update") ) {
|
if ( form_action.includes("update") && url_id) {
|
||||||
opts.form_action = opts.form_action.slice(0, opts.form_action.lastIndexOf("/") + 1) + opts.url_id;
|
form_action = form_action.slice(0, form_action.lastIndexOf("/") + 1) + url_id;
|
||||||
}
|
}
|
||||||
// if input id or input value is not an array, make it a single-element array
|
// set modal attributes
|
||||||
opts.input_id = Array.isArray(opts.input_id) ? opts.input_id : [ opts.input_id ];
|
document.getElementById("dialog-heading").textContent = heading;
|
||||||
if ( opts.input_value ) {
|
document.getElementById("form_submit").formAction = form_action;
|
||||||
opts.input_value = Array.isArray(opts.input_value) ? opts.input_value : [ opts.input_value ];
|
for (var i = 0; i < input_ids.length; i++ ) {
|
||||||
} else {
|
document.getElementById(input_ids[i]).value = input_values[i] || "";
|
||||||
opts.input_value = new Array(opts.input_id.length).fill("");
|
|
||||||
}
|
}
|
||||||
|
// raise modal
|
||||||
console.log(`id[] is ${JSON.stringify(opts.input_id)}, value[] is ${JSON.stringify(opts.input_value)}`);
|
document.getElementById(modal_id).showModal();
|
||||||
document.getElementById("dialog-heading").textContent = opts.heading;
|
}
|
||||||
for (var i = 0; i < opts.input_id.length; i++ ) {
|
|
||||||
document.getElementById(opts.input_id[i]).value = opts.input_value[i] || "";
|
// validates a date to be of format YYYY, MM-YYYY, DD-MM-YYYY, or empty
|
||||||
}
|
function validate_date(tag_id="form_Erscheinungstag", monat_id="form_Erscheinungsmonat", jahr_id="form_Erscheinungsjahr") {
|
||||||
document.getElementById("form_submit").formAction = opts.form_action;
|
let t = document.getElementById(tag_id);
|
||||||
document.getElementById(opts.modal_id).showModal();
|
let m = document.getElementById(monat_id);
|
||||||
|
let j = document.getElementById(jahr_id);
|
||||||
|
t.setCustomValidity("");
|
||||||
|
t.setAttribute("aria-invalid", "false");
|
||||||
|
m.setCustomValidity("");
|
||||||
|
m.setAttribute("aria-invalid", "false");
|
||||||
|
console.log("This is function validate_date(): tag/monat/jahr is " + t.value + "/" + m.value + "/" + j.value); //DEBUG
|
||||||
|
if ( t.value != "" ) {
|
||||||
|
if ( j.value == "" || m.value == "" ) {
|
||||||
|
t.setCustomValidity("wenn der Tag angegeben ist, müssen Monat und Jahr ebenfalls angegeben sein");
|
||||||
|
t.setAttribute("aria-invalid", "true");
|
||||||
|
t.reportValidity();
|
||||||
|
}
|
||||||
|
} else if ( m.value != "" ) {
|
||||||
|
if ( j.value == "") {
|
||||||
|
m.setCustomValidity("wenn der Monat angegeben ist, muss das Jahr ebenfalls angegeben sein");
|
||||||
|
m.setAttribute("aria-invalid", "true");
|
||||||
|
m.reportValidity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize a dropdown select component
|
||||||
|
function initDropdownSelect(dropdown) {
|
||||||
|
// empty search field
|
||||||
|
let search = dropdown.querySelector("input[type='search']");
|
||||||
|
search.value = "";
|
||||||
|
// add event listener to search field
|
||||||
|
let input_name = dropdown.querySelector("input[type='checkbox']").getAttribute("name");
|
||||||
|
search.addEventListener("input", (event) => {
|
||||||
|
filterDropdownChoices(event.target, input_name);
|
||||||
|
});
|
||||||
|
// add event listener to checkboxes
|
||||||
|
let summary = dropdown.querySelector("summary");
|
||||||
|
document.querySelectorAll(`[name='${input_name}']`).forEach( (el) => {
|
||||||
|
el.addEventListener("change", (event) => {
|
||||||
|
updateDropdownSelected(event.target, summary);
|
||||||
|
});
|
||||||
|
// add badges for genre that were already selected
|
||||||
|
if ( el.checked ) {
|
||||||
|
el.dispatchEvent(new Event("change"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// event handler for genre search field; called whenever the search field's input value is changed
|
||||||
|
function filterDropdownChoices(that, checkbox_name) {
|
||||||
|
// show everything when search field is empty
|
||||||
|
if ( ! that.value || that.value == "" ) {
|
||||||
|
that.parentNode.parentNode.querySelectorAll("[name='" + checkbox_name + "']").forEach( (el) => {
|
||||||
|
el.parentNode.parentNode.classList.remove("filtered-out");
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// iterate over genre inputs
|
||||||
|
that.parentNode.parentNode.querySelectorAll("[name='" + checkbox_name + "']").forEach( (el) => {
|
||||||
|
// show/hide genre entry depending on whether genre name includes search string
|
||||||
|
let noHits = true;
|
||||||
|
if ( el.parentNode.textContent.toLowerCase().includes(that.value.toLowerCase()) ) {
|
||||||
|
el.parentNode.parentNode.classList.remove("filtered-out");
|
||||||
|
noHits = false;
|
||||||
|
} else {
|
||||||
|
el.parentNode.parentNode.classList.add("filtered-out");
|
||||||
|
}
|
||||||
|
if ( noHits ) {
|
||||||
|
console.log("everything has been filtered out")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// event handler for dropdown checkboxes; called whenever checkbox is checked or unchecked
|
||||||
|
function updateDropdownSelected(that, summary) {
|
||||||
|
if ( that.checked ) {
|
||||||
|
// add badge when checkbox was checked
|
||||||
|
let badge = document.createElement("span");
|
||||||
|
badge.id = that.id + "-badge";
|
||||||
|
badge.classList.add("dropdown-badge");
|
||||||
|
badge.textContent = that.parentNode.textContent;
|
||||||
|
summary.appendChild(badge);
|
||||||
|
// add closing X to badge
|
||||||
|
let badgeX = document.createElement("span");
|
||||||
|
badgeX.classList.add("dropdown-badge-close");
|
||||||
|
badgeX.innerHTML = "✖";
|
||||||
|
badge.appendChild(badgeX);
|
||||||
|
badgeX.addEventListener("click", removeDropdownBadge);
|
||||||
|
} else {
|
||||||
|
// remove badge when checkbox was unchecked
|
||||||
|
document.getElementById(that.id + "-badge").remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unselect a previously checked checkbox by clicking its badge's "X"
|
||||||
|
function removeDropdownBadge(event) {
|
||||||
|
let input_id = this.parentNode.id.slice(0, -6);
|
||||||
|
document.getElementById(input_id).checked = false;
|
||||||
|
this.parentNode.remove();
|
||||||
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user