Why doesn't this Javascript code execute sequentially? - javascript

Using jQuery I'm doing a call to my server which returns some json. I then have a callback defined using .done to create a callback, which doesn't seem to behave sequentially.
I've got a div in my html (<div id="properties"></div>), and I try to fill that div with a table of results:
request.done(function(data){
if (data['result'].length == 0) {
$("#properties").html("<h3>No results were found..</h3>");
} else {
$("#properties").html("<table><thead><tr><th>Status</th><th>Title</th></tr></thead><tbody>");
data['result'].forEach(function(prop){
$("#properties").append("<tr>");
$("#properties").append("<td>prop.status</td>");
$("#properties").append("<td>prop.title</td></tr>");
});
$("#properties").append("</tbody></table>");
}
});
The result I get is this:
<div id="properties">
<table class="table table-hover"><thead><tr><th>Status</th><th>Title</th></tr></thead><tbody></tbody></table>
<tr></tr>
<td>prop.status</td>
<td>prop.title</td>
</div>
I know that .done is only called once the ajax call returns something, but withint that call, it should behave sequentially right? There are 2 things I really really don't understand here:
Why do the table row and data get written after the </table> tag?
And why on earth does the <tr></tr> gets written before the <td> tags, even though the last </tr> is appended together with the last <td> in the lastappend()` in the foreach loop?
So I also tried appending the whole table row in one go:
$("#properties").append("<tr><td>prop.status</td><td>prop.title</td></tr>");
This works a bit better, but still only produces this:
<div id="properties">
<table class="table table-hover"><thead><tr><th>Status</th><th>Title</th></tr></thead><tbody></tbody></table>
<tr><td>prop.status</td><td>prop.title</td></tr>
</div>
Javascript has puzzled me before, but this really blows my mind. Any tips are welcome!

What you are seeing here are tags closing out on you, because those elements are getting created in whole on append/html. In order to get the behavior you're expecting build in a string, say something more like this:
request.done(function(data){
if (data['result'].length == 0) {
$("#properties").html("<h3>No results were found..</h3>");
} else {
var propertiesTableHTML = "<table><thead><tr><th>Status</th><th>Title</th></tr></thead><tbody>";
data['result'].forEach(function(prop){
propertiesTableHTML += "<tr>";
propertiesTableHTML += "<td>" + prop.status + "</td>";
propertiesTableHTML += "<td>" + prop.title + "</td>";
propertiesTableHTML += "</tr>";
});
propertiesTableHTML += "</tbody></table>";
$("#properties").html(propertiesTableHTML);
}
});

You are expecting .html() and .append() to work like document.write() but they don't. When used with HTML, they expect proper HTML. Broken HTML (for example missing end tags) is corrected which leads to the unexpected behavior. This part of your code for example:
$("#properties")
.html("<table><thead><tr><th>Status</th><th>Title</th></tr></thead><tbody>");
Produces the following result:
<table>
<thead>
<tr>
<th>Status</th>
<th>Title</th>
</tr>
</thead>
<tbody>
</tbody><!-- tag closed automatically -->
</table><!-- tag closed automatically -->
Along the same lines, this code:
$("#properties").append("<tr>");
$("#properties").append("<td>prop.status</td>");
$("#properties").append("<td>prop.title</td></tr>");
Produces the following result:
...
</table>
<tr></tr><!-- tag closed automatically -->
<td>prop.status</td>
<td>prop.title</td><!-- </tr> ignored -->
One possible solution is to revise your code like this:
$("#properties").html("<table><thead><tr><th>Status</th><th>Title</th></tr></thead><tbody></tbody></table>");
data['result'].forEach(function(prop){
var $tr = $("<tr></tr>").appendTo("#properties > table > tbody");
$("<td></td>").text(prop.status).appendTo($tr);
$("<td></td>").text(prop.title).appendTo($tr);
});

You can't add tags to the DOM, you can only add elements. When you try to add a <table> tag, it will add a complete table element. When you try to add the ending tag, it will be ignored (or possibly cause an error, depending on the browser) because it's not code that can be parsed into an element.
Rewrite the code to add elements instead of tags:
$("#properties").html("<table><thead><tr><th>Status</th><th>Title</th></tr></thead><tbody></tbody></table>");
var tbody = $("#propertis tbody");
data['result'].forEach(function(prop){
var row = $("<tr>");
row.append($("<td>").text(prop.status));
row.append($("<td>").text(prop.title));
tbody.append(row);
});
By creating table cells as elements and use the text method to set the content, you avoid the problem with any special characters that would need HTML encoding (e.g. <. >, &) to mess up the HTML code.

Related

Javascript function in PHP generated Table only works in Row 1

I have a PHP generated table that shows x number of rows based upon the number of product that is expected for that particular order. The idea is that a user can input an order number and the script will validate the input text against what is expected a return a Pass or Fail status.
This works for row 1, but after that it will not validate any of the other rows.
Reading about I am pretty sure it is due to duplicate id's, so i created a auto increment row field in the database to serve as the id. However it is beyond my skill to set the id as the row number and then validate against the order number.
Table Code:
if (sqlsrv_num_rows($getres) > 0) {
echo '<table cellpadding="0" cellspacing="0" class="db-table">';
echo '<tr><th>Row</th><th>Works Order</th><th>Scan</th></tr>';
echo 'T-Clip Scan: <br/>';
echo '<br/>';
while ($add_info = sqlsrv_fetch_array($getres)){
$row = ($add_info['row']);
$worksorder = ($add_info['id']);
print ("<tr> <td/> $row <td/> $worksorder
<td/>
<input id= 'worksorder' value='' />
<p id='TR'></p>
<script>
document.getElementById('worksorder').onblur = function() {myFunction()};
function myFunction() {
var worksorder, test;
worksorder = document.getElementById('worksorder').value;
test = (worksorder == '".$_SESSION["worksorder"]."') ? 'PASS':'FAIL';
document.getElementById('TR').innerHTML = test;
}
</script>
</tr>");
}
Screenshot of issue
Any help it getting it to work for all rows is appreciated,
Thanks
The problem is likely because you're using creating <input id='worksorder'> and using document.getElementById('worksorder') to reference it within a loop.
HTML IDs are supposed to be unique on the page, so getElementById will only ever return a single element, that being the first element in the code that has the named ID.
If you want to do this in a loop, then you need to make sure that the ID for each element you generate is unique. Typically this would be done by adding the row ID to the generated HTML ID.
eg <input id= 'worksorder_{$add_info['id']}'> or similar, and then reference that in the JS code.
Alternatively, use an HTML class instead of an ID, since class names do not need to be unique, and then write your JS code to use document.querySelector() to query by classname instead of getElemenetById(). If you do it that way, then the JS code doesn't need to be in the loop, as it will be run once and apply to all the matching elements in one go.
You don't have to use a separate script for every row. You can create a single script to hande all the rows.
Firstly, provide ids to the <tr> tag and the <input> tag to receive the values and add an onblur listener. You can use the autoincrementing column value for the ids that you get from database.
e.g.
echo "<tr> <td/> $row<td/> $worksorder <td/>
<input id='".$id."' value='' /><p id='TR_'".$id."' onblur='myFunction(this)'></p>";
Also, add the onblur listener and pass the object to myFunction().
Then add a single myFunction() at the end.
<script>
function myFunction(param) {
var worksorder, test, id;
worksorder = param.value;
id = param.id;
console.log("order " +worksorder + ' '+id);
test = (worksorder == '".$_SESSION["worksorder"]."') ? 'PASS':'FAIL';
document.getElementById('TR_'+id).innerHTML = test;
};
</script>
This will handle all the rows for you. I hope it helps you.
Working JS fiddle
You are setting all the input fields with the same id. Html cannot handle same ids. You should either use incremental ids or use classes instead of ids, and adapt the JS function to use parameters, so not to have a JS function in every row. One function is enough and you can call it with the row number as parameter

Assign id's (1,2,..etc) to a dynamic html table

Trying to achieve functionality in the linked picture.
The table grows as users click on the add button. I'm trying to replace the text inside the first column of the newly added td with the length of the table. I'm new to jQuery and not sure how to do that. Any help would be great.
It will be easier to answer you with your code but you can know the added row number with $('#yourTBodyID > tr').length
Have a look to this code :
HTML
<table>
<thead>
<tr>
<th>Zone ID</th>
<th>Zone Description</th>
<th><button onclick="addRow();">Add Row</button></th>
</tr>
</thead>
<tbody id="tableBody">
</tbody>
</table>
JS :
function addRow() {
var rowNum = $('#tableBody > tr').length + 1;
$('#tableBody').append('<tr>'
+ '<td>' + rowNum + '</td>'
+ '<td><input type="text"/></td>'
+ '<td><button>Disable</button></td>'
);
}
jsFiddle
Hope it helps ;)
You'll face a few more problems, more than just showing the number on the recently created tr. As you don't write down any code, I'm note sure at which point of the development you are, or which functionalities have you created already. I'll briefly try to explain how to achieve it, just showing you the path.
Add button should find in the DOM a valid <tr>to .clone(). It could be a "seen tr" or a template one. In the first situation you should clean the information in the input.
Once you have saved that cloned node into a variable, you should prepare it for insertion.
Counting the <tr> lines (it'd help if you have a classname on them) and assign the .length() to the first <td> using .text()
Changing name attribute on the input inside the second <td>tag. Otherwise when the form is sent to the server, you will loose every input with the same name which prior the last one.
On the last <td> tag you have a disabled button. It won't have any attached event on your brand new element. You can attach it here, or if you prefer, you can add the event on the very beginning using something like $('table').on('click','.disable-button',function(e){}). Doing that instead of $(.disable-button).click() will assure you that elements with this class created after DOM .ready()fires up will get this event.
I hope it helped you

JS. How to replace html element with another element/text, represented in string?

I have a problem with replacing html elements.
For example, here is a table:
<table>
<tr>
<td id="idTABLE">0</td>
<td>END</td>
</tr>
</table>
(it can be div, span, anything)
And string in JavaScript:
var str = '<td>1</td><td>2</td>';
(It can be anything, 123 text, <span>123 element</span> 456 or <tr><td>123</td> or anything)
How can I replace element idTABLE with str?
So:
<table>
<tr>
<td id="idTABLE">0</td>
<td>END</td>
</tr>
</table>
Becomes:
<table>
<tr>
<td>1</td>
<td>2</td>
<td>END</td>
</tr>
</table>
<!-- str = '<td>1</td><td>2</td>'; -->
<table>
<tr>
123 text
<td>END</td>
</tr>
</table>
<!-- str = '123 text' -->
<table>
<tr>
<td>123</td>
<td>END</td>
</tr>
</table>
<!-- str = '<td>123</td>' -->
I tried createElement, replaceChild, cloneNode, but with no result at all =(
As the Jquery replaceWith() code was too bulky, tricky and complicated, here's my own solution. =)
The best way is to use outerHTML property, but it is not crossbrowsered yet, so I did some trick, weird enough, but simple.
Here is the code
var str = 'item to replace'; //it can be anything
var Obj = document.getElementById('TargetObject'); //any element to be fully replaced
if(Obj.outerHTML) { //if outerHTML is supported
Obj.outerHTML=str; ///it's simple replacement of whole element with contents of str var
}
else { //if outerHTML is not supported, there is a weird but crossbrowsered trick
var tmpObj=document.createElement("div");
tmpObj.innerHTML='<!--THIS DATA SHOULD BE REPLACED-->';
ObjParent=Obj.parentNode; //Okey, element should be parented
ObjParent.replaceChild(tmpObj,Obj); //here we placing our temporary data instead of our target, so we can find it then and replace it into whatever we want to replace to
ObjParent.innerHTML=ObjParent.innerHTML.replace('<div><!--THIS DATA SHOULD BE REPLACED--></div>',str);
}
That's all
Because you are talking about your replacement being anything, and also replacing in the middle of an element's children, it becomes more tricky than just inserting a singular element, or directly removing and appending:
function replaceTargetWith( targetID, html ){
/// find our target
var i, tmp, elm, last, target = document.getElementById(targetID);
/// create a temporary div or tr (to support tds)
tmp = document.createElement(html.indexOf('<td')!=-1?'tr':'div'));
/// fill that div with our html, this generates our children
tmp.innerHTML = html;
/// step through the temporary div's children and insertBefore our target
i = tmp.childNodes.length;
/// the insertBefore method was more complicated than I first thought so I
/// have improved it. Have to be careful when dealing with child lists as
/// they are counted as live lists and so will update as and when you make
/// changes. This is why it is best to work backwards when moving children
/// around, and why I'm assigning the elements I'm working with to `elm`
/// and `last`
last = target;
while(i--){
target.parentNode.insertBefore((elm = tmp.childNodes[i]), last);
last = elm;
}
/// remove the target.
target.parentNode.removeChild(target);
}
example usage:
replaceTargetWith( 'idTABLE', 'I <b>can</b> be <div>anything</div>' );
demo:
http://jsfiddle.net/97H5Y/1/
By using the .innerHTML of our temporary div this will generate the TextNodes and Elements we need to insert without any hard work. But rather than insert the temporary div itself -- this would give us mark up that we don't want -- we can just scan and insert it's children.
...either that or look to using jQuery and it's replaceWith method.
jQuery('#idTABLE').replaceWith('<blink>Why this tag??</blink>');
update 2012/11/15
As a response to EL 2002's comment above:
It not always possible. For example, when createElement('div') and set its innerHTML as <td>123</td>, this div becomes <div>123</div> (js throws away inappropriate td tag)
The above problem obviously negates my solution as well - I have updated my code above accordingly (at least for the td issue). However for certain HTML this will occur no matter what you do. All user agents interpret HTML via their own parsing rules, but nearly all of them will attempt to auto-correct bad HTML. The only way to achieve exactly what you are talking about (in some of your examples) is to take the HTML out of the DOM entirely, and manipulate it as a string. This will be the only way to achieve a markup string with the following (jQuery will not get around this issue either):
<table><tr>123 text<td>END</td></tr></table>
If you then take this string an inject it into the DOM, depending on the browser you will get the following:
123 text<table><tr><td>END</td></tr></table>
<table><tr><td>END</td></tr></table>
The only question that remains is why you would want to achieve broken HTML in the first place? :)
Using jQuery you can do this:
var str = '<td>1</td><td>2</td>';
$('#__TABLE__').replaceWith(str);
http://jsfiddle.net/hZBeW/4/
Or in pure javascript:
var str = '<td>1</td><td>2</td>';
var tdElement = document.getElementById('__TABLE__');
var trElement = tdElement.parentNode;
trElement.removeChild(tdElement);
trElement.innerHTML = str + trElement.innerHTML;
http://jsfiddle.net/hZBeW/1/
You would first remove the table, then add the new replacement to the table's parent object.
Look up removeChild and appendChild
http://javascript.about.com/library/bldom09.htm
https://developer.mozilla.org/en-US/docs/DOM/Node.appendChild
Edit:
jQuery .append allows sting-html without removing tags: http://api.jquery.com/append/
Your input in this case is too ambiguous. Your code will have to know if it should just insert the text as-is or parse out some HTML tags (or otherwise wind up with bad HTML). This is unneeded complexity that you can avoid by adjusting the input you provide.
If the garbled input is unavoidable, then without some sophisticated parsing (preferably in a separate function), you could end up with some bad HTML (like you do in your second example... which is Bad, right?).
I'm guessing you want a function to insert columns into a 1-row table. In this case, your contents should be passed in as an array (without table, tr, td tags). Each array element will be one column.
HTML
<table id="__TABLE__"><tr><td></td></tr></table>
JS
using jQuery for brevity...
function insert_columns (columns)
{
var $row = $('<tr></tr>');
for (var i = 0; i < columns.length; i++)
$row.append('<td>'+columns[i]+'</td>');
$('#__TABLE__').empty(); // remove everything inside
$('#__TABLE__').append($row);
}
So then...
insert_columns(['hello', 'there', 'world']);
Result
<table id="__TABLE__"><tr><td>hello</td><td>there</td><td>world</td></tr></table>
If you need to actually replace the td you are selecting from the DOM, then you need to first go to the parentNode, then replace the contents replace the innerHTML with a new html string representing what you want. The trick is converting the first-table-cell to a string so you can then use it in a string replace method.
I added a fiddle example: http://jsfiddle.net/vzUF4/
<table><tr><td id="first-table-cell">0</td><td>END</td></tr></table>
<script>
var firstTableCell = document.getElementById('first-table-cell');
var tableRow = firstTableCell.parentNode;
// Create a separate node used to convert node into string.
var renderingNode = document.createElement('tr');
renderingNode.appendChild(firstTableCell.cloneNode(true));
// Do a simple string replace on the html
var stringVersionOfFirstTableCell = renderingNode.innerHTML;
tableRow.innerHTML = tableRow.innerHTML.replace(stringVersionOfFirstTableCell,
'<td>0</td><td>1</td>');
</script>
A lot of the complexity here is that you are mixing DOM methods with string methods.
If DOM methods work for your application, it would be much bette to use those.
You can also do this with pure DOM methods (document.createElement, removeChild, appendChild), but it takes more lines of code and your question explicitly said you wanted to use a string.
use the attribute "innerHTML"
somehow select the table:
var a = document.getElementById('table, div, whatever node, id')
a.innerHTML = your_text

dynamic ajax populated table not working in IE 8

Hi all I've an IE8 issure, here's the code:
Html code:
<table id=\"myTable\" border="1">
javascript function:
function loadTableContent() {
$.ajax({
type : 'GET',
url : '/sohara/viewResults.do',
contentType : 'application/json; charset=utf-8',
success : function(response) {
result = response;
var html_Table = '';
html_Table += '<tr><th bgcolor="silver">Type</th>
<th bgcolor="silver">Quantity A</th>
<th bgcolor="silver">Quantity B</th></tr>';
for ( var i = 0; i < result.length; i++) {
html_Table += '<tr>';
html_Table += '<td>'+result[i].description+'</td>';
html_Table += '<td>'+result[i].quantityA+'</td>';
html_Table += '<td>'+result[i].quantityB+'</td>';
html_Table += '</tr>';
}
html_Table += '</table>';
$("#myTable").append(html_Table);
},
error : function(response) {
alert("Error");
}
});
}
Always is perfect in Firefox, Chrome and Opera but in IE8 nothing is displayed in the table. How can I manage that?
I not 100% what is going on but some possibilities:
Your <table> element is incomplete, as it doesn't have an ending tag. I originally assumed that this was just because you were being concise and didn't include it in your example, but then I noticed you included </table> inside your JavaScript. You need to terminate the tag normally in HTML, like so:
<table id=\"myTable\" border="1"></table>
And then work with it in JavaScript (and remove the ending tag in you're appending from JS)
Furthermore, I think the HTML spec says that that table header rows (such as yours with the TH tag) should be wrapped in a <thead> element (e.g. <table><thead><tr><th>Header</th></tr></thead></table>, while body elements are then wrapped in a <tbody> element. Most browsers seem pretty good about parsing tables even without these, but I point it out as a possible source of your problem.
One last potential problem is that IE all the way up to IE9 cannot set innerHTML on tables. See IE9 createElement and setting innerHTML dropping tags on a set operation? and can't innerHTML on tbody in IE for more information. I don't know how JQuery updates table data. I think if they relied on this method we would see more questions here on SO about it, but who knows.
Working solution:
According to Matt first I declare the table this way:
<table id=\"myTable\" border="1"></table> // FULL working method
In fact IE8 automatically closes the html tag this way if I don't close it, causing several issues in appending new html code:
<table id=\"myTable\" border="1"></> // NOT working method
Then instead of using:
$("#myTable).append(html_Table);
I use:
$("#myTable).html(html_Table);
Obviously I have to remove from ajax:
html_Table += '</table>';
Full Working :)

Is there a way to add raw html as new table rows with javascript?

Let's say I have the following table:
<table>
<tr id="1">...</tr>
<tr id="2">..</tr>
...
<tr id="last">..</tr>
</table>
I also have a third-party service from which I get some raw html, also table rows like this:
<tr id="additional-1">...</tr>
<tr id="additional2">...</tr>
Is there a relatively simple javascript way to insert those new rows after the tr with the id "last"?
I'm asking for simple built-in ways to avoid having to do a lot of parsing, replacing and stuff.
I prefer YUI 3 to jQuery solution.
Wrap the string in <table><tbody>..rows here...</tbody></table>. Then, create a dummy element, eg <div>, and set the innerHTML property to the previously constructed string. Finally, loop through all rows, and move the rows to the table using insertBefore(newelem, reference).
This method also works in IE, where setting the innerHTML property on a cell triggers an error.
var raw_html, prev_element, last_element_parent, rows, i;
raw_html = '<tr>....etc....</tr>';
prev_element = document.getElementById('last_element');
last_element_parent = prev_element.parentNode;
dummy = document.createElement('div');
dummy.innerHTML = '<table><tbody>' + raw_html + '</tbody></table>';
rows = dummy.firstChild.rows;
for (i=rows.length-1; i>=0; i--) {
last_element_parent.insertBefore(rows[i], prev_element.nextSibling);
}
What's about table.innerHTML += trs with var table stores the table-Object and var trs stores the additional rows.
Following code should work
$("tr #last").after("ur raw html");
its Jquery though.
In YUI3 you can do:
Y.one('#last').insert(rawHTML, 'after');

Categories