Re: How to target child nodes in HTML collection - javascript

I am new to programming and this is my first question. The problem I am having is I am trying to use DOM manipulation on all the child nodes of an html collection. I am expecting the nodes to change background color when they are hovered. Here is what I have tried so far:
let x = 0;
do{
const square = document.createElement("div");
square.className = "squares";
square.setAttribute("id","block");
document.getElementById("container").appendChild(square);
x++;
}
while(x < 16);
var container = document.getElementById("container");
var cells = container.childNodes;
cells.forEach(function(){
cells.onmouseover = function(){
cells.style.backgroundColor = "black";
}
});
console.log(`${cells.length}`);
This doesn't work even though console.log shows 16 child nodes being targeted.
var container = document.getElementById("container");
var cells = container.children[0];
cells.onmouseover = function(){
cells.style.backgroundColor = "black";
}
I have tried this and can use index but of course only that cell will change bg color. I want any cell that is hovered to change also.
I am at a loss for what I am doing wrong here. If anyone can point me in the right direction I would greatly appreciate it.

Welcome to Stack Overflow.
There is an issue in your forEach cycle. Consider the following:
cells.forEach(cell => {
cell.onmouseover = () => {
cell.style.backgroundColor = "black"
}
})
Note that you need to refer to cycle variable instead of the cells array.

