I have a <div>
wrapped around a set of nested <div>
elements; I'd like to present this as a drop-down menu. The element is part of a plugin I'm using on a WordPress website but this should still be adjustable using custom script.
I would like to replace the encasing div with a <ul>
, and turn all of the inner <div>
s into <li>
elements, so I can show this as a drop-down. Either that or a <select>
, with nested <option>
tags.
Is there a way I can change the HTML tag type , or replace the div, using pure JavaScript, or jQuery?
From:
<div >
<div ></div>
<div ></div>
</div>
to:
<ul >
<li ></li>
<li ></li>
</ul>
or:
<select >
<option ></option>
<option ></option>
</select>
The isolated code: https://codepen.io/bolti95/pen/rNpMdJx
//div into list
const slots_list = document.createElement("ul");
var slots = document.getElementsByClassName("slots")
console.log(slots)
slots.insertBefore(slots_list, slots.children[0])
.slots {
width: 100% !important;
height: max-content;
border: none !important;
box-shadow: none !important;
margin: auto;
}
.slots div a {
color: #77635A;
background: none !important;
}
.slots div a:hover {
background-color: #F7F7F7 !important;
transition: 0.6s;
}
.availableslot {
width: 120px;
height: 55px;
border-radius: 30px;
margin-bottom: 25px;
margin: 10px;
border: none !important;
border-width: 0px;
text-align: center;
font-size: 20px !important;
background-color: #EFE8DA;
color: #77635A;
}
.availableslot a {
margin: 10px;
}
.availableslot:hover {
outline: 1px solid #5FDEAB;
background-color: #F7F7F7;
transition: 0.6;
}
.availableslot a:hover {
background-color: #F7F7F7;
color: #5FDEAB;
transition: 0.6s;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="<?php bloginfo('charset'); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div >
<span>03/22/2022</span>
<br>
<div ><a>11:00</a></div>
<div ><a>12:00</a></div>
<div ><a>13:00</a></div>
<div ><a>14:00</a></div>
<div ><a>15:00</a></div>
</div>
</body>
</html>
CodePudding user response:
To convert the .availableslot
div elements to a ul
/li
list you can use a combination of wrapAll()
and replaceWith()
, like this:
$('.availableslot').wrapAll('<ul />').replaceWith(function() {
return `<li >${this.innerHTML}</li>`
});
$('.availableslot').wrapAll('<ul />').replaceWith(function() {
return `<li >${this.innerHTML}</li>`
});
.slots {
width: 100% !important;
height: max-content;
border: none !important;
box-shadow: none !important;
margin: auto;
}
.slots div a {
color: #77635A;
background: none !important;
}
.slots div a:hover {
background-color: #F7F7F7 !important;
transition: 0.6s;
}
.availableslot {
width: 120px;
height: 55px;
border-radius: 30px;
margin-bottom: 25px;
margin: 10px;
border: none !important;
border-width: 0px;
text-align: center;
font-size: 20px !important;
background-color: #EFE8DA;
color: #77635A;
}
.availableslot a {
margin: 10px;
}
.availableslot:hover {
outline: 1px solid #5FDEAB;
background-color: #F7F7F7;
transition: 0.6;
}
.availableslot a:hover {
background-color: #F7F7F7;
color: #5FDEAB;
transition: 0.6s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div >
<span>03/22/2022</span>
<br>
<div ><a>11:00</a></div>
<div ><a>12:00</a></div>
<div ><a>13:00</a></div>
<div ><a>14:00</a></div>
<div ><a>15:00</a></div>
</div>
The same technique can be used to convert them to a select
/option
form control:
$('.availableslot').wrapAll('<select />').replaceWith(function() {
let time = this.querySelector('a').innerText;
return `<option value="${time}">${time}</option>`;
});
$('.availableslot').wrapAll('<select />').replaceWith(function() {
let time = this.querySelector('a').innerText;
return `<option value="${time}">${time}</option>`;
});
.slots {
width: 100% !important;
height: max-content;
border: none !important;
box-shadow: none !important;
margin: auto;
}
.slots div a {
color: #77635A;
background: none !important;
}
.slots div a:hover {
background-color: #F7F7F7 !important;
transition: 0.6s;
}
.availableslot {
width: 120px;
height: 55px;
border-radius: 30px;
margin-bottom: 25px;
margin: 10px;
border: none !important;
border-width: 0px;
text-align: center;
font-size: 20px !important;
background-color: #EFE8DA;
color: #77635A;
}
.availableslot a {
margin: 10px;
}
.availableslot:hover {
outline: 1px solid #5FDEAB;
background-color: #F7F7F7;
transition: 0.6;
}
.availableslot a:hover {
background-color: #F7F7F7;
color: #5FDEAB;
transition: 0.6s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div >
<span>03/22/2022</span>
<br>
<div ><a>11:00</a></div>
<div ><a>12:00</a></div>
<div ><a>13:00</a></div>
<div ><a>14:00</a></div>
<div ><a>15:00</a></div>
</div>
CodePudding user response:
One approach, using plain JavaScript, is below; explanatory comments are in the code:
// creating a named function, using Arrow syntax, that allows the user to pass in an
// Object of updated preferences:
const replaceWith = (opts) => {
let settings = {
// String, sets the element-type to replace the outer wrapper:
outerTo: 'select',
// String, sets the element-type to replace the inner wrapper:
innerTo: 'option',
// String, CSS selector to select the relevant elements:
source: '.slots',
// Boolean, true: replaces the element when its replacement is inserted into the document,
// false: hides the element when its replacement is inserted into the document:
replace: true,
// Boolean, true: preserves the child-nodes of the inner-elements being replaced,
// false: preserves the text-content of the inner-element, but does not
// keep the child-nodes that may have contributed text:
preserveHTMLChildren: false,
};
// while I'm sure there's an easy way to use destructruring to avoid this step, I chose to
// use this approach, wherein we take the keys of the opts Object passed to the function
// or - if no Object is passed - we look at the empty Object-literal (to avoid errors),
// we then iterate over the Array of Object-keys using Array.prototype.forEach(), again
// using Arrow syntax:
Object.keys(opts || {}).forEach(
// the 'key' passes in a reference to the current key of the Array of Object-keys
// over which we're iterating, and in the function we set the Object-key of the
// settings Object to the value of the opts Object key-value:
(key) => settings[key] = opts[key]
);
// to reduce unnecessary typing, we alias the document to the D variable:
let D = document,
// we create an element according to the desired elements:
outer = D.createElement(settings.outerTo),
inner = D.createElement(settings.innerTo),
// we use document.querySelectorAll(), using the supplied, or default,
// CSS selector, to retrieve all matching elements:
sources = [...D.querySelectorAll(settings.source)];
// we use NodeList.prototype.forEach() to iterate over the NodeList of matching
// elements returned to the sources variable:
sources.forEach(
// using Arrow syntax we pass in a reference to the current Node ('source') of
// the NodeList:
(source) => {
// we clone the created-element for the outer-element's replacement:
let outerClone = outer.cloneNode();
// we copy the classList:
outerClone.classList = source.classList;
// we use an Array-literal with the spread syntax to create an Array of
// the child-nodes of the current 'source', and then iterate over that
// Array of Nodes using Array.prototype.forEach():
[...source.children].forEach(
// we pass in a reference to the current child-element of the parent
// 'source', in the variable named 'child':
(child) => {
// we clone the created-element for the inner element:
let innerClone = inner.cloneNode();
// if settings.preserveHTMLChildren is exactly equal to Boolean true:
if (settings.preserveHTMLChildren === true) {
// while there is a firstChild node (whether comment, text, HTMLElement...):
while (child.firstChild) {
// we copy that firstChild Node accross to the innerClone element:
innerClone.append(child.firstChild);
}
} else {
// we update the text-content of the innerClone to be equal to the
// original text-content:
innerClone.textContent = child.textContent;
}
// copying the classList of the existing child to the innerClone:
innerClone.classList = child.classList;
// we append the current innerClone to the outerClone:
outerClone.append(innerClone);
});
// we then access the current 'source' element's parentNode, and
// use ParentNode.insertBefore() to insert the newly-created
// outerClone before the current 'source' element's next-sibling
// (effectively inserting it after the current 'source' element):
source.parentNode.insertBefore(outerClone, source.nextSibling);
// if the user wants to replace the element, so settings.replace is
// exactly-equal to (Boolean) true:
if (settings.replace === true) {
// we remove the current 'source' element, using Node.remove():
source.remove();
} else {
// otherwise we set source.hidden to 'true' to hide the element
// using the 'hidden' HTML attribute (though in the demo I chose
// to modify the opacity to show that the element does, in fact,
// remain in the document); this should probably be modified when
// in use:
source.style.opacity = 0.4;
// source.hidden = true;
}
})
};
// calling the function:
replaceWith({
// using a non-default CSS Selector:
source: '#demo1'
});
// calling the functiona again:
replaceWith({
// modifying the CSS selector again:
source: '#demo2',
// using a <ul> to 'replace' the outer element:
outerTo: 'ul',
// using an <li> to wrap the inner contents:
innerTo: 'li',
// setting Boolean false, to not replace the original
// element(s), so inserting the new element(s) and hiding
// the original(s):
replace: false,
});
replaceWith({
source: '#demo3',
outerTo: 'ul',
innerTo: 'li',
// setting preserveHTMLChildren to true means the <span> elements
// contained within the inner-<div> elements will be preserved when
// the HTML is modified:
preserveHTMLChildren: true,
});
replaceWith({
source: '#demo4',
// again, setting preserveHTMLChildren to true means the <span> elements
// contained within the inner-<div> elements will be preserved when
// the HTML is modified; but while those <span> elements may be present
// in the DOM (in FF 98/Ubuntu 21.10), they are not styled by the CSS
// (and an <option> element has no valid child-elements), so this isn't
// necessarily going to be respected by the browser:
preserveHTMLChildren: true,
});
*,
::before,
::after {
box-sizing: border-box;
font: normal 400 1rem / 1.5 sans-serif;
list-style-type: none;
margin: 0;
padding: 0;
}
.slots {
border: 1px solid palegreen;
margin: 1em auto;
padding: 0.5em;
width: 20rem;
}
.slots:not([id]) {
border: 1px solid rebeccapurple;
}
.slots span {
color: #f90;
}
<div id="demo1" >
<div >available 1</div>
<div >available 2</div>
</div>
<div id="demo2" >
<div >available 3</div>
<div >available 4</div>
</div>
<div id="demo3" >
<div >available <span>5</span></div>
<div >available <span>6</span></div>
</div>
<div id="demo4" >
<div >available <span>7</span></div>
<div >available <span>8</span></div>
</div>
I have to admit that I'm curious as to why the <select>
elements aren't being positioned correctly (in terms of margin-inline: auto
), but that's a problem for another time.
If there are any questions then please don't hesitate to leave a comment below.
References:
Array.prototype.forEach()
.- Array literals
[ /*...*/ ]
. - Arrow functions.
document.createDocumentFragment()
.document.createElement()
.document.querySelectorAll()
.Element.children()
.Element.classList
.Element.remove()
.Node.cloneNode()
.Node.firstChild
.Node.insertBefore()
.Node.parentNode
.NodeList.prototype.forEach()
.Object.keys()
.- Spread syntax
...
. while (...) {...}
statement.