finding the closest previous sibling that contains .myClass - javascript

I have a bunch of <tr> some of them contain a <td> that has class="myClass" but some don't. So it looks like something like this.
<tr>
<td class="myClass"></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
If I'm at a <tr>, how do I go up in rows until I hit the closest row that contains a td.myClass? Is there a clever way to do this? What I have now is a while loop that checks prev() and if it finds .myClass, it breaks.

$currentTr.prevAll(':has(td.myClass)').first()

Here's a working example, not so great, but works. http://jsfiddle.net/H2k8m/2/
1) The td with class "color" will be the selected ones.
2) Either you can use that or you can directly assign the selected object to some variable and use it outside the function.
HTML :
<table>
<tr>
<td class="myClass">Hi</td>
<td>world</td>
</tr>
<tr>
<td class="myClass">1</td>
<td class="myClass" >2</td>
</tr>
<tr>
<td>3</td>
<td>4</td>
</tr>
<tr>
<td>5</td>
<td>6</td>
</tr>
</table>
CSS :
.color {
background: #000;
color: #fff;
}
table {
cursor: pointer;
}
tr, td {
min-width: 50px;
border: 1px #000 solid;
padding: 5px;
text-align: center;
}
Jquery :
$(document).ready(function()
{
$("table tr td").click(function() {
if( $(this).prevAll('[class="myClass"]').length <= 0 ) {
var parents = $(this).parent().siblings();
for( i = $(this).parent().index(); i >= 0; i-- ) {
parents.eq( i ).find(".myClass").last().addClass("color");
if( parents.eq( i ).find(".myClass").length > 0) {
break;
}
}
}
else {
$(this).prevAll('[class="myClass"]').first().addClass("color");
}
});
});

Here's another approach that will find the actual closest td with the myClass classname. If you want the tr, you can simply get the parent of what it finds:
var td$ = $("#myTable td.myClass");
$("#myTable td").click(function() {
td$.removeClass("found");
var temp$ = td$.add(this);
var i = temp$.index(this);
if (i > 0) {
temp$.eq(i - 1).addClass("found");
}
});
This gets a list of all td's with myClass. It then adds the clicked on element to that jQuery object (jQuery will sort it into DOM order after adding it). It then finds the index of the clicked on element in that jQuery object and if it's not the first item, it just gets the item before it which will be the closest td.myClass object before it in the table.
Working jsFiddle: http://jsfiddle.net/jfriend00/XqLzb/

Related

Trying to move html element to a specific location within a table

I'm trying to move an HTML element to a specific location within a table. For example, I have a disabled button labeled "abcd" and a table with cell value of "xyz". I want to move the button "abcd" on top of the cell with the value "xyz" by referencing that value.
So far, the code I have for the javascript function looks like this:
<script type="text/javascript">
function moveObject() {
var label = prompt("Please enter object to move", "");
var location = prompt("Please enter cell location", "");
var element = document.getElementById(label);
}
</script>
How do I reference the cell value so that I can tell the object to move there?
To achieve expected result, use below option using document.getElementsByTagName and prepend to add button to cell value
Get all tds using document.getElementsByTagName('TD')
Loop all td elements using for of
Look for cell with text xyz and prepend disabled button- abcd
function moveObject() {
let tds = document.getElementsByTagName('TD')
let btn = document.getElementById('abcd')
for(let cell of tds){
if(cell.innerHTML ==='xyz'){
cell.prepend(btn)
}
}
}
table tr td{
border: 1px solid black;
}
<table>
<tr>
<td>test</td>
<td>xyz</td>
</tr>
</table>
<button id="abcd" disabled>abcd</button>
<button onclick="moveObject()">Move</button>
Say you have the following structure for a html table:
<table id="myTable">
<tr>
<td>Cell A</td>
<td>Cell B</td>
</tr>
<tr>
<td>Cell C</td>
<td>Cell D</td>
</tr>
</table>
As you can see every cell is a <td> element.
Since our table has an unique id - myTable - we can get all it's TD elements by calling:
var cells=document.getElementById("myTable").getElementsByTagName("td");
The variable cells holds a html collection of all the TD elements of myTable but what we are really interested in is the actual content of a cell - so we need to loop over this array and refer to each cells content using .firstChild.data.
If we compare this to a string we can see which cell matches.
Here's an example:
function getCell(myString) {
var cells = document.getElementById("myTable").getElementsByTagName("td");
for (var a = 0; a < cells.length; a++) {
if (cells[a].firstChild.data == myString) {
return cells[a];
}
}
return null;
}
console.log(getCell("Cell C"));
<table id="myTable">
<tr>
<td>Cell A</td>
<td>Cell B</td>
</tr>
<tr>
<td>Cell C</td>
<td>Cell D</td>
</tr>
</table>
prompt() is a crappy interface. Do this instead:
Wrap everything in a block level element (div, section, main, etc), we'll refer to it as the "parent"
Register the parent to the click event either by on-event property:
parent.onclick = callback
or by Event Listener:
parent.addEventListener('click', callback)
now the parent will detect all clicks on anything within the parent.
Use event.target to determine what was clicked. No id, class, or tagName is needed to identify event.target, but use .matches() to narrow down event.target in if/else control.
function addButton(event) {
const clicked = event.target;
const buttons = this.querySelectorAll('button');
const prompt = this.querySelector('legend');
if (clicked.matches('button')) {
if (clicked.matches('.selected')) {
clicked.classList.remove('selected');
prompt.textContent = 'Click a Button';
prompt.style.color = 'black';
} else {
for (let button of buttons) {
button.classList.remove('selected');
}
clicked.classList.add('selected');
prompt.textContent = 'Click a Cell';
prompt.style.color = 'red';
}
} else if (clicked.matches('td')) {
let button = this.querySelector('.selected');
if (button) {
clicked.appendChild(button);
} else {
return false;
}
} else {
return false;
}
}
document.querySelector('main').onclick = addButton;
table {
table-layout: fixed;
border-spacing: 3px;
width: 100%;
}
th,
td {
width: 33%;
border: 1px solid #000;
padding: 1px
}
th::before,
td::before {
content: '\a0';
}
legend {
font-size: 1.75rem
}
button {
display: inline-block;
margin: 0 5px;
line-height: 100%;
}
.selected {
color: red;
outline: 2px solid red;
}
<main>
<table>
<thead>
<tr><th>A</th><th>B</th><th>C</th></tr>
</thead>
<tbody>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
<tr><td></td><td></td><td></td></tr>
</tbody>
</table>
<fieldset>
<legend>Click a Button</legend>
<button>X</button>
<button>X</button>
<button>X</button>
<button>X</button>
</fieldset>
</main>