Instead of attaching listeners to all the squares you can use event delegation and just have one listener on the container that captures the events from its children as they "bubble up" the DOM.
// Cache the container element, and add a listener to it
const container = document.querySelector('.container');
container.addEventListener('mouseover', handleMouse);
// Create some squares HTML by pushing template
// strings into an array
const html = [];
for (let i = 1; i < 10; i++) {
html.push(`<div class="square">${i}</div>`);
}
// Add that HTML to the container making sure
// we join the array of strings into one string
container.innerHTML = html.join('');
// When a event is fired check that it was
// was from an element with a square class
// and then add an active class to it
function handleMouse(e) {
if (e.target.matches('.square')) {
e.target.classList.add('active');
}
}
.container { display: grid; grid-template-columns: repeat(3, 50px); grid-gap: 0.2em; }
.square { font-size: 1.2em; padding: 0.7em 0.2em; background-color: #565656; color: white; text-align: center; }
.square.active { background-color: thistle; color: black; cursor: pointer; }
<div class="container"></div>
Additional documentation
Template/string literals

Related

html input create li element how to set an id for the element

I'm new with HTML & JS and I face the following problem:
I have an input in html that creates a new li Element (in combination with JS); is it possible to give every newly-created li element its own id? For example to delete an specific element?
For Example:
<li id="one"> .. </li>
<li id="two"> .. </li>
So far it creates only <li> ... </li>
I think it can be done with a for loop, but I have no idea how to use it in my case.
See my JS code below:
function NewEntry() {
var Inputfield = document.getElementById("Inputfield");
var AddButton = document.getElementById("AddButton");
var ul = document.querySelector("ul");
var li = document.createElement("li");
li.appendChild(document.createTextNode(Input.value));
ul.appendChild(li);
Input.value = "";
I tried to insert a for loop into my code, but after that it doesn't add any elements.
function NewEntry() {
var Inputfield = document.getElementById("Inputfield");
var AddButton = document.getElementById("AddButton");
var ul = document.querySelector("ul");
var li = document.createElement("li");
for (var i = 0; i < li.length; i++)
li[i].id = 'abc-' + i;
li.appendChild(document.createTextNode(Input.value));
ul.appendChild(li);
Input.value = "";
Your for loop needs curly braces to work properly:
function NewEntry() {
var Inputfield = document.getElementById("Inputfield");
var AddButton = document.getElementById("AddButton");
var ul = document.querySelector("ul");
var li = document.createElement("li");
for (var i = 0; i < li.length; i++) {
abcElements[i].id = 'abc-' + i;
li.appendChild(document.createTextNode(Inputfield.value));
ul.appendChild(li);
}
Inputfield.value = "";
}
Otherwise only the immediate line after the for statement will run as part of the loop.
There also appeared to be a typo - you had Input instead of Inputfield? But I notice there are some other variables used here which are not defined, so I assume some extra code was omitted?
You could count the number of elements inside the <ul> and use that as id for the <li>:
var AddButton = document.getElementById("AddButton");
AddButton.addEventListener("click", NewEntry);
function NewEntry() {
var Inputfield = document.getElementById("Inputfield");
var ul = document.querySelector("ul");
var li = document.createElement("li");
li.id = ul.childElementCount;
li.appendChild(document.createTextNode(Inputfield.value));
ul.appendChild(li);
Inputfield.value = "";
console.log(li);
}
<input type="text" id="Inputfield" />
<button id="AddButton">Add</button>
<ul></ul>
One simple means by which this could be accomplished is as follows, with explanatory comments in the JavaScript:
// a generator function, to generate a constantly increasing counter:
const generator = function*() {
// the initial value of the counter:
let i = 0;
// this loop causes the generator to return a number infinitely
// though while the loop is infinite it yields one value and then
// pauses until the next call; this is one instance where an
// infinite loop is deliberate and not a problem:
while (true) {
// increments the value of i, and then returns it to the
// calling context:
yield ++i;
}
},
// assigning a reference to the generator:
counter = generator(),
// a simple Arrow function that takes two arguments:
// tag: String, the element-type to create, and
// props: an Object of element-properties and values to
// assign to the created element:
create = (tag, props) => Object.assign(document.createElement(tag), props),
// a reference to the various elements to which event-listeners are attached:
list = document.querySelector('#list'),
form = document.querySelector('form'),
input = document.querySelector('input'),
button = document.querySelector('#add'),
// the addNew function, written as an Arrow function:
addNew = () => {
// caching the source of the text:
const inputField = document.querySelector('input');
// here we take the value, trim it to remove leading and trailing
// white-space, and then retrieve its length; if there is a zero
// length (so either nothing was entered, or only whitespace) then
// we return here:
if (inputField.value.trim().length === 0) {
return false;
}
// otherwise we cache a reference to the element to which the
// created <li> element will be appended:
const ul = document.querySelector('#list'),
// we call the create() function, here creating an <li>
// element, and passing in an object of properties and
// values:
li = create('li', {
// we set the 'id' - using a template literal string - to
// the string of 'abc-' plus next-value of the counter:
id: `abc-${counter.next().value}`,
// and set the text-content of the element to the
// trimmed value of the <input>:
textContent: inputField.value.trim()
}),
// here we create a <button>,
deleteButton = create('button', {
// with its className set to 'delete':
className: 'delete',
// its text-content set to 'delete task':
textContent: 'delete task',
// and its type set to 'button' in order to
// prevent the <button> submitting the <form>:
type: 'button'
});
// we append the created <button> to the created <li>:
li.append(deleteButton);
// we append the <li> to the <ul>:
ul.append(li);
// and reset the value of the <input> to an empty string:
inputField.value = '';
};
// here we prevent form submission:
form.addEventListener('submit', (e) => e.preventDefault());
// here we bind an anonymous function as the event-handler of the
// 'keyup' event on the <input> element:
input.addEventListener('keyup', (evt) => {
// if the, or an, 'Enter' key is pressed (note that evt.keyCode is
// almost entirely deprecated now, but is here for backwards
// compatibility, it can and possibly should be removed):
if (evt.key === 'Enter' || evt.keyCode === 13) {
// here we call the addNew() function:
addNew();
}
});
// we bind the addNew() function - note the deliberate lack of
// parentheses - as the event-handler for the 'click' event:
button.addEventListener('click', addNew);
// because we're adding the delete <button> elements we delegate the
// event-handling to the closest ancestor element present in the DOM
// on page-load, here that's the #list element. Here we bind the
// anonymous function as the event-handler for the 'click' event
// which bubbles to the <ul>:
list.addEventListener('click', (evt) => {
// if the element on which the click event fired has an ancestor
// .delete element (therefore it was fired on, or in, the .delete
// <button>):
if (evt.target.closest('.delete')) {
// we navigate from the event-target to the closest <li>
// ancestor element and use Element.remove() to remove that
// element from the document:
evt.target.closest('li').remove();
}
})
*,
::before,
::after {
box-sizing: border-box;
font-family: system-ui;
font-size: 16px;
margin: 0;
padding: 0;
}
main {
inline-size: clamp(15em, 80vw, 1000px);
margin-inline: auto;
}
h2 {
font-size: 1.4em;
text-align: center;
}
fieldset {
display: grid;
padding: 0.5em;
}
legend {
border-inline: 1px solid currentColor;
border-radius: 0.25em;
margin-inline: 1em;
padding-inline: 0.5em;
}
label {
display: flex;
gap: 1em;
justify-content: space-between;
margin-block: 0.5em;
}
label input[type=text] {
flex-grow: 1;
}
#list {
border: 1px solid currentColor;
display: grid;
gap: 0.5em;
list-style-type: none;
margin-block: 1em;
margin-inline: auto;
padding: 0.5em;
}
#list:empty::before {
color: hsl(0deg 30% 30% / 0.8);
content: "A beautiful, quiet day of reflection...";
font-style: italic;
}
#list li {
background-color: lavender;
display: grid;
padding: 0.25em;
}
#list li:nth-child(odd) {
background-color: lightcyan;
}
li[id]::before {
content: "#" attr(id) ": ";
}
.delete {
justify-self: end;
}
<main>
<section>
<h2>To do list</h2>
<form action="#" method="post">
<fieldset>
<legend>New task</legend>
<label>
<span class="labelText">What do you want to do today?</span>
<input type="text">
</label>
<button id="add" type="button">Add task to list</button>
</fieldset>
</form>
<ul id="list"></ul>
</section>
</main>
JS Fiddle demo.
It's worth noting that there is one potential issue using a generator function to generate a counter for an id property; the counter will only ever increase, so if you have three elements abc-1, abc-2, abc-3 and then delete those, and create another three elements those elements will continue from abc-4. So the id will be independent of the number of created elements. This may be a benefit in that a duplicate value can't be created, so there's little chance of a duplicate id being created by a generator.
References:
Arrow functions.
document.createElement().
document.querySelector().
Element.append().
Element.remove().
EventTarget.addEventListener().
Generator functions (function*(){...}).
Object.assign().
String.prototype.trim().
Template literals.
while loop.

