Convert javascript arrays into HTML table using DOM - javascript

I am currently creating a desktop app using tide sdk. All of my database information is stored into Parse.com (a serverless database). What I am trying to do is to take the array of the information I queried from Parse (in javascript) and insert it into a table. I am really having a hard time getting used to not using document.write() for my desktop application.
I want the end result to look like:
This is what I started with:
var contactNameArray = [];
var contactNumberArray= [];
var CM = Parse.Object.extend("ContactMenu");
var queryContact = new Parse.Query(CM);
queryContact.ascending("ContactName");
queryContact.find({
success: function(results4) {
alert("Successfully retrieved " + results4.length + " entries.");
// Do something with the returned Parse.Object values
// document.write('<table border="1" cellspacing="1" cellpadding="5">');
for (var i = 0; i < results4.length; i++) {
var object4 = results4[i];
contactNameArray[i] = object4.get('ContactName');
contactNumberArray[i] = object4.get('ContactNumber');
// document.write("<tr><td>Number " + i + " is:</td>");
//document.write("<td>" + contactNameArray[i] + "</td></tr>");
}
//document.write('</table>');
},
error: function(error) {
alert("Error: " + error.code + " " + error.message);
}
});
After doing some research I cam upon this bit of code from http://www.w3schools.com/jsref/dom_obj_table.asp which wrote: the correct response on the bottom of the left handed corner of the screen. (Kind of strange in my opinion). In code how can I better position this table to be in the center for my screen? Is there a way to center this table in javascript?
function generate_table() {
var x = document.createElement("TABLE");
x.setAttribute("id", "myTable");
document.body.appendChild(x);
var y = document.createElement("TR");
y.setAttribute("id", "myTr");
document.getElementById("myTable").appendChild(y);
var z = document.createElement("TD");
for(var i = 0; i< query4.length; i++){
var t = document.createTextNode(contactNameArray[i]);
z.appendChild(t);
var m = document.createTextNode(contactNumberArray[i]);
z.appendChild(m);
}
document.getElementById("myTr").appendChild(z);
}
So I have already figured out how to put the information I want into an array. I am just having a hard time putting this information into a table that is correctly positioned. Thank you in advance. If you need to see any more of my code, then just let me know. If I am unclear, please let me know what I should explain. Thank you!!!

There are several ways to do this. But from what you already have the simplest is to use innerHTML:
queryContact.find({
success: function(results4) {
var html = "";
alert("Successfully retrieved " + results4.length + " entries.");
// Do something with the returned Parse.Object values
html += '<table border="1" cellspacing="1" cellpadding="5">';
for (var i = 0; i < results4.length; i++) {
var object4 = results4[i];
contactNameArray[i] = object4.get('ContactName');
contactNumberArray[i] = object4.get('ContactNumber');
html += "<tr><td>Number " + i + " is:</td>";
html += "<td>" + contactNameArray[i] + "</td></tr>";
}
html += '</table>';
document.body.innerHTML += html;
},
error: function(error) {
alert("Error: " + error.code + " " + error.message);
}
});
As for centering the table on the page the best way is to use CSS. Unfortunately centering anything in CSS is a bit of a hack. There are several ways to do it. See the answers to this question for all the ways of doing it: How to horizontally center a <div> in another <div>?. Note: scroll through the answers, not just read the top one. There really are a lot of ways to do this and some may not work for you.
A few notes about innerHTML:
Although innerHTML looks like a variable it actually behaves more like a function. Specifically it invokes the HTML compiler of the browser. So if you pass it incomplete tags like:
someDiv.innerHTML += '<table>';
it will see that as an incomplete 'table' tag and deals with it the way the browser usually does when it sees an incomplete 'table' tag. For some browsers that means removing the table from the DOM. For others that means immediately inserting a closing </table> tag to make it valid. What this means is that when you later append the closing tag like this:
someDiv.innerHTML += '</table>';
what happens is that the browser will think you did this:
<table></table></table>
^ ^
| |_________ what you're trying to insert
|
auto inserted by the browser earlier
and deal with it the way browsers usually do - consider that tag invalid and discard it.
So you need to pass innerHTML well-formed html which is why I created the table structure in a string then append it to the DOM with innerHTML.
A lot of people consider innerHTML stylistically bad since you're doing DOM manipulation with strings. Also because innerHTML was not originally part of any standard and was a proprietary feature of IE. Since it's not part of any standard there's no real agreement between different browsers for how it should work. Having said that, it's probably the most cross-bowser compatible method of manipulating DOM because it's the most widely implemented (even on really old browsers).
Read the documentation of the DOM API for more info on how to do it "properly": https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model
As mentioned by others in the comment to your question, there are also libraries out there that would make your life easier for DOM manipulation. There are procedural libraries like jQuery that wraps the often clunky DOM API and cross-browser issues into a nice to use API. There are also templating library like handlebars that allows you to write the HTML fragments as templates to be processed and inserted into the DOM.
I suggest getting comfortable with how the DOM works by reading the DOM documentation and follow some tutorial and also look at DOM manipulation libraries like jQuery or YUI or underscore to get a better feel of javascript.
Paraphrasing Douglas Crockford, you wouldn't start to program in C or Java without learning the language first. Similarly javascript is a full-featured language. Take some time to learn it and not just assume that "it works like [insert a language you know]". There are many features of javascript like closures and asynchronous functions that will trip you up. Similarly the DOM has many behaviors that may work differently from your assumptions of how HTML works.
But it is a lot to take in. So for now use innerHTML with care being aware of its limitations. Also look at document.getElementById() which is the second most used DOM API for beginners. It allows you to insert your html anywhere in the document using innerHTML. Not just the end of the document.

