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

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>

Related

Cant get the length of my table in java script

I have following HTML code for an table:
<div id="table">
<table>
<thead>
<tr>
<th>Product</th>
<th>net</th>
<th class="green">VAT</th>
</tr>
</thead>
<tbody>
<tr>
<td class="red">Lipstick</td>
<td>€6.58</td>
<td>19</td>
</tr>
<tr>
<td class="red">Shoelaces</td>
<td>€7.34</td>
<td>19</td>
</tr>
<tr>
<td class="apple">Apple</td>
<td>0.43</td>
<td>7</td>
</tr>
</tbody>
</table>
</div>
The table is created with tableify.
Now i want to select a row with this function:
function selectedRow(){
var index,
table = document.getElementById("table");
for(var i = 1; i < table.rows.length; i++)
{
table.rows[i].onclick = function()
{
// remove the background from the previous selected row
if(typeof index !== "undefined"){
table.rows[index].classList.toggle("selected");
}
console.log(typeof index);
// get the selected row index
index = this.rowIndex;
// add class selected to the row
this.classList.toggle("selected");
console.log(typeof index);
};
}
}
selectedRow();
Here is the following CSS code so that the the background geht a color:
.selected{
background-color: brown;
color: #fff;
}
Normally should the row, that i selected turn brown, but i get a error, that length can't be read. If i have a normal table without and >tbody>, the code works.
How can i get the body so that can i read the length and the row?
Now that you updated the question you added a parent div to fetch the child table. So the selector in that case should be: #table > table
Here's a code that achieves that same result in a better way and using addEventListener instead of the onclick property.
Two strategies are shown to listen for the click event:
Having a click event handler for each row
Having a click event handler for the whole table
The first one just listen for the click event for any single row in the table and will check if the table has any selected row before attempting to toggle the class or if the clicked row is the one having the selected class.
The second has one listener for the click event on the table only. Since the event bubble starting from the exact child element clicked (a td for example) it will trigger anyway but it needs to make further checks to better target the context.
In the end...
I left the first strategy cabled with the logics and made it more narrow so that you can select any row at any moment and it will become the only selected row in the table
addClickEventToTableRows();
//adds a click event handler to the table
function addClickEventToTable(){
const table = document.querySelector("#table table");
table.addEventListener('click', tableOnClick);
}
//the click event handler for the table
function tableOnClick(event){
const clickedElement = event.target;
const hasTbodyAsParent = clickedElement.closest('tbody') !== null;
//if the child element clicked of the table, is a child of tbody
if ( hasTbodyAsParent ){
//the clickedrow taken with .closest (since clickedElement could be a td)
const clickedRow = clickedElement.closest('tr');
clickedRow.classList.toggle("selected");
clickedRow.closest('table').removeEventListener('click', tableOnClick);
}
}
//adds a click event handler to all the rows of the table
function addClickEventToTableRows(){
//the table element
const table = document.querySelector("#table table");
//foreach row element in the tbody of the currently selected table
table.querySelectorAll(':scope > tbody > tr')
.forEach(row => {
//add a click event listener to the current row
row.addEventListener('click', (event)=>{
//retrieves the clicked table row
const clickedRow = event.currentTarget;
console.log(`Row: ${clickedRow.rowIndex} was clicked`);
//if there's not any row in the table with the 'selected' class OR this row has the class selected
//if(!clickedRow.closest('tbody').querySelector('.selected') || clickedRow.classList.contains('selected'))
table.querySelectorAll(':scope > tbody > tr.selected').forEach(trSelected => {
trSelected.classList.remove('selected');
});
//toggle its selected class
clickedRow.classList.toggle("selected");
});
});
}
#table > table > tbody > tr{
cursor: pointer;
}
#table > table{
border-collapse: collapse;
}
#table > table > tbody td{
border: solid 1px lightgray;
padding: 1rem;
}
#table > table > thead th{
background: gray;
color: white;
font-weight: 600;
font-size: 1.5rem;
padding: .5rem;
}
.selected {
background-color: brown;
color: #fff;
}
<div id="table">
<table>
<thead>
<tr>
<th>Product</th>
<th>net</th>
<th class="green">VAT</th>
</tr>
</thead>
<tbody>
<tr>
<td class="red">Lipstick</td>
<td>€6.58</td>
<td>19</td>
</tr>
<tr>
<td class="red">Shoelaces</td>
<td>€7.34</td>
<td>19</td>
</tr>
<tr>
<td class="apple">Apple</td>
<td>0.43</td>
<td>7</td>
</tr>
</tbody>
</table>
</div>

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>

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.

finding the closest previous sibling that contains .myClass

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/

Categories