DOM Event.StopPropagation to child has no effect

I'm doing a tutorial about Events and I'm stuck with Event.StopPropagation() method. It seems that with my example that the bubbling effect to the children are being affected.
Definition of StopPropagation:
The stopPropagation() method prevents propagation of the same event from being called.
Propagation means bubbling up to parent elements or capturing down to child elements.
At first I thought it was a browser problem but it was not the case. I can't find a solution about this.
Code:
// Event Bubbling and Propagation
// element.addEventListener( type, func, useCapture);
let m = document.getElementById('m');
let d = document.getElementById('d');
let p = document.getElementById('p');
let s = document.getElementById('s');
let highlight = (ev)=>{
//add CSS class "gold" to the clicked element
ev.stopPropagation();
let target = ev.currentTarget;
target.className = 'gold';
reset(target);
}
let reset = (_element)=>{
setTimeout(()=>{
_element.className = '';
}, 2000);
}
d.addEventListener('click', (ev)=>{
ev.stopImmediatePropagation();
log('Hi I\'m a DIV');
});
[m,d,p,s].forEach((element)=>{
element.addEventListener('click', highlight);
})
#m,#d,#p,#s{
border: 2px solid black;
padding: 15px;
margin: 10px;
}
.gold{
background-color: gold;
}
<main id="m"> m
<div id="d"> d
<p id="p"> p
<span id="s"> s</span>
</p>
</div>
</main>
This is not a problem with events, you are overthinking this.
You example is simply applying background color to top element, and as children do not have it defined, its applying the top one below it.
And if you remove stopImmediatePropagation() color will be applied as definition is: execute the first event handler, and stop the rest of the event handlers from being executed, and the first one was simply log().
In example below, if you apply background color to child elements, you will see they will stay the same. The color it self is applied only on clicked one.
That means the JS event itself did not bubble up to parent elements or capture down. And class was added only on clicked one. Check it with dev tools or add DOM change event listener on every element.
You confused CSS styling with JS event bubbling.
Example:
// Event Bubbling and Propagation
// element.addEventListener( type, func, useCapture);
let m = document.getElementById('m');
let d = document.getElementById('d');
let p = document.getElementById('p');
let s = document.getElementById('s');
let log = console.log;
let highlight = (ev)=>{
//add CSS class "gold" to the clicked element
ev.stopPropagation();
let target = ev.currentTarget;
target.className = 'gold';
reset(target);
}
let reset = (_element)=>{
setTimeout(()=>{
_element.className = '';
}, 2000);
}
d.addEventListener('click', (ev)=>{
//ev.stopImmediatePropagation();
log('Hi I\'m a DIV');
});
[m,d,p,s].forEach((element)=>{
element.addEventListener('click', highlight);
})
#m,#d,#p,#s{
border: 2px solid black;
padding: 15px;
margin: 10px;
}
#d {
background-color: blue;
}
#s {
background-color: red;
}
.gold{
background-color: gold !important;
}
<main id="m"> m
<div id="d"> d
<p id="p"> p
<span id="s"> s</span>
</p>
</div>
</main>
EDIT:
In your example child nodes do not have background property (rgba(0, 0, 0, 0)), that means you need to set it on click, so that background color of clicked element wont apply on its children.
In code below, you can read the CSS background value of parent element of clicked one, and read its background property. That you take that and apply on all children of clicked one.
This will work in your example.
You can also make sure to apply white to elements if parent was transparent.
Here is a fiddle to play with:
but make sure you will un-comment m (parent) background color to see side effects. You would need to adjust this to suit your production needs
const style = getComputedStyle(target.parentNode);
const backgroundColor = style.backgroundColor;
console.clear();
console.log(backgroundColor);
[...target.children].forEach(el => {
if (backgroundColor==="rgba(0, 0, 0, 0)") {
el.style.backgroundColor = "white";
}else{
el.style.backgroundColor = backgroundColor;}
})
// Event Bubbling and Propagation
// element.addEventListener( type, func, useCapture);
let m = document.getElementById('m');
let d = document.getElementById('d');
let p = document.getElementById('p');
let s = document.getElementById('s');
let log = console.log;
let highlight = (ev)=>{
//add CSS class "gold" to the clicked element
ev.stopPropagation();
let target = ev.currentTarget;
target.className = 'gold';
const style = getComputedStyle(target.parentNode);
const backgroundColor = style.backgroundColor;
console.clear();
console.log(backgroundColor);
[...target.children].forEach(el => {
if (backgroundColor==="rgba(0, 0, 0, 0)") {
el.style.backgroundColor = "white";
}else{
el.style.backgroundColor = backgroundColor;}
})
reset(target);
}
let reset = (_element)=>{
setTimeout(()=>{
_element.className = '';
}, 2000);
}
[m,d,p,s].forEach((element)=>{
element.addEventListener('click', highlight);
})
#m,#d,#p,#s{
border: 2px solid black;
padding: 15px;
margin: 10px;
}
#m {
background-color: blue;
}
.gold{
background-color: gold !important;
}
<main id="m"> m
<div id="d"> d
<p id="p"> p
<span id="s"> s</span>
</p>
</div>
</main>