From reading your questions I deduced the following...
How can I center my table using javascript.
I am having an issue getting my information into a table.
Typically it is not a good idea to do styling within your javascript. While it may seem nice to handle such things conveniently within your jscript, it can end up blowing up your code if not used with moderation. Your best bet would be to write some css, perhaps a generic class that can center an element to a page, and then apply this class to the table element. No Javascript needed, and it makes your code more modular to boot!
Here is a hacky bit of centering code that has worked for me to center a registration form div (Height and Width can be adjusted however you like, use of pixels is not a must.):
body > #register {
margin: auto;
position: absolute;
text-align: center;
height: 156px;
width: 160px;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
For the issues you are experiencing adding information to your table, without knowing what errors or exact output your are seeing, all I can do is go off what I can see in the code. Your generate table function has a few areas I noticed....
The function creates a table, sets an id to it, and appends it to the document, it then creates a new row, setting an id to it as well, it then appends the new row to the table. Then a cell is created.
Here is where I see a problem...
You then jump into a for loop limited by the length of query4 (I'll assume this is the query containing your contact info) and create text nodes, appending them to z (the cell) each iteration, if I am not mistaken that would actually result in the cell in the first(and only) row getting blown up with all your query info. What should be happening is the for loop adds the name and number to its own cells in a NEW row each iteration. This would be your psuedocode...
Create table
Start for loop over contact info for each item...
Create new row
Create new cells
add info to respective cells
append cells to row
append row to table
rinse and repeat
Based on what you have, here is a rough untested representation of what I am suggesting, I built it out of your own code, but it could be done in several ways really...
function generate_table() {
// Create our table
var table = document.createElement("TABLE");
document.body.appendChild(table);
for(var i = 0; i < query4.length; i++) {
// Set up an awesome new row.
var row = document.createElement("TR");
// Set up awesome new cells.
var nameCell = document.createElement("TD");
var numberCell = document.createElement("TD");
// Instantiate variables to hold our data.
var name = document.createTextNode(contactNameArray[i]);
var number = document.createTextNode(contactNumberArray[i]);
// Add values to cells
nameCell.appendChild(name);
numberCell.appendChild(number);
// Add cells to row.
row.appendChild(name);
row.appendChild(number);
// Build out awesome row.
table.appendChild(row);
}
}
I did a couple things here, first off some variable renaming, descriptive variable names do wonders for code readability and maintenance later on. (Check out the book "Clean Code", it talks on this at length, it changed the way I look at code virtually overnight.)
One other thing, I assume the query4 variable is being set up in the global scope, that will work, but it's typically good to try an keep the global space clear if and when you can. (See Douglas Crockfords "Javascript: The Good Parts", another great book on Javascript that really helped me learn the language.) Maybe consider passing the data to the generate table function, and calling the function in the callback of the parse data return?
Anyway, that is my "brief" two cents, hope it helps!
Good luck.

Related

One function for many buttons

I have dynamically created elements on the page, a picture and three buttons which are created upon clicking the main button.
All of this works, but now I am trying to change the display on the dynamically created div with the pics to "none".
More than one issue arises here for me, first I cannot find out how to make the div "images" the target, or select it.
I am trying to get one function to do this for all the elements, they are all structured equally just the pictures are different.
function hidePic(arrayPos){
var elem = document.getElementsByClassName("closingButton") + "[" + arrayPos + "]",
finalTarget = elem.getElementsByClassName("images")[0];
finalTarget.style.display = "none";
}
document.getElementsByClassName("closingButton")[0].addEventListener("click", function(){
hidePic(0);
});
This is the relevant code, lines 4 to 10. If this is commented out, the rest of the code works, but as it is I get entirely unrelated errors in dev Tools.
Click this link to see Codepen.
So the question is, how can I best implement the above code?
So just working on the code above you can do this in order to make it work for all instances. First let me point out that this:
var elem = document.getElementsByClassName("closingButton") + "[" + arrayPos + "]";
will never work. That line is building a string. What you really want to make that line work is:
var elem = document.getElementsByClassName("closingButton")[arrayPos];
But even that I find unnecessary. Take a look at this code.
function hidePic (elem) {
var finalTarget = elem.getElementsByClassName("images")[0];
finalTarget.style.display = "none";
}
var closingButtons = document.getElementsByClassName("closingButton");
var index = 0, length = closingButtons.length;
for ( ; index < length; index++) {
closingButtons[index].addEventListener("click",
function () {
hidePic(this);
}
);
}
This first finds all elements with the class closingButton. Then for each one we attach a click event listener. Instead of attempting to pass some index to this hidePic function we already have our function context which is what you seem to be trying to find in the function so lets just pass that and use it to find the image inside.
Let me know if you have any questions. I took a look at your codepen as well. I am not sure you should be forcing all that interactive HTML into a button element honestly, which itself is considered an interactive element. Not sure that meets the HTML spec. Perhaps add that HTML below the button. I bet when you click on things inside of that button it will register as clicks on the button as well unless you remove the event upon inserting your elements but then it seems like its getting too complicated for the simple stuff you are trying to do here.
The codepen complains because there is no element with the "closingButton" class, so it's trying to call addEventListener on nothing, but I'm doubting that's the actual error you're seeing.
It's also worth nothing that I think this:
var elem = document.getElementsByClassName("closingButton") + "[" + arrayPos + "]",
is excessive.
var elem = document.getElementsByClassName("closingButton")[arrayPos];
should be sufficient. Also not the syntax error at the end of the same line: it should be ; not ,. If this is the error in your code it could explain why you were getting "unrelated errors" syntax errors can cause misleading problems that are supposedly in other areas of the code!
Lastly, I'd highly recommend using JQuery to do your selection magic - it's exactly what it was designed for. If you're averse to using JS libraries, fair enough, but it would make your code a lot simpler and you can have reasonable confidence that it will perform the tasks about as optimally as is possible.

Script performace - script crashing the browser

I have a script that call a CGI to retrieve an XML, parses it, creates a table and show this table to the user. It is pretty simple..
My problem is when this XML is too big because it sometimes crashes the user's browser.
So I want your opinion about what I can improve in my function.
The function does that:
[1]. It parses the XML:
var xmlDoc = req.responseXML;
var rows = xmlDoc.getElementsByTagName('row');
var columns = rows[0].getElementsByTagName('column');
And after this I iterate over the columns to create my table.
[2]. I'm creating the table concatenating strings, like this (iterating over each column in each row):
tableCells += '<td style="text-align:left">' + value + '</td>';
[3]. And to finish, I do this:
document.getElementById('results').innerHTML = resultsTable;
document.getElementById('results').style.display = "";
I checked how time takes these steps (profiling with chrome and firefox+firebug):
I removed all the concatenation and the [3]. The funcion took 0.5s.
I removed only the [3]. The function took 1.5s.
But if I add the [3] my function takes 15.5s (!!!).
What can I do to improve it?
Thank you!
Since innerHTML needs to be parsed by the browser you could try to speed things up by inserting DOM objects, so the there is no need of parsing a large string.
Instead of adding strings, you could use something like this in your table:
var resultTable = document.getElementById('...'), newRow, newCell;
// ... begin loop:
newRow = document.createElement('tr');
newCell = document.createElement('td');
newCell.textContent = "abc"; // use innerText in IE
newRow.appendChild(newCell);
resultTable.appendChild(newRow);
if you want to stick to strings, or if this does not give you a lot of improvement, you should optimize your HTML, for instance the style in here: <td style="text-align:left"> can be eliminated by using CSS and should save you a lot of bytes which do not need to be parsed anymore. Add a CSS like this to acheive this: td {text-align: left;}
Not sure if may help but:
Create an element using javascript (Ex.:var el = document.createElement('span'))
Set the element innerHTML (Ex.: el.innerHTML = resultsTable)
appends it to the page

Most efficient way to create form with Jquery based on server data

I have a database webmethod that I call via Jquery ajax. This returns an array of data objects from the server. For each data object I want to populate a form with maybe a couple dozen fields.
What is the most efficient way to generate and populate these forms.
Right now, I create a string of the html in my javascript code for each record and then add it to the page. Then I add some events to the new elements.
This works OK in firefox, about 700 ms total for an array of 6 elements, and 4500 ms for an array of 30 elements. However, this is an internal app for my company, and the clients can only use IE8 on their machines. In IE8, this takes 2-10 SECONDS! for and array of length 6 and 47 seconds the one time it was actually able to complete for an array of length 30. Not sure what the ##$% IE8 is doing, but anyways... I need something more efficient it seems...
Thanks!
MORE INFO:
Edit: first thing I do is:
$("#approvalContainer").empty();
Web method signature:
[WebMethod]
public List<User> ContractorApprovals()
So I call the method and then do this with the data:
for (var i = 0; i < data.length; i++) {
displayUserResult("#approvalContainer", data[i], false);
}
formEvents("#approvalContainer");
$("#approvalContainer").show("slow");
displayUserResult looks like this:
var div = "<div style=\"width:695px\" class=..........."
fillForm(div,data)
$("#approvalContainer").append(div)
formEvents does things like add click events, create styles buttons and add watermarks.
fillForm does stuff like this:
$(userForm).find(".form-web-userName").text(userObject._web._userName);
where userForm is the div created in the displayUserResult function, and userObject is one of the objects in the array returned by the ajax call.
Please let me know if you need more information.
You are doing too much DOM manipulation. Every .append and .find and .text inside your loop makes it slower.
The most efficient way is to build an entire HTML string and append that once. I'm not going to bother with the displayUserResult function, and just modify your function that handles the data:
var div = "<div style=\"width:695px\" class=...........",
html = "";
for (var i = 0; i < data.length; i++) {
// Keep adding on to the same html string
html += div + data[i]._web._userName + "</div>";
}
// After the loop, replace the entire HTML contents of the container in one go:
$("#approvalContainer").html(html);
However, while this is fast, note that this is only appropriate if _userName doesn't contain special characters, or is already HTML escaped.

How to access CSS generated content with JavaScript

I generate the numbering of my headers and figures with CSS's counter and content properties:
img.figure:after {
counter-increment: figure;
content: "Fig. " counter(section) "." counter(figure);
}
This (appropriate browser assumed) gives a nice labelling "Fig. 1.1", "Fig. 1.2" and so on following any image.
Question: How can I access this from Javascript? The question is twofold in that I'd like to access either the current value of a certain counter (at a certain DOM node) or the value of the CSS generated content (at a certain DOM node) or, obviously, both information.
Background: I'd like to append to links back-referencing to figures the appropriate number, like this:
<a href="#fig1">see here</h>
------------------------^ " (Fig 1.1)" inserted via JS
As far as I can see, it boils down to this problem:
I could access content or counter-increment via getComputedStyle:
var fig_content = window.getComputedStyle(
document.getElementById('fig-a'),
':after').content;
However, this is not the live value, but the one declared in the stylesheet. I cannot find any interface to access the real live value. In the case of the counter, there isn't even a real CSS property to query.
Edit: Digging deeper and deeper through the DOM specs, I found the DOM Level 2 Style Counter interface. This seems to a) allow access to the current counter value and b) be implemented in Firefox, at least. However, I have no idea on how to use it. My current approach died tragically after this Firebug output:
// should return a DOM 2 Counter interface implementation...
window.getComputedStyle(fig_a_element, ':after')
.getPropertyCSSValue("counter-increment")[0]
.getCounterValue();
[Exception... "Modifications are not allowed for this document" code: "7"
nsresult: "0x80530007 (NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR)"
location: "http://localhost/countertest.html Line: 71"]
Any idea, how this could be brought to life would be highly appreciated.
Edit 2: Apparently I misinterpreted the Counter object of DOM Level 2 Style. It, too, has no property to return the current counter value. This makes the above approach invalid.
New approach: Is there a possibility to read the content of a pseudo-element via the DOM? That is, can I select the pseudo-element (treeWalker comes to mind) and then get its nodeValue? (If you start to type 'jQuery' now, please reconsider to change that term into 'Sizzle'...)
I cannot find any interface to access the real live value. [of the counter]
Yeah. I don't think there is one. Sorry.
The only thing I can think of would be to go through every element (including its :before/:after pseudo-elements) before the element in the document, looking for counters and adding up how many there are.
Obviously that's hideous. If you're going to try to reproduce the browser's own counter mechanism it would probably be easier (and much more compatible, given IE<=7's lack of counter/content support) to just replace it with your own script-based counters. eg. something along the lines of:
this
<div class="counter level=0">...</div>
<img id="prettypicture" class="counter level=1" alt="ooo, pretty"/>
window.onload= function() {
var counters= Node_getElementsByClassName(document.body, 'counter');
var indices= [];
for (var counteri= 0; counteri<counters.length; counteri++) {
var counter= counters[counteri];
var level= Element_getClassArgument(counter, 'level');
while (indices.length<=level)
indices.push(0);
indices[level]++;
indices= indices.slice(level+1);
var text= document.createTextNode('Figure '+indices.join('.'));
counter.parentNode.insertBefore(text, counter.nextSibling);
if (counter.id!=='') {
for (var linki= document.links.length; linki-->0;) {
var link= document.links[i];
if (
link.hostname===location.hostname && link.pathname===location.pathname &&
link.search===location.search && link.hash==='#'+counter.id
) {
var text= document.createTextNode('('+indices.join('.')+')');
link.parentNode.insertBefore(text, link.nextSibling);
}
}
}
}
};
read this:
http://www.w3.org/TR/CSS2/generate.html#propdef-content
Generated content does not alter the
document tree. In particular, it is
not fed back to the document language
processor (e.g., for reparsing).
https://developer.mozilla.org/en/CSS/Getting_Started/Content
Content specified in a stylesheet does not become part of the DOM.
so for this reason the getComputedStyle will not work in this case; i think the only way, is to perform a classic loop through as someone has described below!
I would port your css to Javascript, this enables you to get the figure caption and also you get greater browser coverage. Using jQuery you'd do something like this:
$(function() {
var section = 0;
$(".section").each(function() {
section++;
var figure = 0;
$(this).find("img.figure").each(function() {
figure++;
var s = "Fig. " + section + "." + figure;
$(this).attr({alt:s}).after(s);
});
});
});
Then you could do:
<div class="section">blabla <img id="foo" src="http://www.example.com/foo.jpg"></div>
<p>Lorem ipsum dolor sit amet see here</p>
<script type="text/javascript">
$(function() {
$("a.figurereference").each(function() {
var selector = $(this).attr("href");
var $img = $(selector);
var s = $(this).text() + " (" + $img.attr("alt") + ")";
$(this).text(s);
});
});
</script>
Though I agree that doing this using CSS would be very neat.
I cannot find any interface to access the real live value.
If you can't get the value from window.getComputedStyle, it seems that it would be impossible.
More importantly, while it can do it, I think this might be abusing CSS since you're breaking the barrier between content and presentation at this point.
Check out this related question What are good uses of css “Content” property?

