"use strict";
// SPDX-License-Identifier: GPL-3.0-or-later
// myMPD (c) 2018-2024 Juergen Mang <mail@jcgames.de>
// https://github.com/jcorporation/mympd
/** @module elements_js */
/**
* Shortcut for elCreateTextTn for smartCount only
* @param {string} tagName name of the tag to create
* @param {object} attributes attributes to set
* @param {string} text phrase to translate
* @param {number} smartCount smart number for translation
* @returns {HTMLElement} created dom node
*/
function elCreateTextTnNr(tagName, attributes, text, smartCount) {
attributes["data-phrase-number"] = smartCount;
return elCreateTextTn(tagName, attributes, text, undefined);
}
/**
* Creates a html element and translates the text
* @param {string} tagName name of the tag to create
* @param {object} attributes tag attributes
* @param {string} text text phrase to translate
* @param {object} data object to resolve variables from the phrase
* @returns {HTMLElement} created dom node
*/
function elCreateTextTn(tagName, attributes, text, data) {
attributes["data-phrase"] = text;
if (data !== undefined) {
attributes["data-phrase-data"] = JSON.stringify(data);
}
if (attributes["data-phrase-number"] !== undefined) {
if (data === undefined) {
data = {};
}
//add smartCount to data from data-phrase-number attribute
data.smartCount = Number(attributes["data-phrase-number"]);
}
return elCreateText(tagName, attributes, tn(text, data));
}
/**
* Creates a html element with text content
* @param {string} tagName name of the tag
* @param {object} attributes tag attributes
* @param {string} text text for the elements, respects \n for newlines
* @returns {HTMLElement} created dom node
*/
function elCreateText(tagName, attributes, text) {
if (attributes["data-title-phrase"] !== undefined) {
attributes["title"] = tn(attributes["data-title-phrase"]);
}
const tag = elCreateEmpty(tagName, attributes);
if (text.length > 0) {
const lines = text.split(/\n/);
for (let i = 0, j = lines.length; i < j; i++) {
if (i > 0) {
tag.appendChild(document.createElement('br'));
}
tag.appendChild(document.createTextNode(lines[i]));
}
}
else {
tag.textContent = text;
}
return tag;
}
/**
* Creates a html element with a child node
* @param {string} tagName name of the tag
* @param {object} attributes tag attributes
* @param {Element | Node} node node to add as child
* @returns {HTMLElement} created dom node
*/
function elCreateNode(tagName, attributes, node) {
const tag = elCreateEmpty(tagName, attributes);
tag.appendChild(node);
return tag;
}
/**
* Creates a html element with child nodes
* @param {string} tagName name of the tag
* @param {object} attributes tag attributes
* @param {object} nodes array of nodes to add as childs
* @returns {HTMLElement} created dom node
*/
function elCreateNodes(tagName, attributes, nodes) {
const tag = elCreateEmpty(tagName, attributes);
for (const node of nodes) {
if (node !== null) {
tag.appendChild(node);
}
}
return tag;
}
/**
* Creates an empty html element
* @param {string} tagName name of the tag
* @param {object} attributes tag attributes
* @returns {HTMLElement} created dom node
*/
function elCreateEmpty(tagName, attributes) {
const tag = document.createElement(tagName);
for (const key in attributes) {
switch(key) {
case 'class':
tag.classList.add(...attributes[key]);
break;
case 'is':
tag.setAttribute('data-is', attributes[key]);
break;
default:
tag.setAttribute(key, attributes[key]);
}
}
return tag;
}
/**
* Clears the element with given id and appends the new child
* @param {string} id id of the parent element
* @param {Element | Node} child element to add
* @returns {void}
*/
function elReplaceChildId(id, child) {
elReplaceChild(elGetById(id), child);
}
/**
* Clears the given element and appends the new child
* @param {Element} el id of the parent element
* @param {Element | Node} child element to add
* @returns {void}
*/
function elReplaceChild(el, child) {
elClear(el);
el.appendChild(child);
}
/**
* Shortcut for elGetById
* @param {string} id element id
* @returns {HTMLElement} found element
*/
function elGetById(id) {
return document.getElementById(id);
}
/**
* Hides the element with the given id
* @param {string} id element id
* @returns {void}
*/
function elHideId(id) {
elGetById(id).classList.add('d-none');
}
/**
* Shows the element with the given id
* @param {string} id element id
* @returns {void}
*/
function elShowId(id) {
elGetById(id).classList.remove('d-none');
}
/**
* Clears the element with the given id
* @param {string} id element id to clear
* @returns {void}
*/
function elClearId(id) {
elGetById(id).textContent = '';
}
/**
* Hides the element
* @param {Element | EventTarget} el element to hide
* @returns {void}
*/
function elHide(el) {
el.classList.add('d-none');
}
/**
* Shows the element
* @param {Element} el element to show
* @returns {void}
*/
function elShow(el) {
el.classList.remove('d-none');
}
/**
* Clears the element
* @param {Element | ChildNode} el element to clear
* @returns {void}
*/
function elClear(el) {
el.textContent = '';
}
/**
* Disables the element with the given id
* @param {string} id element id
* @returns {void}
*/
function elDisableId(id) {
elDisable(elGetById(id));
}
/**
* Disables the element
* @param {Node} el element to disable
* @returns {void}
*/
function elDisable(el) {
el.setAttribute('disabled', 'disabled');
el.classList.replace('clickable', 'not-clickable');
}
/**
* Enables the element with the given id
* @param {string} id element id
* @returns {void}
*/
function elEnableId(id) {
elEnable(elGetById(id));
}
/**
* Enables the element
* @param {Element | Node} el element to enable
* @returns {void}
*/
function elEnable(el) {
el.removeAttribute('disabled');
el.classList.replace('not-clickable', 'clickable');
}
/**
* Triggers a layout reflow
* @param {Element} el element to trigger the reflow
* @returns {number} element height
*/
function elReflow(el) {
return el.offsetHeight;
}
/**
* Sets the focus on the element with given id for desktop view.
* @param {string} id element id
* @returns {void}
*/
function setFocusId(id) {
setFocus(elGetById(id));
}
/**
* Set the focus on the given element for desktop view.
* @param {HTMLElement} el element to focus
* @returns {void}
*/
function setFocus(el) {
if (userAgentData.isMobile === false) {
el.focus();
}
}
/**
* Sets an attribute on the element given by id.
* @param {string} id element id
* @param {string} attribute attribute name
* @param {object} value could be any type
* @returns {void}
*/
function setDataId(id, attribute, value) {
elGetById(id)['myMPD-' + attribute] = value;
}
/**
* Sets an attribute on the given element.
* @param {Element | Node | EventTarget} el element
* @param {string} attribute attribute name
* @param {object} value could be any type
* @returns {void}
*/
function setData(el, attribute, value) {
el['myMPD-' + attribute] = value;
}
/**
* Removes an attribute on the element given by id.
* @param {string} id element id
* @param {string} attribute attribute name
* @returns {void}
*/
//eslint-disable-next-line no-unused-vars
function rmDataId(id, attribute) {
elGetById(id)['myMPD-' + attribute] = undefined;
}
/**
* Removes an attribute on the given element.
* @param {Element | Node} el element
* @param {string} attribute attribute name
* @returns {void}
*/
//eslint-disable-next-line no-unused-vars
function rmData(el, attribute) {
el['myMPD-' + attribute] = undefined;
}
/**
* Gets the attributes value from the element given by id.
* @param {string} id element id
* @param {string} attribute attribute name
* @returns {object} attribute value
*/
function getDataId(id, attribute) {
return getData(elGetById(id), attribute);
}
/**
* Gets the attributes value from the element
* @param {Element | EventTarget} el element
* @param {string} attribute attribute name
* @returns {object} attribute value or undefined
*/
function getData(el, attribute) {
let value = el['myMPD-' + attribute];
if (value === undefined) {
//fallback to attribute
value = el.getAttribute('data-' + attribute);
if (value === null) {
//return undefined if attribute is null
value = undefined;
}
}
logDebug('getData: "' + attribute + '":"' + value + '"');
return value;
}
/**
* Gets the value of the selected option of a select element
* @param {string} id element id
* @returns {string} selected option value
*/
function getSelectValueId(id) {
return getSelectValue(elGetById(id));
}
/**
* Gets the value of the selected option of a select element
* or undefined if no option is selected
* @param {Element | EventTarget} el element
* @returns {string} selected option value
*/
function getSelectValue(el) {
if (el && el.selectedIndex >= 0) {
return el.options[el.selectedIndex].getAttribute('value');
}
return undefined;
}
/**
* Gets the attribute value of the selected option of a select element
* @param {string} id element id
* @param {string} attribute attribute name
* @returns {object} selected option data value
*/
function getSelectedOptionDataId(id, attribute) {
return getSelectedOptionData(elGetById(id), attribute);
}
/**
* Gets the attribute value of the selected option of a select element
* @param {Element} el element
* @param {string} attribute attribute name
* @returns {object} selected option data value
*/
function getSelectedOptionData(el, attribute) {
if (el && el.selectedIndex >= 0) {
return getData(el.options[el.selectedIndex], attribute);
}
return undefined;
}
/**
* Gets the value of the checked radio box
* @param {string} id element id
* @returns {string} radio box value
*/
function getRadioBoxValueId(id) {
return getRadioBoxValue(elGetById(id));
}
/**
* Gets the value of the checked radio box
* @param {Element} el element
* @returns {string} radio box value
*/
function getRadioBoxValue(el) {
const radiobuttons = el.querySelectorAll('.form-check-input');
for(const button of radiobuttons) {
if (button.checked === true){
return button.value;
}
}
return null;
}
/**
* Gets the x-position of the given element
* @param {Element} el element
* @returns {number} x-position
*/
function getXpos(el) {
let xPos = 0;
while (el) {
xPos += (el.offsetLeft - el.scrollLeft + el.clientLeft);
el = el.offsetParent;
}
return xPos;
}
/**
* Gets the y-position of the given element
* @param {Element | ParentNode} el element
* @returns {number} y-position
*/
function getYpos(el) {
let yPos = 0;
while (el) {
yPos += (el.offsetTop + el.clientTop);
el = el.offsetParent;
}
return yPos;
}
/**
* Gets the index of the element in the parent html collection
* @param {HTMLElement} el element to get the index
* @returns {number} the index
*/
function elGetIndex(el) {
return [...el.parentNode.children].indexOf(el);
}
/**
* Adds a waiting animation to a button
* @param {string} id id of the button
* @param {boolean} waiting true = add animation, false = remove animation
* @returns {void}
*/
function btnWaitingId(id, waiting) {
btnWaiting(elGetById(id), waiting);
}
/**
* Adds a waiting animation to a button
* @param {Node} btn id of the button
* @param {boolean} waiting true = add animation, false = remove animation
* @returns {void}
*/
function btnWaiting(btn, waiting) {
if (waiting === true) {
const spinner = elCreateEmpty('span', {"class": ["spinner-border", "spinner-border-sm", "me-2"]});
btn.insertBefore(spinner, btn.firstChild);
elDisable(btn);
}
else {
//add a small delay, user should notice the change
setTimeout(function() {
elEnable(btn);
if (btn.firstChild === null) {
return;
}
if (btn.firstChild.nodeName === 'SPAN' &&
btn.firstChild.classList.contains('spinner-border'))
{
btn.firstChild.remove();
}
}, 100);
}
}
/**
* Toggles a button group by value
* @param {string} id button group id
* @param {string | number} value value to select
* @returns {HTMLElement} selected button
*/
function toggleBtnGroupValueId(id, value) {
return toggleBtnGroupValue(elGetById(id), value);
}
/**
* Toggles a button group by value
* @param {Element} btngrp button group to toggle
* @param {string | number} value value to select
* @returns {HTMLElement} selected button
*/
function toggleBtnGroupValue(btngrp, value) {
const btns = btngrp.querySelectorAll('button');
//first button
let b = btns[0];
// @ts-ignore
const valuestr = isNaN(value) ? value : value.toString();
for (let i = 0, j = btns.length; i < j; i++) {
if (getData(btns[i], 'value') === valuestr) {
b = btns[i];
}
else {
btns[i].classList.remove('active');
}
}
b.classList.add('active');
return b;
}
/**
* Toggles a button group by value and toggle a collapse
* @param {Element} btngrp button group to toggle
* @param {string} collapseId id of element to collapse
* @param {string | number} value value to select
* @returns {void}
*/
function toggleBtnGroupValueCollapse(btngrp, collapseId, value) {
const activeBtn = toggleBtnGroupValue(btngrp, value);
if (activeBtn.getAttribute('data-collapse') === 'show') {
elGetById(collapseId).classList.add('show');
}
else {
elGetById(collapseId).classList.remove('show');
}
}
/**
* Toggles a button group by triggering element
* @param {string} id id of triggered button
* @returns {HTMLElement | EventTarget} active button
*/
//eslint-disable-next-line no-unused-vars
function toggleBtnGroupId(id) {
return toggleBtnGroup(elGetById(id));
}
/**
* Toggles a button group by triggering element
* @param {HTMLElement | EventTarget} btn triggered button
* @returns {HTMLElement | EventTarget} active button
*/
function toggleBtnGroup(btn) {
const btns = btn.parentNode.querySelectorAll('button');
for (let i = 0, j = btns.length; i < j; i++) {
if (btns[i] === btn) {
btns[i].classList.add('active');
}
else {
btns[i].classList.remove('active');
}
}
return btn;
}
/**
* Toggles a button group by triggering element and toggle a collapse
* @param {HTMLElement} el triggering button
* @param {string} collapseId id of element to collapse
* @returns {void}
*/
//eslint-disable-next-line no-unused-vars
function toggleBtnGroupCollapse(el, collapseId) {
const activeBtn = toggleBtnGroup(el);
if (activeBtn.getAttribute('data-collapse') === 'show') {
if (elGetById(collapseId).classList.contains('show') === false) {
uiElements[collapseId].show();
}
}
else {
uiElements[collapseId].hide();
}
}
/**
* Gets the value from the active button in a button group
* @param {string} id id of the button group
* @returns {object} value the value of the active button
*/
function getBtnGroupValueId(id) {
let activeBtn = document.querySelector('#' + id + ' > .active');
if (activeBtn === null) {
//fallback to first button
activeBtn = document.querySelector('#' + id + ' > button');
}
return getData(activeBtn, 'value');
}
/**
* Toggles the active state of a button
* @param {string} id id of button to toggle
* @param {boolean | number} state true, 1 = active; false, 0 = inactive
* @returns {void}
*/
//eslint-disable-next-line no-unused-vars
function toggleBtnId(id, state) {
toggleBtn(elGetById(id), state);
}
/**
* Toggles the active state of a button
* @param {HTMLElement | EventTarget} btn button to toggle
* @param {boolean | number} state true, 1 = active; false, 0 = inactive
* @returns {void}
*/
function toggleBtn(btn, state) {
if (state === undefined) {
//toggle state
state = btn.classList.contains('active') ? false : true;
}
if (state === true) {
btn.classList.add('active');
}
else {
btn.classList.remove('active');
}
}
/**
* Mirrors the button horizontal
* @param {string} id button id to mirror
* @param {boolean} mirror true = mirror, false = not
* @returns {void}
*/
function mirrorBtnId(id, mirror) {
mirrorBtn(elGetById(id), mirror);
}
/**
* Mirrors the button horizontal
* @param {HTMLElement | EventTarget} btn button to mirror
* @param {boolean} mirror true = mirror, false = not
* @returns {void}
*/
function mirrorBtn(btn, mirror) {
if (mirror === true) {
btn.classList.add('mirror');
}
else {
btn.classList.remove('mirror');
}
}
/**
* Gets the enabled state of a check button
* @param {string} id check button id
* @returns {boolean} enabled = true, disabled = false
*/
//eslint-disable-next-line no-unused-vars
function getBtnChkValueId(id) {
return getBtnChkValue(elGetById(id));
}
/**
* Gets the enabled state of a check button
* @param {HTMLElement | EventTarget} btn check button id
* @returns {boolean} enabled = true, disabled = false
*/
function getBtnChkValue(btn) {
return btn.classList.contains('active');
}
/**
* Toggles a check button
* @param {string} id id of the button to toggle
* @param {boolean} state true, 1 = active; false, 0 = inactive
* @returns {void}
*/
function toggleBtnChkId(id, state) {
toggleBtnChk(elGetById(id), state);
}
/**
* Toggles a check button
* @param {HTMLElement | EventTarget} btn the button to toggle
* @param {boolean | number} state true, 1 = active; false, 0 = inactive
* @returns {boolean} true if button is checked, else false
*/
function toggleBtnChk(btn, state) {
if (state === undefined) {
//toggle state
state = btn.classList.contains('active') ? false : true;
}
if (state === true) {
btn.classList.add('active');
btn.textContent = 'check';
return true;
}
else {
btn.classList.remove('active');
btn.textContent = 'radio_button_unchecked';
return false;
}
}
/**
* Toggles a check button and an assigned collapse
* @param {string} id the id of the triggering button
* @param {string} collapseId id of element to collapse
* @param {boolean | number} state true = active, false = inactive
* @returns {void}
*/
function toggleBtnChkCollapseId(id, collapseId, state) {
toggleBtnChkCollapse(elGetById(id), collapseId, state);
}
/**
* Toggles a check button and an assigned collapse
* @param {HTMLElement} btn triggering button
* @param {string} collapseId id of element to collapse
* @param {boolean | number} state true = active, false = inactive
* @returns {void}
*/
function toggleBtnChkCollapse(btn, collapseId, state) {
const checked = toggleBtnChk(btn, state);
if (checked === true) {
elGetById(collapseId).classList.add('show');
}
else {
elGetById(collapseId).classList.remove('show');
}
}
/**
* Gets the y-scrolling position
* @param {HTMLElement | Element} [el] element
* @returns {number} the vertical scrolling position
*/
function getScrollPosY(el) {
// element in scrolling modal
if (el) {
const modal = el.closest('.modal');
if (modal) {
let scrollPos = window.scrollY;
scrollPos += modal.scrollTop;
return scrollPos;
}
}
if (userAgentData.isMobile === true) {
// scrolling body
return document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop;
}
// scrolling container
const container = elGetById(app.id + 'List');
if (container) {
return container.parentNode.scrollTop;
}
return 0;
}
/**
* Scrolls the container or the window to the y-position
* @param {Element | ParentNode} container or null
* @param {number} pos position to scroll to
* @returns {void}
*/
function scrollToPosY(container, pos) {
if (userAgentData.isMobile === true ||
container === null)
{
elReflow(domCache.body);
// For Safari
document.body.scrollTop = pos;
// For Chrome, Firefox, IE and Opera
document.documentElement.scrollTop = pos;
}
else {
container.scrollTop = pos;
}
}
/**
* Enables all button in a btn-group
* @param {Element} el Button group
* @returns {void}
*/
function elEnableBtnGroup(el) {
const btns = el.querySelectorAll('button');
for (const btn of btns) {
btn.removeAttribute('disabled');
}
}
/**
* Disables all button in a btn-group
* @param {Element} el Button group
* @returns {void}
*/
function elDisableBtnGroup(el) {
const btns = el.querySelectorAll('button');
for (const btn of btns) {
btn.setAttribute('disabled', 'disabled');
}
}