What is the best plain javascript way of inserting X rows into a table in IE.
The table html looks like this:
<table><tbody id='tb'><tr><td>1</td><td>2</td></tr></tbody></table>
What I need to do, is drop the old body, and insert a new one with 1000 rows. I have my 1000 rows as a javascript string variable.
The problem is that table in IE has no innerHTML function. I've seen lots of hacks to do it, but I want to see your best one.
Note: using jquery or any other framework does not count.
Here's a great article by the guy who implemented IE's innerHTML= on how he got IE to do tbody.innerHTML="<tr>...":
At first, I thought that IE was not
capable of performing the redraw for
modified tables with innerHTML, but
then I remembered that I was
responsible for this limitation!
Incidentally the trick he uses is basically how all the frameworks do it for table/tbody elements.
Edit: #mkoryak, your comment tells me you have zero imagination and don't deserve an answer. But I'll humor you anyway. Your points:
> he is not inserting what i need
Wha? He is inserting rows (that he has as an html string) into a table element.
> he also uses an extra hidden element
The point of that element was to illustrate that all IE needs is a "context". You could use an element created on the fly instead (document.createElement('div')).
> and also the article is old
I'm never helping you again ;)
But seriously, if you want to see how others have implemented it, take a look at the jQuery source for jQuery.clean(), or Prototype's Element._insertionTranslations.
Do as jQuery does it, eg. add <table> and </table> around it, insert into document and move the nodes you want to where you want them and ditch the temp-element.
the code ended up being this:
if($.support.scriptEval){
//browser needs to support evaluating scripts as they are inserted into document
var temp = document.createElement('div');
temp.innerHTML = "<table><tbody id='"+bodyId +"'>"+html;
var tb = $body[0];
tb.parentNode.replaceChild(temp.firstChild.firstChild, tb);
temp = null;
$body= $("#" + bodyId);
} else {
//this way manually evaluates each inserted script
$body.html(html);
}
Things that beed to exist beforehand: a table that has a body with id of 'bodyId'. $body is a global variable (or the function has a closure on it), and there is a bit of jquery in there too, because IE does not evalute scripts that are inserted into the html on the fly.
I had the same problem (as do lots of other people) and after a lot of playing around here's what I got to work.
You have to make a tr via document.createelement ('tr') then make a td, the same way.
appendchild the td to the tr, appendchild the tr to tbody (not table) THEN you can innerhtml the td you created and it will work.
This was ie8 I was using. Basically the table structure has to be made with createelement but the rest of it can be innerHTMLed. I was doing this watching in the IE8 debugger and it would say it would add it (if I did tr.innerhtml="blah") and give no error, but it wouldn't display, and the html dom view showed a very broken table (no td ever showed up, but the /td did)
So when finally I did the tr AND td by createelement calls, it created a correct looking dom and drew the page correctly.
Related
We have a very large website that is quite old and has a lot of 'b' tags. My boss wants to change them to 'strong' tags but this will require a lot of time to change manually so she was hoping we could change it with some code.
I had a nice bit of JQuery code that worked (intermittently), but I couldn't get it to work on the site as it uses JQuery 1.9.1 and cannot be upgraded.
I then found this piece of Javascript which does what I need but only works on the first 'b' tag on the page and all others stay as 'b' tags. I don't know enough about Javascript selectors to change the firstChild selector.
<script>
function replaceElement(source, newType) {
// Create the document fragment
const frag = document.createDocumentFragment();
// Fill it with what's in the source element
while (source.firstChild) {
frag.appendChild(source.firstChild);
}
// Create the new element
const newElem = document.createElement(newType);
// Empty the document fragment into it
newElem.appendChild(frag);
// Replace the source element with the new element on the page
source.parentNode.replaceChild(newElem, source);
}
// Replace the <b> with a <div>
replaceElement(document.querySelector('b'), 'strong');
</script>
You might use querySelectorAll:
Array.from(document.querySelectorAll('b')).forEach(e=>{
replaceElement(e, 'strong');
});
But this is really a xy question. You really should do the change server side, for example by using some search/replace (learn to use your code editor). You're adding to the code debt here.
Note also that there's no obvious reason to prefer strong over b in HTML5.
Use getElementsByTagName(). It's more efficient than querySelectorAll because it doesn't have to parse selectors, and it describes better what you are really trying to do - get elements by tag name.
var elements = document.getElementsByTagName("b");
replaceElement(elements[0], "strong");
replaceElement(elements[1], "strong");
replaceElement(elements[2], "strong");
You can also iterate over this collection by using Array.from().
You would be better off finding the source of the <b> tags and changing them there as Denys has mentioned.
Updating the DOM would have little benefit and would cause performance issues when there are many tags on a page.
Does this system use a CMS or database to store the content? I would look to use something like these 2 SQL queries to replace them across the site:
update content_table set content_column = replace(content_column, '<b>','<strong>');
update content_table set content_column = replace(content_column, '</b>','</strong>');
I'm running this code:
$('#storeTable').empty();
$('#storeTable').append("<tr>");
$('#storeTable').append("<td>");
$('#storeTable').append(returnData["1"]["ID"]);
$('#storeTable').append("</td>");
$('#storeTable').append("</tr>");
However if I run it multiple times in one page load, the 'storeTable' element gets taller and taller with every run. Am I missing something obvious, is this a gotcha of jQuery/JavaScript, is it the fault of my browser (Chrome), or am I doing something wrong?
The code obviously needs refining, I'm just trying to get a bare-bones implementation of a dynamic section of a page.
If you'd like any other details, please ask, rather than just downvoting without a comment.
storeTable is, of course, a table, too.
You can't use .append() to append partial pieces of HTML. .append() only appends whole formed objects and when it appends them it appends them as the last child of the target object.
To add to that, some browsers are picking about how you can or cannot create/remove content in tables.
Assuming #storeTable is the <table> tag, you could assign .html() to create a whole new table all at once:
$("#storeTable").html("<tr><td>" + returnData["1"]["ID"] + "</td></tr>");
Working demo: http://jsfiddle.net/jfriend00/5BVdX/
$('#storeTable').empty();
// Will create three rows, one after another
$('#storeTable').append("<tr><td>123</td></tr>");
$('#storeTable').append("<tr><td>456</td></tr>");
$('#storeTable').append("<tr><td>789</td></tr>");
// Will create three rows, each next replaces previous
$('#storeTable').html("<tr><td>123</td></tr>");
$('#storeTable').html("<tr><td>456</td></tr>");
$('#storeTable').html("<tr><td>789</td></tr>");
I am using JQuery to manipulate a page which I do not have access to modify server-side. One of the main things I need to do is create a copy of just a sub-section of one of the tables on the page. More specifically, I need to create 2 tables, one which is a copy of just the first two rows of the table and another which is just the first column.
After optimizing it as far as I could, I'm fairly happy with the performance of the row copy but the column copy is still a bit on the slow side.
I have the following, where sourceTable is the original table and titleColBody is the tbody of a new table created earlier:
sourceTable.find("tr").each(function(){
titleColBody.append(
$("<tr></tr>").append($(this).children(":first").clone())
);
});
On a table with roughly 50 rows and a huge number of columns, this takes approx. 350ms in IE8. Not terrible, but I would like to bring this down a little if possible.
My question is if there is a more efficient way of doing this in JQuery.
Similarly, I create the copy of the first 2 rows of the table as below.
headerTable.find("tbody")
.append(sourceTable.find("tr:eq(0)").clone())
.append(sourceTable.find("tr:eq(1)").clone());
This is fairly quick, however if there is a better way of doing it I would be happy to hear it.
Any optimization at all would be greatly appreciated.
Seems like this might be a tad bit faster for the columns, this way you'll only grab one resource set instead of grabbing two sets (one for the TRs and then one for the child TDs):
sourceTable.find("tr > td:first-child").each(function(){
titleColBody.append($("<tr></tr>").append($(this).clone()));
});
Also on another note, if the "titleColBody" is a pointer to a DOM object that causes the browser to have to render the page when it's updated, you're better off appending to a documentFragment and then appending that fragment (so the browser only re-renders once).
For instance:
var titleCol = document.createDocumentFragment();
sourceTable.find("tr > td:first-child").each(function(){
titleCol.append($("<tr></tr>").append($(this).clone()));
});
titleColBody.append(titleCol);
im having a firefox issue where i dont see the wood for the trees
using ajax i get html source from a php script
this html code contains a tag and within the tbody some more tr/td's
now i want to append this tbody plaincode to an existing table. but there is one more condition: the table is part of a form and thus contains checkboxe's and drop down's. if i would use table.innerHTML += content; firefox reloads the table and reset's all elements within it which isnt very userfriendly as id like to have
what i have is this
// content equals transport.responseText from ajax request
function appendToTable(content){
var wrapper = document.createElement('table');
wrapper.innerHTML = content;
wrapper.setAttribute('id', 'wrappid');
wrapper.style.display = 'none';
document.body.appendChild(wrapper);
// get the parsed element - well it should be
wrapper = document.getElementById('wrappid');
// the destination table
table = document.getElementById('tableid');
// firebug prints a table element - seems right
console.log(wrapper);
// firebug prints the content ive inserted - seems right
console.log(wrapper.innerHTML);
var i = 0;
// childNodes is iterated 2 times, both are textnode's
// the second one seems to be a simple '\n'
for(i=0;i<wrapper.childNodes.length;i++){
// firebug prints 'undefined' - wth!??
console.log(wrapper.childNodes[i].innerHTML);
// firebug prints a textnode element - <TextNode textContent=" ">
console.log(wrapper.childNodes[i]);
table.appendChild(wrapper.childNodes[i]);
}
// WEIRD: firebug has no problems showing the 'wrappid' table and its contents in the html view - which seems there are the elements i want and not textelements
}
either this is so trivial that i dont see the problem OR
its a corner case and i hope someone here has that much of expirience to give an advice on this - anyone can imagine why i get textnodes and not the finally parsed dom elements i expect?
btw: btw i cant give a full example cause i cant write a smaller non working piece of code
its one of those bugs that occure in the wild and not in my testset
thx all
You are probably running into a Firefox quirk of following the W3C spec. In the spec the whitespace between tags are "text" nodes instead of elements. These TextNodes are returned in childNodes. This other answer describes a workaround. Also Using something like JQuery makes this much easier.
I would expect this behavior in any browser as the += operand overwrites what is already in the table by definition. Two solutions:
Instead of receiving HTML code from your PHP file, have the PHP generate a list of items to add to the table. Comma/tab separated, whatever. Then use Table.addRow(), Row.addCell() and cell.innerHTML to add the items to the table. This is the way I would suggest doing it, no point in creating GUI elements in two separate files.
The other solution is to save all the form data that's already been entered to local JavaScript variables, append the table, and then reload the data into the form fields.
Well, returning a JSON object with the new data seems like the best option. Then, you can synthesize the extra table elements by using it.
In case one is forced to get plain HTML as response, it is possible to use var foo = document.createElement('div');, for example, and then do foo.innerHTML = responseText;. This creates an element that is not appended to anything, yet hosts the parsed HTML response.
Then, you can drill down the foo element, get the elements that you need and append them to the table in a DOM-friendly fashion.
Edit:
Well, I think I see your point now.
wrapper is a table element itself. The nodes reside under the tbody, a child of wrapper which is its lastChild (or you can access it via its tBodies[0] member, in Firefox).
Then, using the tBody element, I think that you would be able to get what you want.
BTW, You do not need to append the wrapper to the document before appending its children to the table, so no need to hide it etc.
In my current application i need to copy the content of one table into another... With setting innerHTML it works perfectly in FF... but not in IE8...
Here is the Code i used to copy in FF:
getID("tableA").innerHTML = getID("tableB").innerHTML;
// getID is a custom function i wrote to provide a shorter version of document.getElementById();
TableA is empty (only the tbody tag exists). TableB is looking like this:
table
tbody
tr
td "Content" /td
td "Content" /td
/tr
/tbody
/table
I already tried using nodeValue.. or appendData... or outerHTML.. but nothing really worked...
Internet Explorer doesn't let you edit the inside of tables with innerHTML - it is all or nothing.
Since you are trying to use innerHTML to copy the information, a complete copy should be safe (i.e. not have any id attributes that might become duplicated), in which case I would do this:
var source = document.getElementById('tableA');
var destination = document.getElementById('tableB');
var copy = source.cloneNode(true);
copy.setAttribute('id', 'tableB');
destination.parentNode.replaceChild(copy, destination);
I'm kind of surprised to learn this didn't get fixed for IE 8. Geez, talk about dragging your feet. This is an intentional omission in Internet Explorer's implementation of innerHTML — you can't set innerHTML in a table. The creator of the feature has offered an explanation and a workaround. Basically, you can get hold of an actual tbody node and use replaceChild() to turn the original table's tbody into that.