I am creating a table with sticky columns using css position: sticky. I want to style the columns differently when they are "stuck". My first project involves styling the first column or .wdtscroll td:nth-child(1) when the second column partially slides over it.
Here is the javascript
const stickyElm = document.querySelector('.wdtscroll td')
const observer = new IntersectionObserver(
([e]) => e.target.classList.toggle('isSticky', e.intersectionRatio < 1),
{threshold: [1]}
);
observer.observe(stickyElm)
Here is the jsfiddle: https://jsfiddle.net/g421kjcx/
While it is certainly not perfect, I have accomplished this by setting its left position at -1px so as soon as you start scrolling the table horizontally, it is styled. As you can see this is working but only for the top cell.
When I use this code on my website, it does not work and I get a warning stating:
"TypeError: Argument 1 of IntersectionObserver.observe is not an object."
When I looked it up, it seems Object.observe is obsolete.
How would I go about using this javascript without using Object.observe, and how can I target all td's in the first column.
Bonus question: How would I style the second column or .wdtscroll td:nth-child(2) when it is stuck, even though it will never scroll past the viewport.
Thank you!
Here is the jsfiddle: https://jsfiddle.net/g421kjcx/
Here is my response to your fiddle: https://jsfiddle.net/5nfb2qdy/4/
I answered a range of questions you put in this post:
As you can see this is working but only for the top cell.
...how can I "target" all td's in the first column.
1) the target of the observer is a single element (in this case). This means you can't rely on it for styling multiple elements.
Instead, do it this way:
([e]) => {
let all_td = document.querySelectorAll('.wdtscroll td:first-child')
all_td.forEach(entry =>
entry.classList.toggle('isSticky', e.intersectionRatio < 1)
)
}
When I use this code on my website, it does not work and I get a warning stating: "TypeError: Argument 1 of IntersectionObserver.observe is not an object."
2) This is most likely because the JavaScript is running BEFORE the elements referred to even exist on the page. If this block of code is in the <head></head> part of the page, only a slight revision is needed to make it work:
window.onload = function(){
observer.observe(stickyElm)
}
By wrapping the observer activation in the onload, it won't run before the page finishes rendering. The other option is to move all of this JavaScript to the end of the page just before your </body></html>
Bonus question: How would I style the second column or .wdtscroll td:nth-child(2) when it is stuck, even though it will never scroll past the viewport.
3) Maybe like this?
([e]) => {
let all_td = document.querySelectorAll('.wdtscroll td:nth-child(1)')
let all_2nd_td = document.querySelectorAll('.wdtscroll td:nth-child(2)')
all_td.forEach(entry =>
entry.classList.toggle('isSticky', e.intersectionRatio < 1)
)
all_2nd_td.forEach(entry =>
entry.classList.toggle('isSticky', e.intersectionRatio < 1)
)
}
Also add this to the end of the CSS:
.wdtscroll tr td:nth-child(2).isSticky {
background-color: pink;
}
4) Not a response to any of your questions but I noticed some problems with your CSS in general. Here are the things I changed:
CSS
/* added td to the selectors of the next 2 rules */
.wdtscroll tr:nth-child(even) td { background-color: #f2f2f2; }
.wdtscroll tr:hover td { background-color: #ddd; }
/* added tr to the selector list to override the background color set above */
.wdtscroll tr td.isSticky{
background-color: salmon;
}
5) Lastly, a critique of the approach used to do the class assignments on the elements. You may have good reason to assign class attributes on each td of each tr. This can also be achieved more simply by assigning class attributes to the table itself with rules that apply styles to td:nth-child(1) and td:nth-child(2) with only 2 CSS rules. This would eliminate the need to loop through every row of the table in the JavaScript and leverage the feature of CSS to style bulk elements.
CSS:
.wdtscroll.isSticky tr td:nth-child(1) {
background-color: salmon;
}
.wdtscroll.isSticky tr td:nth-child(2) {
background-color: pink;
}
JavaScript:
// get the sticky element
const stickyElm = document.querySelector('.wdtscroll td')
const tableElm = document.querySelector('.wdtscroll')
const observer = new IntersectionObserver(
([e]) => {
tableElm.classList.toggle('isSticky', e.intersectionRatio < 1)
},
{threshold: [1]}
);
window.onload = function(){
observer.observe(stickyElm)
}
How's that for a nice, neat, and tidy solution? :)
Final fiddle: https://jsfiddle.net/5nfb2qdy/5/
I want to remove a HTML table completely from the screen and still be able to add rows to it later. I have the following code to remove all rows from a table (basically, it sets the inner HTML of the table to ""):
function removeAllRows() {
table = document.getElementById("myTable")
table.innerHTML = ""
}
However, when this function is called, the table isn't completely removed...there's a small speck on the table, which I believe has to do with the 1 pixel border of a table element.
I'd like to modify this function to completely remove the table from the screen, which would basically be hiding the table border when the table is empty.
Is there something that I could put in the style block to do this?
Right now, my style block looks like:
table, td {
border: 1px solid black;
border-collapse: separate;
empty-cells: hide;
}
empty-cells: hide; will hide the borders of empty cells, but not the border of the whole table if the whole table has no cells.
Is there any way to hide the border of an empty table?
fiddle
To just hide the table you can do this...
function removeAllRows() {
var table = document.getElementById("myTable")
// Checking that the table element exists before setting it to hidden to avoid nullreference exceptions.
if(!!table){table.hidden = true;}
}
You can alter the border programmatically. Add this line in removeAllRows()
table.style.border="0px";
Then in the addRow() method, add the border back:
table.style.border="1px solid black";
You can add a no border class to you table
function removeAllRows() {
table = document.getElementById("myTable")
table.innerHTML = ""
table.className = 'no-border';
}
And in you CSS file:
.no-border {
border: none;
}
Did not manage to solve it with CSS alone.
But you can either hide the table or just toggling the classlist property
if (table.children.length) {
table.classList.toggle('border') // whatever should replace default
}
I want to remove the row where the text is not found, when checkbox unchecked . If checkbox checked, then the table needs to return to its original state . What can you recommend?
function keySearch() {
$('#search').keyup(function (e) {
var search = $(this).val();
$('td').removeClass('found');
$('td').each(function (index, element) {
if ($(element).html() == search) {
$(element).addClass('found');
}
if (!$('#checkbox').prop('checked', true)) {
$(element).parent().hide();
}
else
{
$(element).parent().show();//how replace remove row?
}
});
});
}
Instead of using JavaScript to show and hide the rows, you could use it to add or remove a .checked CSS class on the parent table. Then set up the .found class so that it only hides the rows if the .checked class is present. You'll need to add the .found class to the rows instead of the cells for this to work. Just add .parent() before .addClass('found') in your code. Also, get rid of the code below your first if statement.
Here's what the CSS would look like:
table.checked > * > tr.found {
display: none;
}
Some additional code will be needed to make the checkbox work. Here's how to do it with plain ol' JavaScript (sorry, I don't use jQuery much):
document.querySelector('#checkbox').addEventListener('click', function(e) {
if(e.target.checked)
document.querySelector('#table').classList.add('checked');
else
document.querySelector('#table').classList.remove('checked');
});
(This assumes that your table has an id of 'table').
I'm using Bootstrap and have a striped table that can be filtered by selecting some options on a form. Javascript interprets the form inputs, and hides rows from the table that don't match the selected criteria.
However, this breaks the table striping on the table depending on which rows are hidden (gray rows next to gray rows, white rows next white rows).
I'd like to reapply the striping based on what rows are visible after filtering the results. How can I do this?
Using .remove() on the table rows is not an option, because I may need to show them again if the filter criteria changes and I'm trying to avoid using AJAX to update the table dynamically based on the filter inputs (I'd like to stick to hiding DOM elements).
Any help is appreciated! I can clarify the question if needed :)
Seems like Bootstrap 4 have a different implementation. Following #Anthony's answer, this is how it would work:
$("tr:visible").each(function (index) {
$(this).css("background-color", !!(index & 1)? "rgba(0,0,0,.05)" : "rgba(0,0,0,0)");
});
Tables are now striped by pure CSS and not by adding the "stripe" class name.
Yes, this is definitely one of the annoying parts of table striping. The better part of valor here is probably just to reapply the striping with jQuery after each update:
$("tr:not(.hidden)").each(function (index) {
$(this).toggleClass("stripe", !!(index & 1));
});
Anthony's answer did not work for me. First, it does not hide the Bootstrap table class table-striped, and second, there is not (or at least does not appear to be) a built-in class stripe for table rows.
Here's my approach, where I've filtered rows in a table with an id of "reports".
Here's a version to use if you define the class "stripe" for <tr> elements:
// un-stripe table, since bootstrap striping doesn't work for filtered rows
$("table#reports").removeClass("table-striped");
// now add stripes to alternating rows
$rows.each(function (index) {
// but first remove class that may have been added by previous changes
$(this).removeClass("stripe");
if ( index % 2 == 0) {
$(this).addClass("stripe");
}
});
If you're too lazy to define the CSS class "stripe" then here's a quick & dirty version:
// un-stripe table, since bootstrap striping doesn't work for filtered rows
$("table#reports").removeClass("table-striped");
// now add stripes to alternating rows
$rows.each(function (index) {
// but first remove color that may have been added by previous changes:
$(this).css("background-color", "inherit");
if ( index % 2 == 0) {
$(this).css("background-color", "#f9f9f9");
}
});
This is the same answer as #Jacobski's answer but will keep the hover effect of a bootstrap table-hover.
$("tr:visible").each(function (index) {
$(this).css("background-color", !!(index & 1) ? "rgba(0,0,0,.05)": "rgba(0,0,0,0)");
if (!(index & 1)) {
$(this).hover(
function () { //On hover over
$(this).css("background-color", "rgba(0,0,0,.07)");
},
function () { //On hover out
$(this).css("background-color", "rgba(0,0,0,0)");
}
)
}
});
My answer build upon what #Jacob and #yehuda suggested.
This works with bootstrap4, for a table that needs both the behavior of ".table-striped" and ".table-hover".
The hover part is handled by CSS, which makes it more efficient (I noticed a small delay due to javascript handler, when testing #yehuda's snippet).
// CSS
<style>
.table-striped tbody tr.visible-odd {
background-color: rgba(0, 0, 0, 0.05);
}
.table-striped tbody tr.visible-even {
background-color: rgba(0, 0, 0, 0.00);
}
.table-hover tbody tr.visible-even:hover {
background-color: rgba(0, 0, 0, 0.075);
}
</style>
// JS
$("tr:visible").each( function(index, obj) {
if (index % 2) {
$(this).addClass('visible-odd').removeClass('visible-even');
} else {
$(this).addClass('visible-even').removeClass('visible-odd');
}
});
For me this works fine with hidden rows and reapplies the striping as expected:
$("table#ProductTable").removeClass("table-striped");
$("table#ProductTable").addClass("table-striped");
#Jacobski's answer was great, but I had some pages with multiple tables and the header row's background would get changed on separate tables. Also my table rows that were always visible had the class "accordion-toggle" not sure if that's a bootstrap 5 thing, but that is how I targeted it! (also I don't know JavaScript so there's probably cleaner syntax to do what I did)
$("tr:visible").each(function (index) {
if ($(this).hasClass("tb-header")) {
rowIndex = 0; // need to reset the rowIndex since we are now on a new table!
} else {
if ($(this).hasClass("accordion-toggle")) {
$(this).css("background-color", !!(rowIndex & 1)? "rgba(0,0,0,0)" : "rgba(0,0,0,.05)");
rowIndex++;
}
}
});
I'm using a jquery quick search plugin (https://github.com/riklomas/quicksearch) which filters a list based upon the data entered into an input field.
If there's no results returned, I want to display a message saying so.
The quick search plugin adds display: none to all list elements that aren't to be shown.
Therefore, I tried this:
// load jquery.quicksearch
$('#search').parent().css('display','block').end().quicksearch('#ul'+id+' li');
// show / hide message
$("input#search").keypress(function() {
li = $('.category li');
if (li.css('display') == 'none') {
$('body').append('<div id="noContent">no content</div>');
} else {
$('#noContent').remove();
}
});
The result is a very twitchy / buggy solution. Some times it doesn't append the message even if all li items have display: none. It also doesn't even remove the no content message even when there ARE list items visible.
Any ideas?
Read the docs: you don't need to do what you're doing.
Simply use the noResults option.
Their example:
$('input#search').quicksearch('table tbody tr', {
'delay': 100,
'selector': 'th',
'stripeRows': ['odd', 'even'],
'loader': 'span.loading',
'noResults': 'tr#noresults',
.......
looks like you would want 'noResults': '#noContent'