DOM memory issue with IE8 (inserting lots of JSON data)

I am developing a small web-utility that displays some data from some database tables.
I have the utility running fine on FF, Safari, Chrome..., but the memory management on IE8 is horrendous. The largest JSON request will return information to create around 5,000 or so rows in a table within the browser (3 columns in the table).
I'm using jQuery to get the data (via getJSON). To remove the old/existing table, I'm just doing a $('#my_table_tbody').empty(). To add the new info to the table, within the getJSON callback, I am just appending each table row that I am creating to a variable, and then once I have them all, I am using $('#my_table_tbody').append(myVar) to add it to the existing tbody. I don't add the table rows as they are created because that seems to be a lot slower than just adding them all at once.
Does anyone have any recommendation on what someone should do who is trying to add thousands of rows of data to the DOM? I would like to stay away from pagination, but I'm wondering if I don't have a choice.
Update 1
So here is the code I was trying after the innerHTML suggestion:
/* Assuming a div called 'main_area' holds the table */
document.getElementById('main_area').innerHTML = '';
$.getJSON("my_server", {my: JSON, args: are, in: here}, function(j) {
var mylength = j.length;
var k =0;
var tmpText = '';
tmpText += /* Add the table, thead stuff, and tbody tags here */;
for (k = mylength - 1; k >= 0; k--)
{
/* stack overflow wont let me type greater than & less than signs here, so just assume that they are there. */
tmpText += 'tr class="' + j[k].row_class . '" td class="col1_class" ' + j[k].col1 + ' /td td class="col2_class" ' + j[k].col2 + ' /td td class="col3_class" ' + j[k].col3 + ' /td /tr';
}
document.getElementById('main_area').innerHTML = tmpText;
}
That is the gist of it. I've also tried using just a $.get request, and having the server send the formatted HTML, and just setting that in the innerHTML (i.e. document.getElementById('main_area').innerHTML = j;).
Thanks for all of the replies. I'm floored with the fact that you all are willing to help.
var tmpText = [];
for (k = mylength - 1; k >= 0; k--)
{
/* stack overflow wont let me type greater than & less than signs here, so just assume that they are there. */
tmpText.push('anything you want')
tmpText.push( 'tr class="' + j[k].row_class . '" td class="col1_class" ' + j[k].col1 + ' /td td class="col2_class" ' + j[k].col2 + ' /td td class="col3_class" ' + j[k].col3 + ' /td /tr';)
}
$('#main_area').html(tmpText.join(''))
}
you dont need document.getElementById('main_area').innerHTML = '';
this method is to push into array, then join and use jquery html function to update. This is the fastest method I know. Sorry for the format here - its my first post and I thought I'd give something back here to stackoverflow.
To get IE to respond quickly you should be creating your table rows as string representations of HTML , appending them to a string variable, and then adding the result to your table's like this.
myTable.myTbody.innerHTML = allThoseRowsAsAString;
It's not a memory issue: 5,000 rows should be trivial. That's got to be far less than one megabyte.
Robusto is right about innerHTML assignment being a better way to go. IE sucks at dynamic DOM creation
Why not form your innerHTML on the server using a jsp and stream it back via ajax in one shot. It will definitely speed things up, remove complexity from your javascript and delegate markup creation to its proper place.
As Plodder said, IE has big problems when working with DOM. jQuery best practices recommends creating code on a simple string and appending just once inside the container.
Beside this, I recently had a similar problem for a hierarchycal data, having an amount of 5,000 data records. I asked myself: did the user really need all that information available at a given moment? Then I realized the best I could do was just present a "first chunk of data" and then insert more data on user demand.
Finally, just one good tool: Dynatrace Ajax (it helps a lot to find the javascript function it takes more time to operate)
Since you are dealing with thousands of data rows I wouldn't call $('#my_table_tbody').empty() and add the new data with new DOM elements. Instead I'd follow the Object Pool Pattern. Thus instead of dropping all the tr's you can reuse existing ones and just populate with the new data.
If your new data set has less rows then the previous one, remove the rest of the rows from the DOM, but keep references to them in some pool so that garbage collector won't destroy them. If your new data set is bigger - just create new tr's on demand.
You can look at the implementation of YUI DataTable, here's the source. IIRC they use this approach to speed up the render time.

Categories

Resources