insertBefore not updating rowIndex/nextSibling properties - javascript

It may sound stupid or even trivial for most experienced users, but I just landed a few hours ago on front-end javascript and I must say I am a bit puzzled with the behavior of the insertBefore javascript function.
My intention here is plain and simple: I have a table with its rows and cells, and in each row I have a cell with a button with the only purpose of duplicating that cell (with all its contents) and place the new duplicated cell right next to the original one.
I have a javascript function for it such like this one:
// id -> the id of the table I want the row to be added
// caller -> the object of the element that called the function
function duplicateRow(id, caller)
{
const table = document.getElementById(id);
const row = caller.parentNode.parentNode; // Caller is always a button inside a cell inside a row
const clone = row.cloneNode(true);
table.insertBefore(clone, row.nextElementSibling);
}
This function is called like this (from an extract of my HTML):
<tr>
<td>
<input type="text" name="competence-name">
</td>
<td>
<button name="duplicate-row-button" onclick="duplicateRow( 'competencies-table', this )"></button>
</td>
</tr>
So, what I would expect from it is that, at each click on the duplicate row button, it would create an exact copy of the row where the button is being clicked and add it right after that row.
My problem here is not with the duplicating (that is done just right and smooth as one would expect) but with where the new row is placed:
The first time, when there is only one row, it is placed at the end (since nextSibling is null).
The second time clicking the button on the first row (despite now having a sibling right after it), the new row is again placed at the end of the table (as if nextSibling for the first row was still null).
And so on (even strager placements happen when mixing duplications with the newly added rows).
Shouldn't the nextSibling and/or rowIndex properties be updated when adding a new node to the DOM? Is there a way of forcing them to update? What is it that I have wrong? My code, my understanding of how it should work?
I am surely open to any possible explanation/solution/alternative to achieve what I need, and thank you all in advance!

The problem is that initial table row is wrapped in a tbody element (for which you can omit both start and end tag), which is required according to the content model of tables. However, when you programmatically add more rows, they are inserted outside the tbody and your initial row is the only child of that implicit tbody, so the DOM tree looks like this:
<table>
<tbody>
<tr></tr>
</tbody>
<tr></tr>
<tr></tr>
</table>
To solve it I suggest to add a clone to cloned row's parent:
function duplicateRow(caller){
const row = caller.parentNode.parentNode; // Caller is always a button inside a cell inside a row
const clone = row.cloneNode(true);
row.parentNode.insertBefore(clone, row.nextElementSibling);
}
<table id="competencies-table">
<tr>
<td>
<input type="text" name="competence-name">
</td>
<td>
<button name="duplicate-row-button" onclick="duplicateRow( this )">Duplicate</button>
</td>
</tr>
</table>

Related

Drag-and-Dropped Row's Parent Element is Null

I have two tables. The client should be able to drag and drop each table's row. They can either drop the row into a new position on the current table, or add the row to a specific position on the other table.
I'm able to insert the new row into the desired position on either table (and delete the original), but when I try to drag-and-drop that element a second time, it's parent element comes back as null, preventing me from repeating the drag-and-drop function a second time.
I need to solve this with pure, vanilla javascript.
//Table Structure (there are two of these, I'm trying to save space here)
<table id="thisTable" class="dropzone">
<thead>
...
</thead>
<tbody id="thisBody">
<tr id="123" class="drag-row draggable="true">
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr id="124" class="drag-row draggable="true">
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
//Collect Variables from the original row, the target table and the row being dropped into.
//Code to Insert New Row
function addRow(table_id, copy_id, target_id) {
let targetRow =document.getElementById(target_id);
let targetIndex = targetRow.rowIndex;
let copyRow = document.getElementById(copyId); // find row to copy
let targetTable = document.getElementById(tableID); // find table to append to
let clone = copyRow.cloneNode(true); // copy children too
//clone.id = "newID"; // change id or other attributes/contents
//table.appendChild(clone); // add new row to end of table
//targetTable.children[1].insertAdjacentElement("afterbegin", clone);
// Insert a row at desired index
let newRow = targetTable.insertRow(targetIndex);
newRow.id = clone.id;
newRow.draggable = "true";
newRow.classList.add("drag-row");
newRow.innerHTML = clone.innerHTML;
}
//Code to delete original row
dragSrcEl.remove();
I don't think I'll need to add in all the drag-and-drop code, because those seem to be working fine (when the parent element of the inserted node isn't null).
I've gone through a couple questions similar to this one and it seems that some commands in vanilla create new elements without tying them to the DOM? Supposedly by using the append() command you could add a new row to a table without disconnecting it from the DOM, but I tried that and got no result.
Which of the commands I'm using above would disconnect the newly inserted row from the DOM?
Is there a safe way, in vanilla js, to copy the contents of a row and inserted into a specific index of another (or the same table)?
It's frustrating because I finally got the code working... once.
Any thoughts?
Ah, I forgot to add the necessary event listeners to the newly inserted row, that would explain why drag-and-drop stopped working for them :)