Alternative to .nextSibling?

I'm currently working on a project and I have to fill different column of a table, for that I'm using .nextSibling, but the lines can be very long if I target, let's say the 4th column:
firstTd.nextSibling.nextSibling.nextSibling.nextSibling.innerHTML = "example";
So I was wondering if there was any more elegant way of doing it, that doesn't require writing .nextSibling every time?
Just make a small helper:
const sibling = (el, count) => count ? sibling(el.nextSibling, count - 1) : el;
Which can be used as
sibling(firstTd, 5).innerHTML = "example";
Rather than relying on a specific position like that, which is inherently brittle (what if you add a new column?), I'd suggest giving your target td some kind of identifying mark, like a class name or data-* attribute. Then you'd use:
tr.querySelector(".the-class").innerHTML = "example";
if you don't have tr handy, you can get it from firstTd.parentNode.
Naturally, because querySelector doesn't just look at children but all descendants, you'll want to plan for that.
Live example:
// In a real situation I'd use a delegated handler, but the goal here is to
// show that the same code works regardless of the starting point
document.querySelectorAll("td:not(.x)").forEach(el => {
el.addEventListener("click", function() {
this.parentNode.querySelector(".ex").innerHTML = Math.random();
});
});
table {
border-collapse: collapse;
border: 1px solid #aaa;
}
td {
border: 1px solid #aaa;
padding: 4px;
}
<table>
<tbody>
<tr>
<td>Click me</td>
<td>Or me</td>
<td>Or me</td>
<td class="ex"></td>
</tr>
</tbody>
</table>
Alternately, give yourself a "find my next matching sibling" function that accepts a selector:
const findNext = (el, selector) => {
let sib = el.nextElementSibling;
while (sib && !sib.matches(selector)) {
sib = sib.nextElementSibling;
}
return sib;
};
then
findNext(firstTd, ".the-class").innerHTML = "example";
Live example:
const findNext = (el, selector) => {
let sib = el.nextElementSibling;
while (sib && !sib.matches(selector)) {
sib = sib.nextElementSibling;
}
return sib;
};
// In a real situation I'd use a delegated handler, but the goal here is to
// show that the same code works regardless of the starting point
document.querySelectorAll("td:not(.x)").forEach(el => {
el.addEventListener("click", function() {
findNext(this, ".ex").innerHTML = Math.random();
});
});
table {
border-collapse: collapse;
border: 1px solid #aaa;
}
td {
border: 1px solid #aaa;
padding: 4px;
}
<table>
<tbody>
<tr>
<td>Click me</td>
<td>Or me</td>
<td>Or me</td>
<td class="ex"></td>
</tr>
</tbody>
</table>
Table rows and cells can be accessed by index:
table1.rows[2].cells[2].innerText = 42
<table id=table1>
<tr> <th> A </th> <th> B </th> <th> C </th> </tr>
<tr> <td> 1 </td> <td> 2 </td> <td> 3 </td> </tr>
<tr> <td> 4 </td> <td> 5 </td> <td> 6 </td> </tr>
</table>