How would I make specific boxes changes color upon clicking on them?

I'd like to change color of more than one box to purple upon clicking on it. With my current code below, only one box gets colored purple when clicking on it.
I've tried so many different ways to make it work in terms of upon you clicking on any number of boxes, the box should turn purple, but all my attempts have failed.
What am I doing wrong?
function createBoxesDynamically() {
var tileLength = Math.floor(Math.random() * 11);
console.log("tileLength " + tileLength);
var box = new Array(tileLength);
console.log("box" + box);
for (var i = 0; i < box.length; i++) {
box[i] = "box";
}
for (var j = 0; j < box.length; j++) {
var div = document.createElement("div");
div.id = "box";
document.body.appendChild(div);
}
var boxes = document.querySelector("[id^=box]");
boxes.addEventListener("click", function () {
boxes.style.backgroundColor = "purple";
});
}
createBoxesDynamically();
#box {
border: 1px solid;
width: 50px;
height: 50px;
background-color: green;
}
You can't have multiple elements with identical id values, that's why no matter which box you click, the first one is always affected, your .querySelector() call stops looking after finding the first match.
Instead, move the code that sets up the event handler inside the loop where the box is being created and just use this in the click callback to have the callback act upon the box that was clicked. No id necessary. And, because you won't be using ids, you don't need your array or the first loop.
In general, stay away from coding solutions that rely on ids. Yes, they seem precise and easy to use at first, but what you'll find (and you already are) is that they create very brittle solutions that don't scale very well. There are many other ways of referencing and styling elements besides an id.
You should also try to avoid inline styling of elements (setting up styles directly on the style property) as this usually leads to duplication of code and therefore makes the code more difficult to read and maintain. Use CSS classes for as much as you can.
function createBoxesDynamically() {
var tileLength = Math.floor(Math.random() * 11);
console.log("tileLength " + tileLength);
for (var j = 0; j < tileLength; j++) {
var div = document.createElement("div");
div.classList.add("box"); // Add the CSS class to the element
div.addEventListener("click", function () {
this.classList.add("clickColor");;
});
document.body.appendChild(div);
}
}
createBoxesDynamically();
/* Use Classes instead of IDs */
.box {
border: 1px solid;
width: 50px;
height: 50px;
background-color: green;
}
.clickColor { background-color: #800080; }

Know when flex-box puts item to new row [duplicate]

I have flex container with items inside. How to detect flex wrap event? I want to apply some new css to elements that have been wrapped. I suppose that it is impossible to detect wrap event by pure css. But it would be very powerful feature! I can try to "catch" this break point event by media query when element wraps into new line/row. But this is a terrible approach. I can try to detect it by script, but it's also not very good.
I am very surprised, but simple $("#element").resize() doesn't work to detect height or width changes of flex container to apply appropriate css to child elements. LOL.
I have found that only this example of jquery code works
jquery event listen on position changed
But still terribly.
Here's one potential solution. There might be other gotchas and edge cases you need to check for.
The basic idea is to loop through the flex items and test their top position against the previous sibling. If the top value is greater (hence further down the page) then the item has wrapped.
The function detectWrap returns an array of DOM elements that have wrapped, and could be used to style as desired.
The function could ideally be used with a ResizeObserver (while using window's resize event as a fallback) as a trigger to check for wrapping as the window is resized or as elements in the page change due to scripts and other user-interaction. Because the StackOverflow code window doesn't resize it won't work here.
Here's a CodePen that works with a screen resize.
var detectWrap = function(className) {
var wrappedItems = [];
var prevItem = {};
var currItem = {};
var items = document.getElementsByClassName(className);
for (var i = 0; i < items.length; i++) {
currItem = items[i].getBoundingClientRect();
if (prevItem && prevItem.top < currItem.top) {
wrappedItems.push(items[i]);
}
prevItem = currItem;
};
return wrappedItems;
}
window.onload = function(event){
var wrappedItems = detectWrap('item');
for (var k = 0; k < wrappedItems.length; k++) {
wrappedItems[k].className = "wrapped";
}
};
div {
display: flex;
flex-wrap: wrap;
}
div > div {
flex-grow: 1;
flex-shrink: 1;
justify-content: center;
background-color: #222222;
padding: 20px 0px;
color: #FFFFFF;
font-family: Arial;
min-width: 300px;
}
div.wrapped {
background-color: red;
}
<div>
<div class="item">A</div>
<div class="item">B</div>
<div class="item">C</div>
</div>
Little bit improved snippet on jQuery for this purpose.
wrapped();
$(window).resize(function() {
wrapped();
});
function wrapped() {
var offset_top_prev;
$('.flex-item').each(function() {
var offset_top = $(this).offset().top;
if (offset_top > offset_top_prev) {
$(this).addClass('wrapped');
} else if (offset_top == offset_top_prev) {
$(this).removeClass('wrapped');
}
offset_top_prev = offset_top;
});
}
I've modified sansSpoon's code to work even if the element isn't at the absolute top of the page. Codepen: https://codepen.io/tropix126/pen/poEwpVd
function detectWrap(node) {
for (const container of node) {
for (const child of container.children) {
if (child.offsetTop > container.offsetTop) {
child.classList.add("wrapped");
} else {
child.classList.remove("wrapped");
}
}
}
}
Note that margin-top shouldn't be applied to items since it's factored into getBoundingClientRect and will trigger the wrapped class to apply on all items.
I'm using a similar approach in determining if a <li> has been wrapped in an <ul> that has it's display set to flex.
ul = document.querySelectorAll('.list');
function wrapped(ul) {
// loops over all found lists on the page - HTML Collection
for (var i=0; i<ul.length; i++) {
//Children gets all the list items as another HTML Collection
li = ul[i].children;
for (var j=0; j<li.length; j++) {
// offsetTop will get the vertical distance of the li from the ul.
// if > 0 it has been wrapped.
loc = li[j].offsetTop;
if (loc > 0) {
li[j].className = 'wrapped';
} else {
li[j].className = 'unwrapped';
}
}
}
}
I noticed elements will typically wrap in relation to the first element. Comparing offset top of each element to the first element is a simpler approach. This works for wrap and wrap-reverse. (Probably won't work if elements use flex order)
var wrappers = $('.flex[class*="flex-wrap"]'); //select flex wrap and wrap-reverse elements
if (wrappers.length) { //don't add listener if no flex elements
$(window)
.on('resize', function() {
wrappers.each(function() {
var prnt = $(this),
chldrn = prnt.children(':not(:first-child)'), //select flex items
frst = prnt.children().first();
chldrn.each(function(i, e) { $(e).toggleClass('flex-wrapped', $(e).offset().top != frst.offset().top); }); //element has wrapped
prnt.toggleClass('flex-wrapping', !!prnt.find('.flex-wrapped').length); //wrapping has started
frst.toggleClass('flex-wrapped', !!!chldrn.filter(':not(.flex-wrapped)').length); //all are wrapped
});
})
.trigger('resize'); //lazy way to initially call the above
}
.flex {
display: flex;
}
.flex.flex-wrap {
flex-wrap: wrap;
}
.flex.flex-wrap-reverse {
flex-wrap: wrap-reverse;
}
.flex.flex-1 > * { /*make items equal width*/
flex: 1;
}
.flex > * {
flex-grow: 1;
}
.cc-min-width-200 > * { /*child combinator*/
min-width: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="flex flex-1 flex-wrap-reverse cc-min-width-200">
<div>Hello</div>
<div>There</div>
<div>World</div>
</div>
If someone wants to find the last element of the row from where wrapped elements started can use the below logic. It's applicable for multiple lines as well
window.onresize = function (event) {
const elements = document.querySelectorAll('.borrower-detail');
let previousElement = {};
let rowTop = elements[0].getBoundingClientRect().top;
elements.forEach(el => el.classList.remove('last-el-of-row'))
elements.forEach(el => {
const elementTop = el.getBoundingClientRect().top;
if (rowTop < elementTop) {
previousElement.classList.add('last-el-of-row');
rowTop = elementTop;
}
previousElement = el;
})
};

How to detect CSS flex wrap event

I have flex container with items inside. How to detect flex wrap event? I want to apply some new css to elements that have been wrapped. I suppose that it is impossible to detect wrap event by pure css. But it would be very powerful feature! I can try to "catch" this break point event by media query when element wraps into new line/row. But this is a terrible approach. I can try to detect it by script, but it's also not very good.
I am very surprised, but simple $("#element").resize() doesn't work to detect height or width changes of flex container to apply appropriate css to child elements. LOL.
I have found that only this example of jquery code works
jquery event listen on position changed
But still terribly.
Here's one potential solution. There might be other gotchas and edge cases you need to check for.
The basic idea is to loop through the flex items and test their top position against the previous sibling. If the top value is greater (hence further down the page) then the item has wrapped.
The function detectWrap returns an array of DOM elements that have wrapped, and could be used to style as desired.
The function could ideally be used with a ResizeObserver (while using window's resize event as a fallback) as a trigger to check for wrapping as the window is resized or as elements in the page change due to scripts and other user-interaction. Because the StackOverflow code window doesn't resize it won't work here.
Here's a CodePen that works with a screen resize.
var detectWrap = function(className) {
var wrappedItems = [];
var prevItem = {};
var currItem = {};
var items = document.getElementsByClassName(className);
for (var i = 0; i < items.length; i++) {
currItem = items[i].getBoundingClientRect();
if (prevItem && prevItem.top < currItem.top) {
wrappedItems.push(items[i]);
}
prevItem = currItem;
};
return wrappedItems;
}
window.onload = function(event){
var wrappedItems = detectWrap('item');
for (var k = 0; k < wrappedItems.length; k++) {
wrappedItems[k].className = "wrapped";
}
};
div {
display: flex;
flex-wrap: wrap;
}
div > div {
flex-grow: 1;
flex-shrink: 1;
justify-content: center;
background-color: #222222;
padding: 20px 0px;
color: #FFFFFF;
font-family: Arial;
min-width: 300px;
}
div.wrapped {
background-color: red;
}
<div>
<div class="item">A</div>
<div class="item">B</div>
<div class="item">C</div>
</div>
Little bit improved snippet on jQuery for this purpose.
wrapped();
$(window).resize(function() {
wrapped();
});
function wrapped() {
var offset_top_prev;
$('.flex-item').each(function() {
var offset_top = $(this).offset().top;
if (offset_top > offset_top_prev) {
$(this).addClass('wrapped');
} else if (offset_top == offset_top_prev) {
$(this).removeClass('wrapped');
}
offset_top_prev = offset_top;
});
}
I've modified sansSpoon's code to work even if the element isn't at the absolute top of the page. Codepen: https://codepen.io/tropix126/pen/poEwpVd
function detectWrap(node) {
for (const container of node) {
for (const child of container.children) {
if (child.offsetTop > container.offsetTop) {
child.classList.add("wrapped");
} else {
child.classList.remove("wrapped");
}
}
}
}
Note that margin-top shouldn't be applied to items since it's factored into getBoundingClientRect and will trigger the wrapped class to apply on all items.
I'm using a similar approach in determining if a <li> has been wrapped in an <ul> that has it's display set to flex.
ul = document.querySelectorAll('.list');
function wrapped(ul) {
// loops over all found lists on the page - HTML Collection
for (var i=0; i<ul.length; i++) {
//Children gets all the list items as another HTML Collection
li = ul[i].children;
for (var j=0; j<li.length; j++) {
// offsetTop will get the vertical distance of the li from the ul.
// if > 0 it has been wrapped.
loc = li[j].offsetTop;
if (loc > 0) {
li[j].className = 'wrapped';
} else {
li[j].className = 'unwrapped';
}
}
}
}
I noticed elements will typically wrap in relation to the first element. Comparing offset top of each element to the first element is a simpler approach. This works for wrap and wrap-reverse. (Probably won't work if elements use flex order)
var wrappers = $('.flex[class*="flex-wrap"]'); //select flex wrap and wrap-reverse elements
if (wrappers.length) { //don't add listener if no flex elements
$(window)
.on('resize', function() {
wrappers.each(function() {
var prnt = $(this),
chldrn = prnt.children(':not(:first-child)'), //select flex items
frst = prnt.children().first();
chldrn.each(function(i, e) { $(e).toggleClass('flex-wrapped', $(e).offset().top != frst.offset().top); }); //element has wrapped
prnt.toggleClass('flex-wrapping', !!prnt.find('.flex-wrapped').length); //wrapping has started
frst.toggleClass('flex-wrapped', !!!chldrn.filter(':not(.flex-wrapped)').length); //all are wrapped
});
})
.trigger('resize'); //lazy way to initially call the above
}
.flex {
display: flex;
}
.flex.flex-wrap {
flex-wrap: wrap;
}
.flex.flex-wrap-reverse {
flex-wrap: wrap-reverse;
}
.flex.flex-1 > * { /*make items equal width*/
flex: 1;
}
.flex > * {
flex-grow: 1;
}
.cc-min-width-200 > * { /*child combinator*/
min-width: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="flex flex-1 flex-wrap-reverse cc-min-width-200">
<div>Hello</div>
<div>There</div>
<div>World</div>
</div>
If someone wants to find the last element of the row from where wrapped elements started can use the below logic. It's applicable for multiple lines as well
window.onresize = function (event) {
const elements = document.querySelectorAll('.borrower-detail');
let previousElement = {};
let rowTop = elements[0].getBoundingClientRect().top;
elements.forEach(el => el.classList.remove('last-el-of-row'))
elements.forEach(el => {
const elementTop = el.getBoundingClientRect().top;
if (rowTop < elementTop) {
previousElement.classList.add('last-el-of-row');
rowTop = elementTop;
}
previousElement = el;
})
};

Categories

Resources