I would like to inject a independent div element into a table styled: display: table.
The div automatically takes the properties of a table-cell element, especially the width; How do I get it to be the width of 100%?
Here my codepen to test: codepen.io
.wrapper {
margin: 0 auto;
padding: 40px;
max-width: 800px;
}
.herotable {
background-color: #fff;
padding: 1.5em;
box-shadow: 3px 3px 20px rgba(0, 0, 0, .3);
border-radius: 3px;
}
.table {
margin: 0 0 10px 0;
width: 100%;
display: table;
}
.table.list {
display: contents;
}
.row {
display: table-row;
background: var(--colorEven);
/* #f6f6f6; */
}
.row:nth-of-type(odd) {
background: var(--colorOdd);
/* #e9e9e9; */
}
.row.header {
background: #fff;
}
.cell {
padding: 6px 12px;
display: table-cell;
position: relative;
}
.row .cell {
border-top: 1px solid #ddd;
}
.row .cell:last-child {
border-right-width: 0
}
.list .row:last-child .cell {
border-bottom: 1px solid;
}
.row.header .cell {
cursor: pointer;
border: none;
border-bottom: 1px solid;
}
.orders {
display: inline-block;
width: 100%;
margin: 10px 0 10px 0;
border: solid 1px #ccc;
border-radius: 5px;
padding: 10px;
}
.order {}
<div class="wrapper herotable">
<div id="test">
<div class="table">
<div class="row header">
<div class="cell sorting sort" data-sort="name">Name</div>
<div class="cell sorting sort" data-sort="age">Age</div>
<div class="cell sorting sort" data-sort="ocupat">Occupation</div>
<div class="cell sorting sort" data-sort="location">Location</div>
</div>
<div class="table list">
<div class="row">
<div class="cell name" data-title="Name">Luke Peters</div>
<div class="cell age" data-title="Age">25</div>
<div class="cell ocupat" data-title="Occupation">Freelance Web Developer</div>
<div class="cell location" data-title="Location">Brookline, MA</div>
</div>
<div class="orders">
<div class="order">This should be an independent div-element. I would like to inject it by JavaScript when clicking on the line above.<br> It should not expand the name column.</div>
</div>
<div class="row">
<div class="cell name" data-title="Name">Joseph Smith</div>
<div class="cell age" data-title="Age">27</div>
<div class="cell ocupat" data-title="Occupation">Project Manager</div>
<div class="cell location" data-title="Location">Somerville, MA</div>
</div>
<div class="row">
<div class="cell name" data-title="Name">Maxwell Johnson</div>
<div class="cell age" data-title="Age">26</div>
<div class="cell ocupat" data-title="Occupation">UX Architect & Designer</div>
<div class="cell location" data-title="Location">Arlington, MA</div>
</div>
</div>
</div>
</div>
</div>
I do not ask for a JavaScript solution, I do only have a problem with CSS. But JavaScript solutions are welcome.
The easiest means of achieving the layout requirements is to use CSS grid, along with grid-template-columns: subgrid, unfortunately this is (currently) supported only in FireFox version 71+, which represents almost 3%1 of global browser use. As such, while it works, it isn't really viable for a production site in the wild.
That said, the relevant HTML and CSS is below:
// here we cache a reference to all .row elements which are not .header elements,
// using document.querySelectorAll(), which returns a non-live NodeList:
const rows = document.querySelectorAll('.row:not(.header)'),
// declaring a function, using Arrow syntax (since we don't use 'this'
// within the function body:
isVisible = (el) => {
// if el is truthy, so is not 'null' or 'undefined':
if (el) {
// we return the results of the following assessments:
// el.hidden === false: we test whether the hidden property
// evaluates to false; if so:
// window.getComputedStyle(el, null).display !== 'none':
// recovers the rendered 'display' property
// of the element, and the assessment checks
// that it is not equal to 'none';
// if both assessments evaluate to true, we return true otherwise
// we return false:
return el.hidden === false && window.getComputedStyle(el, null).display !== 'none';
}
// by default, in the event of a non-existent element or null-reference,
// is passed to the function we simply return false:
return false;
},
// another function declared using Arrow syntax:
orderToggle = (e) => {
// 'e' is a reference to the Event Object passed from the
// EventTarget.addEventListener() call (later);
// activated is a reference to the element to which the
// event-handler was bound:
const activated = e.currentTarget,
// potentialOrders is a reference to the nextElementSibling
// of the current-target; the variable name refers to the
// fact that there may not be a nextSiblingElement or
// that the nextSiblingElement may not be the appropriate
// element:
potentialOrders = activated.nextElementSibling,
// here we call the isVisible() function, above, to retrieve
// a Boolean (true/false) to represent the current-visibility
// of the potentialOrder element:
currentVisibility = isVisible(potentialOrders);
// if the potentialOrders element is truthy (exists, and does not
// evaluate to either undefined or null), we test whether that
// element matches the CSS selector passed to the Element.matches()
// method:
if (potentialOrders && potentialOrders.matches('.orders')) {
// here we update the hidden property of the potentialOrders
// element, isVisible() function returns true, we wish to hide
// the element, so we set 'hidden' to true, otherwise we set
// it to false (as returned by the isVisible() function):
potentialOrders.hidden = currentVisibility;
}
};
// here we iterate over the NodeList of .row:not(.header) elements
// using NodeList.prototype.forEach() along with an Arrow function:
rows.forEach(
// 'el' is a reference to the current Node of the NodeList over
// which we're iterating:
(el) => {
// here we use EventTarget.addEventListener() to bind the anonymous
// function of the addEventListener() method to:
el.addEventListener('click', function(e) {
// call the orderToggle() function, passing the Event
// Object to the function:
orderToggle(e)
});
});
/* setting common default properties for all elements,
along with the ::before and ::after pseudo-elements: */
*,
::before,
::after {
box-sizing: border-box;
font-size: 1rem;
line-height: 1.5;
margin: 0;
padding: 0;
}
.herotable {
margin: auto 1em;
width: 90vw;
}
/* using CSS grid layout: */
.table {
display: grid;
/* setting four columns of equal width: */
grid-template-columns: repeat(4, 1fr);
}
/* distinguishing the .header element to
make the headings visually distinguishable,
but do consider adding appropriate ARIA
roles/attributes to the HTML for
accessibility purposes: */
.header {
font-weight: bold;
border-bottom: 2px solid currentColor;
}
.table.list {
/* placing this element full width across the
defined grid-columns 1 is the first
column, -1 represents the last column; this
is shorthand for "grid-column-start: 1"
and "grid-column-end: -1": */
grid-column: 1 / -1;
/* setting grid-row-gap of 0.5em, and a
grid-column-gap of 0.25em; to separate
cells (adjust to taste): */
gap: 0.5em 0.25em;
}
.row {
/* using CSS grid-layout: */
display: grid;
/* again positioning the elements
to start in the first column
and end in the last column: */
grid-column: 1 / -1;
/* here we use subgrid to have the
grid of the .row elements match
the columns defined on the parent
element: */
grid-template-columns: subgrid;
}
.row:not(.header) {
/* used to indicate interactivity, this
may or not be desired in your use-case,
adjust to taste: */
cursor: pointer;
}
.orders {
/* we don't need to use subgrid here since
the .orders element(s) are children of a
.table element: */
display: grid;
grid-column: 1 / -1;
}
/* hiding elements with the hidden attribute,
because we've overriden the default
display of the .orders elements: */
.orders[hidden] {
display: none;
}
.order {
border: 1px solid currentColor;
border-left-color: transparent;
border-right-color: transparent;
}
<div class="wrapper herotable">
<div id="test">
<div class="table">
<div class="row header">
<div class="cell sorting sort" data-sort="name">Name</div>
<div class="cell sorting sort" data-sort="age">Age</div>
<div class="cell sorting sort" data-sort="ocupat">Occupation</div>
<div class="cell sorting sort" data-sort="location">Location</div>
</div>
<div class="table list">
<div class="row">
<div class="cell name" data-title="Name">Luke Peters</div>
<div class="cell age" data-title="Age">25</div>
<div class="cell ocupat" data-title="Occupation">Freelance Web Developer</div>
<div class="cell location" data-title="Location">Brookline, MA</div>
</div>
<!-- note that I've set the 'hidden' attribute on the element
so that it is hidden on page-load; this may or may not
be required for your use-case: -->
<div class="orders" hidden>
<div class="order">This should be an independent div-element. I would like to inject it with JavaScript when clicking on line above.<br> It should not expand the name column.</div>
</div>
<div class="row">
<div class="cell name" data-title="Name">Joseph Smith</div>
<div class="cell age" data-title="Age">27</div>
<div class="cell ocupat" data-title="Occupation">Project Manager</div>
<div class="cell location" data-title="Location">Somerville, MA</div>
</div>
<div class="row">
<div class="cell name" data-title="Name">Maxwell Johnson</div>
<div class="cell age" data-title="Age">26</div>
<div class="cell ocupat" data-title="Occupation">UX Architect & Designer</div>
<div class="cell location" data-title="Location">Arlington, MA</div>
</div>
</div>
</div>
</div>
</div>
A version that works in both browsers that don't implement subgrid is below, this is much the same except that column-widths are set using CSS variables and can therefore be accessed, and updated if required, by JavaScript; that possibility is not, however, included in the demo:
const rows = document.querySelectorAll('.row:not(.header)'),
isVisible = (el) => {
if (el) {
return el.hidden === false && window.getComputedStyle(el, null).display !== 'none';
}
return false;
},
orderToggle = (e) => {
const activated = e.currentTarget,
potentialOrders = activated.nextElementSibling,
currentVisibility = isVisible(potentialOrders);
if (potentialOrders && potentialOrders.matches('.orders')) {
potentialOrders.hidden = currentVisibility;
}
};
rows.forEach(
(el) => {
el.addEventListener('click', function(e) {
orderToggle(e)
});
});
:root {
/* setting column-widths for each individual column: */
--column1Width: 25%;
--column2Width: 25%;
--column3Width: 25%;
--column4Width: 25%;
/* I chose to use variables here, too, because some
elements will have to be spaced using margins and
others using gap; this allows consistency in
sizing: */
--horizontalGap: 0.25rem;
--verticalGap: 0.5rem;
/* and this is to ensure that all elements representing
table-rows match the same width: */
--fullWidth: 90vw;
}
/* simple CSS reset as above: */
*,
::before,
::after {
box-sizing: border-box;
font-size: 1rem;
line-height: 1.5;
margin: 0;
padding: 0;
}
/* centering the 'table' on the page,
and assigning its width: */
.herotable {
margin: auto 1em;
width: var(--fullWidth);
}
/* styling the table-header information for
visual distinction: */
.header {
font-weight: bold;
border-bottom: 2px solid currentColor;
}
/* because this is a block-element, and not within
a flex, or grid, element we use margin - along
with the defined custom properties - to maintain
consistent spacing: */
.table.list {
margin-top: var(--verticalGap);
margin-bottom: var(--verticalGap);
}
.row {
/* using CSS flex layout: */
display: flex;
/* justifying the contents of the element
with space-between, to separate them
appropriately based on their size and
the defined gaps: */
justify-content: space-between;
/* assigning the margin between vertically-
adjacent elements: */
margin-top: var(--verticalGap);
margin-bottom: var(--verticalGap);
/* assigning the gaps between adjacent-elements
within the .row elements: */
gap: var(--verticalGap) var(--horizontalGap);
}
.row:not(.header) {
cursor: pointer;
}
/* assigning the flex-basis of each element
.cell element based on its 'column' placement;
this allows any column to be resized via
JavaScript: */
.row .cell:nth-child(1) {
flex: 1 0 var(--column1Width);
}
.row .cell:nth-child(2) {
flex: 1 0 var(--column2Width);
}
.row .cell:nth-child(3) {
flex: 1 0 var(--column3Width);
}
.row .cell:nth-child(4) {
flex: 1 0 var(--column4Width);
}
.order {
border: 1px solid currentColor;
border-left-color: transparent;
border-right-color: transparent;
}
<div class="wrapper herotable">
<div id="test">
<div class="table">
<div class="row header">
<div class="cell sorting sort" data-sort="name">Name</div>
<div class="cell sorting sort" data-sort="age">Age</div>
<div class="cell sorting sort" data-sort="ocupat">Occupation</div>
<div class="cell sorting sort" data-sort="location">Location</div>
</div>
<div class="table list">
<div class="row">
<div class="cell name" data-title="Name">Luke Peters</div>
<div class="cell age" data-title="Age">25</div>
<div class="cell ocupat" data-title="Occupation">Freelance Web Developer</div>
<div class="cell location" data-title="Location">Brookline, MA</div>
</div>
<div class="orders">
<div class="order">This should be an independent div-element. I would like to inject it with JavaScript when clicking on line above.<br> It should not expand the name column.</div>
</div>
<div class="row">
<div class="cell name" data-title="Name">Joseph Smith</div>
<div class="cell age" data-title="Age">27</div>
<div class="cell ocupat" data-title="Occupation">Project Manager</div>
<div class="cell location" data-title="Location">Somerville, MA</div>
</div>
<div class="orders" hidden>
<div class="order">This should be an independent div-element. I would like to inject it with JavaScript when clicking on line above.<br> It should not expand the name column.</div>
</div>
<div class="row">
<div class="cell name" data-title="Name">Maxwell Johnson</div>
<div class="cell age" data-title="Age">26</div>
<div class="cell ocupat" data-title="Occupation">UX Architect & Designer</div>
<div class="cell location" data-title="Location">Arlington, MA</div>
</div>
<div class="orders" hidden>
<div class="order">This should be an independent div-element. I would like to inject it with JavaScript when clicking on line above.<br> It should not expand the name column.</div>
</div>
</div>
</div>
</div>
</div>
JS Fiddle demo.
References:
CSS:
Attribute selectors ([attribute]).
CSS custom properties.
:not().
display.
gap.
margin.
padding.
repeat().
:root.
var() function.
HTML:
hidden attribute.
JavaScript
Arrow functions.
document.querySelector().
document.querySelectorAll().
Element.nextElementSibling.
HTMLElement.hidden.
Logical AND (&&).
NodeList.prototype.forEach().
Node.currentTarget.
Node.target.
Window.getComputedStyle().
Footnotes:
See: https://caniuse.com/css-subgrid.
You need to do it with <table>, <tr> and <td> elements and you can use colspan attribute for a full width cell.
Related
Question: Is it possible to draw a CSS box around multiple div elements with unique classes, and with no ability to add parent div tags into the HTML?
I'm trying to group multiple div classes together as part of a larger form. The HTML in the form itself cannot be modified to add additional parent div layers. The CSS and JavaScript are editable
Example: We have a large request form that has 50 questions, with 10 questions in each section of the form. We can draw boxes around those individual questions, but also want to draw a box around a section of questions.
Each div has its own unique class. This is something we cannot change, as it's supplied by the tool we use.
Here's an example of our HTML and CSS:
.form-field {
background-color: #eee;
padding: 15px;
/* border: 1px solid black; */
border-radius: 5px;
box-shadow: 4px 4px grey;
}
.form-field:valid {
background-color: #000;
padding: 15px;
/* border: 1px solid black; */
border-radius: 5px;
box-shadow: 4px 4px grey;
}
.form-field.select.optional.request_ticket_form_id {
background-color: #d9ead3;
padding: 15px;
/* border: 1px solid black; */
border-radius: 5px;
box-shadow: 4px 4px grey;
}
.form-field:hover {
background-color: #ccc;
}
<div class="form-field text optional request_custom_fields_12345">
<label id="request_custom_fields_12345_label" for="request_custom_fields_12345">Vest Size<span class="optional">(optional)</span></label>
<textarea name="request[custom_fields][12345]" id="request_custom_fields_12345" aria-required="false" aria-labelledby="request_custom_fields_12345_label"></textarea>
</div>
<br>
<div class="form-field text optional request_custom_fields_67890">
<label id="request_custom_fields_67890_label" for="request_custom_fields_67890">Vest Shape<span class="optional">(optional)</span></label>
<textarea name="request[custom_fields][67890]" id="request_custom_fields_67890" aria-required="false" aria-labelledby="request_custom_fields_67890_label"></textarea>
</div>
<br>
<div class="form-field text optional request_custom_fields_98765">
<label id="request_custom_fields_98765_label" for="request_custom_fields_98765">Vest Color<span class="optional">(optional)</span></label>
<textarea name="request[custom_fields][98765]" id="request_custom_fields_98765" aria-required="false" aria-labelledby="request_custom_fields_98765_label"></textarea>
</div>
<br>
<div class="form-field text optional request_custom_fields_23456">
<label id="request_custom_fields_98765_label" for="request_custom_fields_23456">Vest Color<span class="optional">(optional)</span></label>
<textarea name="request[custom_fields][23456]" id="request_custom_fields_23456" aria-required="false" aria-labelledby="request_custom_fields_23456_label"></textarea>
</div>
The part that does not seem to work for us is trying to say "draw box around 67890 to 23456" (and anything in between).
Yes. There is a possibility
if the sole purpose is to highlight, or add a "visual shape" on-screen without altering the DOM.
Create a <div> element which has CSS position fixed.
Add pointer-events: none; to that DIV in order to use it exclusively as a "drawing layer" on-top of your app but allow pointer events to propagate trough it.
Get all your elements on the page From-element and to-element. Get their coordinates using getBoundingClientRect. BCR is relative to the viewport, which is perfect to use on a position fixed element overlay.
Once you have the "min-max" coordinates (calculate to min top-left value and the max bottom-right value) having those two points you can now freely draw a rectangle on your canvas. Coincidentally it will be drawn exactly around all the desired elements.
Make sure to "redraw/reposition" the DIV on page resize, scroll, load, DOM ready - or on some button click - if needed.
Create a JavaScript function to be used like:
highlightBox(Elements_to_consider, start_index, end_index)
// DOM utility functions:
const ELS = (sel, par) => (par || document).querySelectorAll(sel);
const EL = (sel, par) => (par || document).querySelector(sel);
// Task:
const EL_highlightBox = EL("#highlightBox");
const highlightBox = (ELS_items, fr, to) => {
const BCR_fr = ELS_items[fr].getBoundingClientRect();
const BCR_to = ELS_items[to].getBoundingClientRect();
EL_highlightBox.style.cssText = `
top: ${BCR_fr.top}px;
left: ${BCR_fr.left}px;
height: ${BCR_to.top - BCR_fr.top + BCR_to.height}px;
width: ${BCR_to.left - BCR_fr.left + BCR_to.width}px;
`;
};
const drawHighlightBox = () => highlightBox(ELS(".item"), 2, 4);
addEventListener("resize", drawHighlightBox);
addEventListener("scroll", drawHighlightBox);
drawHighlightBox();
#highlightBox {
/* Important styles: */
position: fixed;
z-index: 1000;
pointer-events: none;
/* Other styles: */
background: hsla(100, 60%, 50%, 0.2);
outline: 4px solid hsla(100, 60%, 50%, 1);
}
.item {
margin: 10px 0;
padding: 60px;
border: 1px solid gray;
}
<div id="highlightBox"></div>
<div class="item">Item 0 (scroll down)</div>
<div class="item">Item 1</div>
<div class="item">Item 2</div>
<div class="item">Item 3</div>
<div class="item">Item 4</div>
<div class="item">Item 5</div>
<div class="item">Item 6</div>
<div class="item">Item 7</div>
<div class="item">Item 8</div>
<div class="item">Item 9</div>
<div class="item">Item 10</div>
I have a CSS Framework for personal usage, that I want to be as "automatic" as possible. And i've met a issue that I cannot solve, but maybe you can help me.
Let's say I have 3 cards where one is the top parent and the rest are children of eachother. I'll show what I mean.
<div class="card"> // Top parent
Lorem!
<div class="card"> // First child
Lorem!
<div class="card"> // Second child
Lorem!
</div>
</div>
</div>
My goal is to have it such that the Parent is styled with an opposing background color from the body and the first child to be the same as the body again so it opposes to the parent card. And the second child to be opposed again to the first child, aka the same background as the top parent. And for this to continue infinitely.
It's possible to use set a background colour for each, but instead of doing this manually I wish for this to happen by itself as you add cards inside other cards.
I am currently solving this doing .card > .card but this only works once, and I don't wish to spam the css with .card > .card > .card > .card > .card etc...
I tried using div:nth-child(even) but this doesn't work if you keep doing children inside one another.
How can I make this possible?
Current Code:
SCSS:
#if #{$class} == card {
/* If parent card has a child card */
.#{$class} > .#{$class} {
background-color: var(--clr-light-primary-background);
}
}
CSS:
.card > .card {
background-color: var(--clr-light-primary-background);
}
HTML:
<div class="card column33 sm-rounded">
<div class="card-header">
<h2 class="card-title text-regular text-uppercase">TOP PARENT</h2>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title text-medium">1st CHILD</h2>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title text-medium">2nd CHILD</h2>
</div>
</div>
</div>
</div>
At the moment CSS (AFAIK) does not provide methods to increment CSS vars based on nesting level - why you need to set a finite level of nesting allowed...
but on the other hand would it make sense if your DOM had 20 nested cards ;-)?
Here is a an example that allows 5 nested cards
body {
--color-text: #fff;
--color-back: #272b34;
--color-back-alt: #323743;
color: var(--color-text);
background: var(--color-back);
padding: 1rem;
}
.card {
background: var(--color-back-alt);
padding: 1rem;
}
:not(.card)>.card>.card>.card>.card>.card>.card,
:not(.card)>.card>.card>.card>.card,
:not(.card)>.card>.card {
background: var(--color-back);
}
Body
<div class="card"> 1
<div class="card"> 2
<div class="card"> 3
<div class="card"> 4
<div class="card"> 5
<div class="card"> 6
</div>
</div>
</div>
</div>
</div>
</div>
Can't thnk of a way to do this with CSS that is dynamic enough so here is some JS.
It starts at the outer level card, and then goes on down through them alternating the background color until there are no more.
let odd = true;
let cards = document.querySelectorAll('.card');
do {
cards[0].style.backgroundColor = (odd) ? 'blue' : 'red';
odd = !odd;
cards = cards[0].querySelectorAll('.card');
} while (cards.length > 0)
<div class="card column33 sm-rounded">
<div class="card-header">
<h2 class="card-title text-regular text-uppercase">TOP PARENT</h2>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title text-medium">1st CHILD</h2>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title text-medium">2nd CHILD</h2>
</div>
</div>
</div>
</div>
CSS solution
This is probably not practical for a CSS framework, but you could play around with CSS filters and see if you can get a colour combination that works.
Note the JS here is just for the demo.
document.querySelector("button").addEventListener("click", () => {
const cards = document.querySelectorAll(".card");
const lastCard = cards[cards.length - 1];
lastCard.innerHTML = `${cards.length} <div class="card">${
cards.length + 1
}</div>`;
});
document.querySelector("input").addEventListener("input", (e) => {
const newColor = e.target.value;
document.documentElement.style.setProperty("--card-bg-color", newColor);
});
:root {
--card-bg-color: #666;
}
.card {
padding: .5rem;
border-radius: 0.25em;
background: var(--card-bg-color);
>
}
.card>.card {
filter: invert(100%);
}
/* Demo */
.control {
position: fixed;
bottom: 0;
left: 0;
display: grid;
grid-gap: .5rem;
background: black;
color: white;
padding: 1rem;
}
<div class="card">1
<div class="card">2
<div class="card">3</div>
</div>
</div>
<!-- Demo -->
<div class="control">
<button>Add card</button>
<label for="color">Change colour <input type="color" value="#666666"></label>
</div>
JavaScript solution
const setCardBackgroundColors = () => {
const cards = [...document.querySelectorAll(".card")];
cards && cards.length && cards.forEach((card, index) => {
index % 2 && card.classList.add('has-bg-dark');
})
}
// Just for demo
const template = (level) => {
return `
<article class="card">
<p>${level}</p>
</article>
`
}
document.querySelector("button").addEventListener("click", () => {
const cards = document.querySelectorAll(".card");
const lastCard = cards[cards.length - 1];
lastCard.insertAdjacentHTML('beforeend', template(cards.length + 1));
setCardBackgroundColors();
});
.card {
background-color: #555;
color: white;
padding: 1rem;
border-radius: 0.25em;
position: relative;
}
.card.has-bg-dark {
background-color: #222;
}
/* demo styles */
* {
box-sizing: border-box;
}
body {
font: 18px/1.2 system-ui;
padding: 1rem;
}
.control {
position: fixed;
bottom: 0;
left: 0;
display: grid;
grid-gap: 0.5rem;
background: black;
color: white;
padding: 1rem;
}
<article class="card">
<p>1</p>
</article>
<!-- Demo -->
<div class="control">
<button>Add card</button>
</div>
<div class="abc"></div>
I need to delete this div if there is nothing in between the div tags means when div is empty.
I am approaching in CSS like this
div.abc:empty{display:none;}
But I have one problem if I use this method. If there is a single space between div, like <div> </div> :empty doesn't work.
div.abc { border: 1px solid red; height:50px; }
div.abc:empty{display:none;}
<div class="abc"></div>
<hr/>
<div class="abc"> </div>
As of November 2021 impossible without JavaScript. There is no trim in CSS (yet except in FireFox
Note this example will also hide divs that have pseudo class content
document.querySelectorAll(".abc")
.forEach(div => div.hidden = div.textContent.trim() === "")
// alternative if you want to use a class:
// div.classList.toggle("hide",div.textContent.trim() === "")
div.abc { border: 1px solid red; height:50px; }
div.pscontent:after { content: "Also will be hidden"}
div.abc:empty{display:none;}
<div class="abc"></div>
<hr/>
<div class="abc"> </div>
<hr/>
<div class="abc pscontent"></div>
To handle pseudo class content we can do this:
const hideEmpty = (sel, testPseudoContent) => {
const elems = document.querySelectorAll(sel)
elems.forEach((elem,i) => {
const text = [elem.textContent.trim()]
if (testPseudoContent) {
["before", "after"].forEach(ps => text.push(window.getComputedStyle(elem, ps).getPropertyValue("content").trim()))
}
elem.hidden = text.join('').length === 0;
})
};
hideEmpty('.abc')
hideEmpty('.def.pscontent', true)
div.abc {
border: 1px solid red;
height: 50px;
}
div.def {
border: 1px solid green;
height: 50px;
}
div.pscontent:after {
content: "Don't hide this"
}
<div id="div1" class="abc"></div>
<hr/>
<div id="div2" class="abc"> </div>
<hr/>
<div id="div5" class="def pscontent"></div>
Your code does work but in the near future because the Specification has changed to make :empty consider white spaces
Note: In Level 2 and Level 3 of Selectors, :empty did not match elements that contained only white space. This was changed so that that—given white space is largely collapsible in HTML and is therefore used for source code formatting, and especially because elements with omitted end tags are likely to absorb such white space into their DOM text contents—elements which authors perceive of as empty can be selected by this selector, as they expect. ref
Until then, there is a Firefox solution using -moz-only-whitespace
div.abc { border: 1px solid red; height:50px; }
div.abc:-moz-only-whitespace {display:none;}
div.abc:empty {display:none;}
<div class="abc"></div>
<hr/>
<div class="abc"> </div>
When I click on div-two I want to show div-2. I'm using Jquery index and it works but when I put other content/html inside the on-click div it always shows div-1 regardless of what button is clicked. On the following fiddle example if you click the green inner div you'll see the results.
I'm assuming this is an index or propagation issue, can this be corrected or am I pushing the limits of the function?
https://jsfiddle.net/1e230w8s/
// Matches and shows Butts to index order.
let $games = $('.game');
$('.butts').on('click', e => {
let $target = $games.eq($(e.target).index()).show();
$games.not($target).hide();
});
// Finds all Butts and calls `sumBoxes` onclick
const boxes = document.getElementsByClassName("butts");
for (let butts of boxes){ butts.addEventListener("click", sumBoxes); }
// sumBox Calculates each visible Box
function sumBoxes(event) {
var total = 0;
$('.box:visible').each(function() {
total += parseInt($(this).text());
});
$('.sum').html("Total : " + total); // Replaces contents of `.sum`
}
body {background: #eeeeee;}
.game {display:none;}
.box {float: left;font: bold 17px arial;text-align: center;margin: 10px 10px 20px 0;padding: 10px 25.54px;background: #fff;color: blue}
.butts {cursor:pointer; width:200px;height:40px;background:#fff; margin-bottom:10px; margin-right:10px; float:left;border:1px solid blue;}
.sum {clear:both;margin-top:40px;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container" style="float: left;width: 100%;">
<div class="butts">1 <div style="float:right;width:40px;height:20px;background:lightgreen;">1</div></div>
<div class="butts">2 <div style="float:right;width:40px;height:20px;background:lightgreen;">2</div></div>
<div class="butts">3 <div style="float:right;width:40px;height:20px;background:lightgreen;">3</div></div>
</div>
<div class="container">
<div class="game">
<div class="box">1 Box</div>
</div>
<div class="game">
<div class="box">2 Box</div>
</div>
<div class="game">
<div class="box">3 Box</div>
</div>
</div>
<div class="sum"></div>
In your current code you have use e.target so when you click on children div current target will be inner div tag and according to your code its value will be 0 that's why only first one is showing .Instead you can check if the e.target has butts class or not depending on this change your selector.
Demo Code :
// Matches and shows Butts to index order.
let $games = $('.game');
$('.butts').on('click', e => {
//check is target has class .butts
var target = $(e.target).hasClass("butts") ? $(e.target) : $(e.target).closest(".butts")
let $target = $games.eq(target.index()).show();
$games.not($target).hide();
});
body {
background: #eeeeee;
}
.game {
display: none;
}
.box {
float: left;
font: bold 17px arial;
text-align: center;
margin: 10px 10px 20px 0;
padding: 10px 25.54px;
background: #fff;
color: blue
}
.butts {
cursor: pointer;
width: 200px;
height: 40px;
background: #fff;
margin-bottom: 10px;
margin-right: 10px;
float: left;
border: 1px solid blue;
}
.sum {
clear: both;
margin-top: 40px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container" style="float: left;width: 100%;">
<div class="butts">1
<div style="float:right;width:40px;height:20px;background:lightgreen;">1</div>
</div>
<div class="butts">2
<div style="float:right;width:40px;height:20px;background:lightgreen;">2</div>
</div>
<div class="butts">3
<div style="float:right;width:40px;height:20px;background:lightgreen;">3</div>
</div>
</div>
<div class="container">
<div class="game">
<div class="box">1 Box</div>
</div>
<div class="game">
<div class="box">2 Box</div>
</div>
<div class="game">
<div class="box">3 Box</div>
</div>
</div>
<div class="sum"></div>
How it should work:
Open (toggle .show class) on div.user, and displays the .userSub div.
If I click on another div.user, close (remove .show class) and opens the clicked div.userSub
If I click on the already .show-ed div.user (NOT .userSub), it'd close the target div.user.
Almost works but the problem:
when .userSub div is .show-ed, I can only click to close on the .userSub div, not the .user div. However that would be goal. :)
I've tried to eliminate the problem. Probably the .user selection is wrong and I should use stopPropagation() somewhere, or I should be more specific with the child elements, but I can't figure it out.
let $active
$(document).ready(() => {
$(".user").click(function(e) {
if ($active != null) {
$active.toggleClass("show")
}
$(e.target).children().toggleClass("show")
$active = $(e.target).children()
})
})
.user {
background-color: gray;
padding: 20px;
margin: 5px;
}
.userSub {
display: none;
padding: 20px;
background-color: lightgray;
color: black;
margin: 5px;
}
.show {
display: block;
}
button {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="user">
name 1
<div class="userSub">details 1<button>more 1</button></div>
</div>
<div class="user">
name 2
<div class="userSub">details 2<button>more 2</button></div>
</div>
<div class="user">
name 3
<div class="userSub">details 3<button>more 3</button></div>
</div>
To achieve your goal check that the clicked element was the .user element directly, not a child of it. To do that you can use the target property of the event.
Also note that you can simplify the logic by only applying the .show class to the parent .user and having the CSS rules apply the display: block rule to the child elements based on the class on a parent. Try this:
$(document).ready(() => {
let $users = $(".user").click(function(e) {
if (e.target !== this)
return;
$users.not(this).removeClass('show');
$(this).toggleClass("show")
})
})
.user {
background-color: gray;
padding: 20px;
margin: 5px;
}
.userSub {
display: none;
padding: 20px;
background-color: lightgray;
color: black;
margin: 5px;
}
.user.show .userSub {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="user">
name 1
<div class="userSub">details 1<button>more 1</button></div>
</div>
<div class="user">
name 2
<div class="userSub">details 2<button>more 2</button></div>
</div>
<div class="user">
name 3
<div class="userSub">details 3<button>more 3</button></div>
</div>