Expand Row of Table on Click and draw D3 charts inside

I am trying to create a Table in which a row expands on click and two D3 charts are drawn inside the new expanded row.
My logic for Expand Row:-
<tbody>
<tr ng-repeat-start="item in nodeSummary">
<td>
<button ng-if="item.expanded" ng-click="tableRowExpand($index,false)">-</button>
<button ng-if="!item.expanded" ng-click="tableRowExpand($index,true)">+</button>
</td>
<td>{{item.a}}</td>
<td>{{item.b}}</td>
<td>{{item.c}}</td>
</tr>
<tr ng-if="item.expanded" ng-repeat-end>
<td colspan="4" style="height:100px;width:100%;">
<div id="expanded-tablerow-container" style="height:100%;width:100%;overflow:auto;" >
<div id="expanded-tablerow-circleProgress" style="height:100%;width:20%;float:left;">Here</div>
<div id="expanded-tablerow-barchart" style="height:100%;width:80%;float:left;">Here</div>
</div>
</td>
</tr>
</tbody>
So using ng-repeat-start/end, and onClick of button the particular row is expanded/shrinked.
tableRowExpand function:-
$scope.tableRowExpand = function(index, value){
$scope.nodeSummary[index].expanded = value;
if (value){
drawCircularProgressBar(d3.select("#expanded-tablerow-circleProgress"),0.8);
drawHorizontalBar("#expanded-tablerow-barchart",data);
}
};
So on checking value is true I am calling methods to draw d3 chart and bar. But they are not drawing.
My Problems:-
Well, I looked into the problem and it seems the problem is calling these method before the changes to the document is done, How I know this? Well, suppose if there are two rows, then on expanding 1st row, nothing is drawn but on then clicking the 2nd row, bar and chart is drawn on 1st row's expanded space. Am I able to make myself clear? So how to fix this problem?
Multiple rows can remain expanded at a time. I may have 100s of rows, and the d3 drawing is done based on data of each row which will vary, so each row will have unique d3 drawing on its expanded view. But as the drawing is done by passing the divs, which for all expanded rows will be same, won't this kill the uniqueness, saying same drawing on all expanded view?. In a way, how can I have unique drawing for each row?
Hope I was able to make myself clear. Please help me resolve this.
Thanks.
Well I was finally able to solve it. Lets go for it.
Problem 1)
Yeah I was right :) The problem is that the document was not fullt loaded, I solved adding a simple check code,
angular.element(document).ready(function(){
//calling the d3 draw code here
});
Problem 2)
I don't know if this is the most efficient way, if someone want to input, please do, but I did this.
var newCircleProgressId = 'expanded-tablerow-circleProgress' + index;
var newHoizontalBarId = 'expanded-tablerow-barchart' + index;
document.getElementById("expanded-tablerow-circleProgress").setAttribute('id',newCircleProgressId);
document.getElementById("expanded-tablerow-barchart").setAttribute('id', newHoizontalBarId);
i.e. dynamically altering the ID of the div inside the tableRowExpand function and passing this new ID along with row unique(extracted using index) to draw d3 functions.

Get index from table (the tr has no ID)

