In order to make my code concise and reduce unnecessary dom rendering, I'm trying to construct and populate a table before appending it to the document. The problem I'm having is that only the divs are getting appended to the table, so I end up with a table full of divs instead of tr's with td's that contain divs.
I'm pretty sure this is because when I use the .appendTo function it's not appending the td to the tr, and the tr to the table but instead is removing the div and appending it to each in turn, lastly ending up in the table.
How can I construct a node chain before appending to the document?
Code:
var playerSelect = $( "#playerSelect" );
var playerElements = [];
var rowCounter = 0;
var playerTable = $("<table/>").attr("id", "playerTable");
for (player in playerBase){
var playerDiv = $("<div/>").addClass("player").text(player + playerBase[player].rating);
playerDiv.appendTo("<td/>").appendTo("<tr/>").appendTo(playerTable);
};
playerSelect.append( playerTable );
.appendTo() does not accept a string value. $() does however, so you could change your code like this:
$("<tr />").append($("<td />").append(playerDiv)).appendTo(playerTable);
That said, this is not the cleanest way to do it, you might want to have a look at templating engines if you have a lot of these structures in your code.
for (player in playerBase){
var tr = $('<tr/>');
var td = $('<td/>');
td.appendTo(tr);
var playerDiv = $("<div/>").addClass("player").text(player + playerBase[player].rating);
playerDiv.appendTo(td);
tr.appendTo(playerTable);
};
Related
I've tried with a stupid way of inserting code for a new table, but not even that seems to work. What would be the proper way?
Here's what I tried to do:
var table = document.getElementsByClassName("test")
[0].getElementsByClassName("tableclass");
for (var i = 0, l = table.length; i < l; i++) {
var content = table[i];
let s = content.innerHTML;
s = s.replace(/table/g, 'table border="1"');
s = s.replace(/tr>[\s\S]*?<tr>[\s\S]*?<td>3/g, 'tr>\r\n<tr>\r\n<td>3');
content.innerHTML = s;
}
And a fiddle: https://jsfiddle.net/d10tk7nr/1/
Also, the reason my stupid way doesn't contain the whole table is because some of the cells where I want to eventually use this would contain random data and I don't know how to skip that.
If you want to create a new HTML-Element, every browser got you covered on that.
var tr = document.createElement('tr');
console.log(tr);
The browser console will show you exactly what you have created - a new HTML element that is not yet part of the DOM:
<tr></tr>
The same goes with the creation of some content for that table row:
var td1 = document.createElement('td'),
td2 = document.createElement('td');
td1.innerText = '5';
td2.innerText = '6';
console.log(td1, td2);
The result will be two td-elements:
<td>5</td> <td>6</td>
Now we have to glue these parts together. Browsers will also have you coverd on this:
tr.append(td1);
tr.append(td2);
console.log(tr);
The result is a complete table row:
<tr><td>5</td><td>6</td></tr>
All we have to do is append this row to your table:
var table = document.querySelector('.test table tbody');
table.append(tr);
The elements you have created are now part of the DOM - child elements of the body of your table to be excact.
Click here for a fiddle
Edit
If you want to insert the new row to a specific place, you have to find the element you that should be next to it and use insertBefore. This would change the the last piece of code to:
var targetTr = document.querySelector('.test table tr:nth-child(2)');
targetTr.parentNode.insertBefore(tr, targetTr);
If you want to choose where to put your new row within your javascript, you can use the childNodes property:
console.log(table.childNodes);
I'd use insertAdjacentHTML, like so:
table[i].insertAdjacentHTML('beforeend', '<tr><td>5</td><td>6</td></tr>');
Please see this fiddle: https://jsfiddle.net/52axLsfn/4/
Also demonstrates how to set the border. Note that this code targets all tables, so depending on your situation you may want to be more specific.
I have a table like this.
<table id='table1'>
</table>
and in this table i am dynamically adding rows to the table using jquery like below.
var tbl = $("#table1");
tbl.append('<tr></tr><tr></tr><tr></tr>');
I can add class to the appended rows using 2 methods like below
case 1
tbl.find('tr').eq(0).addClass("test");
tbl.find('tr').eq(1).addClass("test");
or case 2
for (var i=0;i<tbl.find('tr').length;i++) {
tbl.find('tr').eq(i).addClass("test")
}
and my question is there any way i can add same classname to the dynamically appended rows. Answers expecting in jquery. Thanks in advance.
Once an element is added to the DOM, you have no way of telling if it was dynamically added or not unless you have custom code that does such. I would suggest changing .append to .appendTo so you have access to the rows you're adding and can call .addClass:
var tbl = $("#table1");
$('<tr></tr><tr></tr><tr></tr>').appendTo(tbl).addClass("test")
You could also put the class in the string then append it or modify it (add a class) prior to appending it. Note how I only have one tr in my string but append it multiple times (optionally adding a class as noted)
var tbl = $("#table1");
var tr = '<tr class="test"></tr>';
var td = '<td class="test">new data</td>';
//var addedrow = $(tr).append(td).addClass("newclass");//adds class to the row
var addedrow = $(tr).append(td);//create new row object
addedrow.find('td').addClass("newclass"); //adds class to the td in the new row
var ar2 = $(tr).append(td);
var ar3 = $(tr).append(td);
tbl.append(addedrow, [ar2, ar3]); // appends the new rows
tbl.find('tr').last(td).addClass("newclass");//add class to last rows td
see it in action here: http://jsfiddle.net/MarkSchultheiss/0osLfef3/
I have to pass HTML around in as a string (as I'm using postmessage for communication). To apply modifications to the html, I'm doing:
function foo(my_string) {
var temp, element_list;
temp = document.createElement("div")
temp.innerHTML = my_string;
element_list = temp.querySelectorAll(".foo");
...
My problem is that my_string can be anything and in case I'm passing a string with table rows and cells like this:
'<tr>' +
'<td>' +
'<a href="#gadget._key=project_module%2F1&gadget.view=view">' +
'My Test Project 2014/12/16 14:24:48.930904 GMT' +
'</a>' +
'</td>' +
'...' +
'</tr>'
appending this to a <div> removes the table rows and cells and I'm left with links only. Something like this:
'<a href="#gadget._key=project_module%2F1&gadget.view=view">' +
'My Test Project 2014/12/16 14:24:48.930904 GMT' +
'</a>' +
Question:
Is there a generic element, which accepts any type of child elements and does not modify whatever it's passed via innerHTML?
Thanks!
Edit:
The method is used to translate html snippets. When I'm updating a table, it will only pass the generated table rows vs receiving the whole table on the initial page rendering.
There isn't such an element. <tr> is a very good example of this. According to W3C standards, the "Permitted parent elements" for <tr> are "A <table>, <thead>, <tbody> or <tfoot> element."
If you must have these strings coming in as they are, your best bet is to perform some sort of detection as to the type of element(s) you are inserting, and wrap them in the appropriate HTML if required.
For example: (View as a CodePen)
HTML
<div id="container"></div>
JavaScript
var anyone = "<div>In a Div</div>";
var tableOnly = "<tr><td>In a..</td></tr>" +
"<tr><td>...table</td></tr>";
$(function () {
var $container = $("#container");
appendContent(anyone);
appendContent(tableOnly);
function appendContent(html) {
var $html = $(html),
$parent = $(),
lastParent = "";
$html.each(function () {
var parent = parentTag(this.tagName);
if(parent !== lastParent)
{
$container.append($parent);
$parent = $(parent);
}
$parent.append(this);
lastParent = parent;
});
$container.append($parent);
}
function parentTag(tagName) {
switch (tagName.toLowerCase()) {
case "tr":
return "<table></table>";
default:
return "<div></div>";
}
}
});
Edit: Note that the technique used here to detect the tags used in your HTML can have problems if your HTML contains content that cannot be part of the same parent. For example, the following code would fail:
appendContent("<tr><td>Also in a table</td></tr><div>Also in a div</div>");
This is because of how jQuery internally builds its selectors. Since you can't have a div tag as a sibling to a tr, effectively the div element gets dropped. Here's a CodePen demonstrating this, but from the sound of things, this wouldn't be an issue for the OP's needs. If it is, you could use some alternative method of detecting the tags such as Regular Expressions.
If you append the mal-formatted HTML data (as you've noticed) with missing tags you're at the Browser DOM parser mercy removing every single one till a conformable HTML is returned.
If your main concern (project-wise) is just about table HTML content than you could
treat the string as an XML data structure and get the needed wrapping tag and act accordingly:
jsBin demo
function sanitizeHTML( string ) {
// Treat friendly a HTMLString as XML structure:
var par = new DOMParser();
var doc = par.parseFromString(string, 'text/xml');
var chd = doc.firstChild;
var tag = chd.nodeName.toUpperCase(); // Get the tag
var ele;
function wrapWith(parent, childEl){ // Wrap a node into a parent
var p = document.createElement(parent);
p.appendChild(childEl);
return p; // And return that parent element.
}
if(/^(THEAD|TBODY|TR)$/.test(tag)){ // If THEAD or TBODY or TR
ele = wrapWith("table", chd); // just wrap in TABLE.
}else if(/^(TD|TH)$/.test(tag)){ // Else if TD or TH
ele = wrapWith("tr", chd); // wrap first in TR
ele = wrapWith("table", ele); // and than in TABLE.
}else{
// All fine. Do we need something here?
}
return ele || chd; // Returns a HTMLElement
}
// This will return the final HTMLElement:
// var myEl = sanitizeHTML( str );
// Let's see in console:
console.log( sanitizeHTML( str ).outerHTML );
For simplicity sake the above code will consider strings with only one children.
To extend it - loop all the children of the doc object.
See this jsfiddle: http://jsfiddle.net/Grezzo/x1qxjx5y/
With <tr>s in <table>s they are ok.
It's because you are putting a <tr> in a <div> which isn't valid.
Putting unsanitized content in the page like this is a real security risk
Update: I updated the jsfiddle to include two <div>s that were not modified by javascript and you can see that the <tr>s are stripped if they are not in a <table> parent: http://jsfiddle.net/Grezzo/x1qxjx5y/1/
I'm creating a JavaScript progress bar and the bar itself and the detail message are inside a table. Now, I'm creating this so that all that needs to be in the page is a div and then the class will fill in the rest when it's created. Since it's in a table, the bar and message are supposed to be on different rows, however when I try to create the rows with jQuery they aren't getting generated and the only thing that is getting put in the tables is the two td elements.
The code I currently have is down below. I've tried several different methods to accomplish it that I thought would work.
I have tried using .wrap('<tr></tr>') to try and get it before I put it in the table, and in the call for the table too (i.e. tdMessage.wrap('<tr></tr>') and tdMessage.wrap('<tr></tr>').html()).
I have tried both document.createElement('tr') and just $('<tr></tr>') and calling .html() when putting it in the table.
I feel like there was another attempt in there too...but I can't think of what it was.
var tdMessage = $(document.createElement('td'));
tdMessage.prop('id', this.MessageId.substr(1));
tdMessage.css('text-align', 'center');
//tdMessage.wrap('<tr></tr>');
//var trRow2 = $(document.createElement('tr'));
var trRow2 = $('<tr></tr>');
trRow2.html(tdMessage);
tdMessage = null;
var divBar = $(document.createElement('div'));
divBar.prop('id', this.BarId.substr(1));
divBar.css('width', '0%');
divBar.css('height', '15px');
divBar.css('background', 'url(images/LoadingBarBG.gif)');
var tdBar = $(document.createElement('td'));
tdBar.css('border', '1px #B0B1B1 solid');
tdBar.css('padding', '1px');
tdBar.html(divBar);
//tdBar.wrap('<tr></tr>');
divBar = null;
//var trRow1 = $(document.createElement('tr'));
var trRow1 = $('<tr></tr>');
trRow1.html(tdBar);
tdBar = null;
var tblInner = $(document.createElement('table'));
tblInner.prop('width', '400');
tblInner.prop('cellpadding', '0');
tblInner.prop('cellspacing', '0');
tblInner.prop('border', '0');
tblInner.html(trRow1.html() + trRow2.html());
trRow1 = null;
trRow2 = null;
I'm probably just missing something, but I can't for the life of me figure it out. Everything looks like it should work, and everything else seems to be.
Also, the HTML that it keeps generating is either just putting both td elements in the table without the tr elements surrounding them or it will even just put the bars td and omit the message one.
Thanks for any help.
Don't use .html() because everything you have is a jQuery object, not raw HTML, instead append the cell to the row:
trRow2.append(tdMessage);
trRow1.append(tdBar);
Then append the rows to the table:
tblInner
.append(trRow1)
.append(trRow2);
Do the same with your div when you want to insert it into the cell:
tdBar.append(divBar);
Update: I just narrowed my problem to this:
Why doesn't this work:
var tmp = document.createElement('tbody');
tmp.innerHTML="<tr><td>hello</td></tr>";
tmp is getting the string hello. the tr and td html is lost (on FireFox).
Why is that? and how can I make such html injection work?
Original question:
I need to inject arbitrary HTML after a arbitrary element in arbitrary HTML documents.
I came across this method (inject the html string into dynamically generated div, get its firstchild element and insert it in the right place):
var tmp = document.createElement('div');
tmp.innerHTML = _injected_html;
var new_w = tmp.firstChild;
var parent = insertion_point.parentNode;
parent.insertBefore(new_w, insertion_point.nextSibling);
The problem is that this does not work when trying to inject table elements.
if the injected html is for example
"<tr> <td> table data </td> </tr>"
The _tmp.innerHTML = _injected_html; would not accept it (adding tr under div element).
Any idea how to make this work for any tag?
Are you testing in IE by any chance? Most likely it does work in other browsers.
Here's why
edit: Wait, you're inserting something into the table that looks like <div><tr><td>... that's not going to work. Why don't you replace the document.createElement('div') by document.createElement('tr'), and remove the <tr> tags from the _injected_html?
Something like this (tested in Firefox):
<script>
var i = 3;
function f() {
var table = document.getElementById('someTable');
var children = table.children[0].children;
var after = children[Math.round(Math.random() * (children.length - 1))];
var html = "<td>" + i++ + "</td>";
g(html, after);
}
function g(_injected_html, insertion_point) {
var tmp = document.createElement('tr');
tmp.innerHTML = _injected_html;
var new_w = tmp.firstChild;
var parent = insertion_point.parentNode;
parent.insertBefore(new_w, insertion_point.nextSibling);
}
</script>
<table id="someTable" onclick="f();">
<tr><td>1</td></tr>
<tr><td>2</td></tr>
</table>
The second line of f() is a little awkward, but it gets the first child of the table (which is a <tbody>, and then its children (the actual <tr>s).
<div><td><lol/>
..isn't valid HTML! Containers are required for table rows/cols/heads, list items, definition lists and so on. Could you somehow validate the HTML for proper containers before injecting it?
The following javascript will allow you to inject HTML/etc into the local page:
var example = "<p>test</p>"
document.body.appendChild(example);
That said, you will have to customize the code depending on what you are inserting.
For a table, you must insert tr's into tbody. When you write html
<table><tr><td>abc</td></tr></table>
IE, FF, Chrome, Safari at least (don't know about others directly) will modify this to be:
<table><tbody><tr><td>abc</td></tr></tbody></table>
Therefore, something like:
var tmp = document.createElement('tr');
tmp.innerHTML = "<td>def</td>";
var new_w = tmp.firstChild;
var parent = insertion_point.parentNode;
parent.insertBefore(new_w, insertion_point.nextSibling);
if insertion_point is a tr tag.
But honestly, with Jquery there are more elegant ways of going about this as christina toma notes.