css and html columns - flip table

I am trying to find a way to easily flip (transpose, rotate) a HTML table including moving the attributes to tags. According to http://quirksmode.org/css/css2/columns.html I can only use border, background, width and visibilty in ... is this still valid? And what about class names and event handlers that I have on the table rows?
There are also draggable columns (using jQuery-UI). Will I have to reinitialize them after flipping?
Is there any jQuery-UI plugin that takes care of all the stuff?
It looks to me as if this stack overflow question might scratch your same itch.
Here's the js fiddle mentioned in that question's answer: http://jsfiddle.net/CsgK9/2/ - I did not write this, the credit goes to: svinto
Copied the contents of that jsfiddle to this code snippet.
Hope this helps.
$("a").click(function(){
$("table").each(function() {
var $this = $(this);
var newrows = [];
$this.find("tr").each(function(){
var i = 0;
$(this).find("td").each(function(){
i++;
if(newrows[i] === undefined) { newrows[i] = $("<tr></tr>"); }
newrows[i].append($(this));
});
});
$this.find("tr").remove();
$.each(newrows, function(){
$this.append(this);
});
});
return false;
});
td { padding:5px; border:1px solid #ccc;}
.red { color: red; }
.border td { border: 1px solid black; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tr>
<td class="red">1</td>
<td class="red">4</td>
<td class="red">7</td>
</tr>
<tr class="border">
<td>2</td>
<td>5</td>
<td>8</td>
</tr>
<tr>
<td>3</td>
<td>6</td>
<td>9</td>
</tr>
</table>
<p>Do it.</p>
Notice that classes put on the <tr> do get pulled off. So there is room for improvement on this answer.
This method might be closer to what you're looking for - it is another approach worth mentioning. Credit goes to Stefan Kendall
function swap( cells, x, y ){
if( x != y ){
var $cell1 = cells[y][x];
var $cell2 = cells[x][y];
$cell1.replaceWith( $cell2.clone() );
$cell2.replaceWith( $cell1.clone() );
}
}
var cells = [];
$('table').find('tr').each(function(){
var row = [];
$(this).find('td').each(function(){
row.push( $(this) );
});
cells.push( row );
});
for( var y = 0; y <= cells.length/2; y++ ){
for( var x = 0; x < cells[y].length; x++ ){
swap( cells, x, y );
}
}
.red { color: red; }
.border { border: 2px solid black; }
.blue { color: blue; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tbody>
<tr>
<td class="red">01</td><td class="red">02</td><td class="red">03</td><td>04</td>
</tr>
<tr class="blue">
<td class="border">05</td><td class="border">06</td><td class="border">07</td><td class="border">08</td>
</tr>
<tr>
<td>09</td><td>10</td><td>11</td><td>12</td>
</tr>
<tr>
<td>13</td><td>14</td><td>15</td><td>16</td>
</tr>
</tbody>
</table>
In both of these examples, so long as you keep the css classes on the <td> it will move over without a problem. I've noticed the <tr> are losing their attributes. With some small modification, I'm pretty sure you could move those also.
Hopefully this gets you closer to your goal.

Why does JQuery show() function only work on one (rather than all) of elements with the selector?

JSFiddle.
In the following SSCCE, there is a <table> nested inside another <table>.
The question is about the click listener for #add button. Specifically, the last if/else block of the function. When you run this code, click the Add TextField button once (or more times), and you will see that the #remove button on which show() should be executed, is only shown for the first matched selector, and not both (or all) of them.
Ideally the Remove TextField should be shown for all the #remove selectors.
The question is why? How do I fix this?
$(document).on("click", "button#add", function() {
event.preventDefault();
var parentTable = $(this).parent().parent().parent().parent().parent().parent().parent().parent();
var lastTableRow = parentTable.children('tbody').children('tr:last');
//Adding the new row
parentTable.children('tbody').append(lastTableRow.clone());
//Reset lastRow variable
lastTableRow = parentTable.children('tbody').children('tr:last');
//Reset the fields
lastTableRow.find('table tbody tr td input').each(function() {
$(this).val('');
});
//update numberOfRows variable
var numberOfRows = parentTable.children('tbody').children('tr').length;
alert("numberOfRows:" + numberOfRows); //check
if (!(numberOfRows > 1)) {
$("#remove").hide();
} else {
$("#remove").show();
}
});
#outer-table {
padding: 20px;
border: 3px solid pink;
}
#inner-table {
border: 3px solid orange;
}
#remove {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="outer-table">
<tbody>
<tr>
<td>
<table id="inner-table">
<tbody>
<tr>
<td>
<p style="display:inline-block">Enter first complain:</p>
<input type="text" />
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>
<button id="add">Add Textfield</button>
<button id="remove">Remove Textfield</button>
</td>
</tr>
</tfoot>
</table>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>Table Footer</td>
</tr>
</tfoot>
</table>
That's because you're using id for a group of objects. id should be unique per document. You should use a class name instead.

Javascript onClick event in all cells

I'm learning JavaScript and I've not that much experience.
But I'm making a HTML table and I want to add in every table cell (<td>) a onClick event.
<table id="1">
<tr>
<td onClick="tes()">1</td><td onClick="tes()">2</td>
</tr>
<tr>
<td onClick="tes()">3</td><td onClick="tes()">4</td>
</tr>
</table>
Is there another way to do this event in every cell?
I know this is a bit old, but you should use a click envent on the table and use the target value to get the cell. Instead of having a 10 x 10 table = 100 event you will have only 1.
The best thing with that method is when you add new cells you don't need to bind an event again.
$(document).ready( function() {
$('#myTable').click( function(event) {
var target = $(event.target);
$td = target.closest('td');
$td.html(parseInt($td.html())+1);
var col = $td.index();
var row = $td.closest('tr').index();
$('#debug').prepend('<div class="debugLine">Cell at position (' + [col,row].join(',') + ') clicked!</div>' );
});
});
td {
background-color: #555555;
color: #FFF;
font-weight: bold;
padding: 10px;
cursor: pointer;
}
#debug {
background-color: #CCC;
margin-top: 10px;
padding: 10px;
}
#debug .debugLine {
margin: 2px 0;
padding: 1px 5px;
background-color: #EEE;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="myTable">
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
</table>
<div id="debug"></div>
You may try this too (using event delegation)
window.onload = function(){
document.getElementById('tbl1').onclick = function(e){
var e = e || window.event;
var target = e.target || e.srcElement;
if(target.tagName.toLowerCase() == "td") {
alert(target.innerHTML);
}
};
};
EXAMPLE.
Using jQuery
$(function(){
$('#tbl1').on('click', 'td', function(){
alert($(this).html());
});
});
EXAMPLE.
There are two ways:
var cells = table.getElementsByTagName("td");
for (var i = 0; i < cells.length; i++) {
cells[i].onclick = function(){tes();};
}
and the other way using jQuery:
$('td').click(function(){tes();});
upd:
To get exactly what is needed, firstly the table must be selected, so, for the first option:
var table = document.getElementById('1');
var cells = table.getElementsByTagName("td");
...
and for the second, the jQ selector should be like this:
$('#1 td')
Just the following code will return the contained text of the clicked element. In this case the td
event.target.innerText
Example:
td
{
border: 1px solid red;
}
<table onclick="alert(event.target.innerText)">
<tr>
<td>cell 1</td>
<td>cell 2</td>
</tr>
<tr>
<td>cell 3</td>
<td>cell 4</td>
</tr>
</table>
Of course you can implement it into a js and attach it to the onclick event as usual (search for the addEventListener() function in javascript).
If needed you can separate the table into thead, tbody tfoot and add the onclick event to the tbody only. In this way the event will be triggered only if the user clicks on this section of the table and not when he clicks on other elements...
Try :
var tds = document.getElementsByTagName("td");
for(var i in tds) tds[i].onclick = tes;
and a Demo...
Replace document with any other dom element that you need to find td's in.
You can do something like that:
var tbl = document.getElementById("1");
var numRows = tbl.rows.length;
for (var i = 1; i < numRows; i++) {
var ID = tbl.rows[i].id;
var cells = tbl.rows[i].getElementsByTagName('td');
for (var ic=0,it=cells.length;ic<it;ic++) {
cells[ic].OnClick = new function() {tes()};
}
}
if you have access to jquery, do this
$('#1 td').click(function(){
});
You might not require putting onclick event on every TD element in your table. Providing onclickevent at the table level can do the trick easily as shown in the below code snippet:
function tes(event) {
if (event.target.nodeName == "TD") {
alert('TD got clicked');
}
}
td {
border: 1px solid black;
}
<html>
<head>
<title>Tic-Tac-Toe</title>
<meta charset="UTF-8" />
</head>
<body>
<div id="app">
<table id='gameBoard' width="300" height="300" onclick="tes(event);">
<tr>
<td data-index = "0 0"></td>
<td data-index = "0 1"></td>
<td data-index = "0 2"></td>
</tr>
<tr>
<td data-index = "1 0"></td>
<td data-index = "1 1"></td>
<td data-index = "1 2"></td>
</tr>
<tr>
<td data-index = "2 0"></td>
<td data-index = "2 1"></td>
<td data-index = "2 2"></td>
</tr>
</table>
</div>
</body>
</html>
You can do the appropriate handling inside tes function with the help of event parameter.

Categories