I'm dynamically creating and appending into a table, a <tr> and a <td> with the following code:
$("#table1").append(
'<tr>'+
'<td style="cursor:pointer" onclick="pass.data.carregar(this.parentNode.rowIndex-2);">'+$("#DRVR_NAME").val()+'</td>'+
'</tr>');
It's Ok so far: I can load the Td data into another fields that I want and there's no problem on creating it.
But beyond creating it I must allow the user to remove the <td> dynamically, but as you can see, there's no ID to look for. What's the better way to allow the user to remove the <td> ?
Im trying the following code:
$("#table1>tbody>td>tr."+teste+"").remove();
But no success! The test variable is a number that I automatically define for the register.
Any help?
If test is the 0-based index of the row you want to remove...
$("#table1>tbody>tr:eq("+test+")").remove();
Set a unique id attribute when you dynamically create each cell.
<tr id="uniqueID"><td>Cell Content</td></tr>
Then use simple jquery to remove the row by id.
$('#uniqueID').remove();
Your remove code assumes there is a class on the TD. You'll have to target it with :eq() by referencing the value in your hidden field. If the value in the hidden field is a number (the index of the td the user selected = your counter), you could try something like this:
var hiddenFieldValue = $('input.hiddenFieldClassName').val();
$("#table1>tbody>tr>td:eq("+hiddenFieldValue+")").remove();
You could add a little x next to each driver name that will delete that td by changing your code a bit:
$("#table1").append('<tr>'+'<td style="cursor:pointer" onclick="pass.data.carregar(this.parentNode.rowIndex-2);">'+$("#DRVR_NAME").val()+'<span onclick="$(this.parentNode).remove()">x</span></td>'+'</tr>');
Or you could add another function to the click handler that you are binding to each <td> that will display a message in a predefined <div> asking the user if s/he wants to delete that element:
$("#table1").append('<tr>'+'<td style="cursor:pointer" onclick="pass.data.carregar(this.parentNode.rowIndex-2);askDelete(this);">'+$("#DRVR_NAME").val()+'</td>'+'</tr>');
and then define a message area (id = 'messageArea') somewhere in your HTML and the following function in your code:
askDelete(el){
var MA=$('#messageArea');
MA.empty();
MA.html('Do you want to delete ' + el.innerHTML + '?<br>').append('<span id="confirmDelete">delete</span> <span id="cancelDelete">cancel</span>')
MA.find('#confirmDelete').click(function(){$(el).remove();});
MA.find('#cancelDelete').click(function(){MA.empty();})
}
then style your delete and cancel buttons as you wish.

Trigger a link in adjoining table cell using jQuery

I have an HTML table where the first cell of each row contains text and the second cell contains a link. Like so:
<table id="foo">
<tr>
<td>Some text</td>
<td><img src="/path/to/image.jpg" /></td>
</tr>
...
</table>
I have a hover effect on the first table cell and when a user clicks on the first cell I wanted to trigger the link in the next cell so I chained a click function sort of like this:
$('#foo tr td:first-child').hover(...).click(
function() {
$(this).next().children().click();
}
);
This doesn't work. I've tried a few different ways with no success. I realise that I could put the text and link in the same cell and may end up doing that, but this is a simplified version of how my page actually is and it would take a bit of work to reshuffle things.
What am I doing wrong?
Clicking a link programmatically won't go to the href, only a native (user) click will, you can emulate it though, like this:
$('#foo tr td:first-child').hover(...).click(
function() {
window.location.href = $(this).next().children('a').attr('href');
}
);
This sets the window.location.href to go to the destination URL.

Easily setting field values with JavaScript

Wasn't really sure how to ask the question, but my situation is this...
I have a table with one row, which contains a few input boxes. When a user presses the add new, I have managed to add another row to the table. First I make a new variable called oldtable, and then split it at each <tr>.. and save that to the array rows. I use this code:
var newdata="";
for(i=0;i<rows.length;i++)
{
// adds the next row on...
newdata=newdata+rows[i]+"<tr>";
}
newdata=newdata+"MY NEW ROW GOES HERE!";
Then I go on to reset the table innerHTML to the newdata variable.
All works fine, until the user enters data in the first row, and then goes to add data in the second. Then the values on the first row are gone, understandably. Taking into account that the table can theoretically have as many rows as the user wants, how can I go about keeping the values of the first row intact? Is there a way to insert data into the table without reseting what's there?
Thanks.
var olddata = document.getElementById("products_table").innerHTML;
// This splits up each row of it
var rows = olddata.split("<tr>");
// Makes a variable where the new data is going to be stored
var newdata = "";
// This loop is to add back the existing html into the table, it is one short, because we Omit the "add new product" row.
for(i=0;i<rows.length-1;i++)
{
// adds the next row on...
newdata=newdata+rows[i]+"<tr>";
}
// Adds the additional html needed to
newdata=newdata+"my new row goes here!";
// Add newly edited table back in...
document.getElementById("products_table").innerHTML=newdata;
Instead of manipulating HTML in string form (very error prone), you should use the built-in DOM manipulation methods to add new rows to your table. Make sure your table has a TBODY element and give it an ID:
<table id = "product_table">
<tbody id = "product_table_tbody">
<tr>
<td>A Cell</td>
</tr>
</tbody>
</table>
Then, attach an event handler to the Add Row button and do something like this:
function addRow() {
var myTbody = document.getElementById("products_table_tbody");
var myRow = document.createElement("tr");
var myCell = document.createElement("td");
myCell.innerHTML = "My new cell";
myTbody.appendChild(myRow);
myRow.appendChild(myCell);
}

Categories