DataTables Initialized/Executed too Early on Dynamic HTML Data - javascript

I have a function that creates an HTML table based on an array. Once the HTML table is finished being created, I then want to call jQuery DataTables to render the table.
However, this doesn't work. I get the "No data Available in Table" line whenever I do this, and my table data appears. But, as soon as I click a filter for it, all the <td> HTML data disappears. I've checked my table's HTML, it's correctly defined, has all the necessary tags, and matches with what my function creates.
From what I've read, the issue lies with the DataTables function firing too early. I confirmed this by using the setTimeout function- it creates the table perfectly and with no errors. However, this solution isn't scalable and would cause the same problem once the data expands.
I've tried fixing this by using a callback (DataTables still fires too early, I used renderTable(function(){$('#divtable').DataTable();}), and a promise (it still fires too early, I tried to use function.done()...). How can I call the render function as only when the HTML table div is finished updating?
Here is my (shortened) code:
HTML Table render:
function renderTable() {
for (var i = 0; i < array.length; i++) {
var tableRow = '';
tableRow += '<tr><td class="ID">' + array[i].id + '</td><td>' + array[i].date + '</td><td>' + array[i].total + '</td><td><input type=button class="editValues" value="Edit Values" /></td></tr>';
$("#divtable > tbody").append(tableRow);
}
jQuery:
$(document).ready(function() {
renderTable();
$('#divtable').DataTable();
}

Related

How to us $.data in a loop to display correctly for each div

When I click a marker on my map, I want to display data from the database associated with that marker. Currently I am doing this for the divs that I create. I am having a problem when I click on a div to open my form under it, I want to load the same data into the form, but currently the only data that will load into each for is the first one that ran through the loop.
Another words, my venue-list div might hold 3 venues for this one location, and I can click on each div and display a form under it, but if "Sally's Salon" is the first div in the list, every form will display "Sally's Salon" instead of their own name. Here is my code:
// initially detach form to be attached to each div on click
var editForm = $('.edit-container').detach();
function fillForm(title, priority) {
$('.venue-input').val(title);
$('.priority-input').val(priority);
}
for(var i = array.length -1; i>=0; i--) {
var prop = array[i].feature.prop
if(prop.title) {
// create variable to hold html
var html = $([
"<div class='venue-item'>",
" <span><strong>Venue Name</strong></span>",
" <span class='venue-name venue-title' data-title=" + prop.title + ">" + prop.title + "</span>",
"<span class='venue-priority' data-priority=" + prop.prioritylevel + "><strong>Priority</strong>" + ' ' + prop.prioritylevel + "</span></div>"].join("\n"));
// append venue item to venue list div with event handler to add form and prefill when clicked
$(html).appendTo('.venue-list').on('click', function() {
$(this).after(editForm);
fillForm($('.venue-title').data('title'), $('.venue-priority').data('priority'));
});
}
any input would be helpful, I've been battling this for a couple days now, not sure if I am even attaching the html elements correctly but I mainly just want to be able to click on one of the html elements I create and then the data gets loaded into the form so it can be manipulated.
You are appending multiple copies of your content, however there are 2 elements using the same id (venue-priority & venue-title) - which should be unique on a web page.
Thereafter, you use jQuery to get those elements, however as jQuery expects id's to be unique, it will just grab the first (or last, im not sure) element with that Id.
The solution is not to use duplicated id's in your markup.
You seem to have a syntax error on line 6. You can view errors in a browser using developer tools in case there are others.
$('#priority-input.val('priority);
should be
$('#priority-input').val('priority');
Actually, if you want to set the value to the value of the parameter, you should use
$('#priority-input').val(priority);
not
$('#priority-input').val('priority');

Find next input element in DIV using JQuery

The API method I am using takes a list of parameters in the form of Dictionary<string,string>. I am building the inputs for this dynamically with JavaScript, where I have a DIV row and then two inner DIV cells, one for the parameter name and one for the parameter value input.
I can use .each() to loop through each of the parameter name DIVs but need to figure out how to get the value of the input which is displayed directly after it.
With the following pseudo code to show the idea, what would be the best approach where I could get the parameter values together?
CSS Table:
consumedValues.innerHTML += "<div class='valuesRow'>";
consumedValues.innerHTML += "<div class='valuesCellName'>" + consumesItem + "</div><div class='valuesCellValue'><input type='text'></div>";
consumedValues.innerHTML += "</div>";
Parameter Dictionary:
var parameters = {};
$('.valuesCellName').each(function(index)
{
parameters[$(this).html()] = "INPUT_VALUE_HERE";
});
Since they are siblings, Use next()
$(this).next().find("input").val()

Why doesn't this Javascript code execute sequentially?

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.

Javascript Stopwatch Performance

i am trying to display multiple Running Times in a table.
The table has 3 columns (nr, name, time). The time is displayed in that format 'hh:mm:ss.f', that means that i update the time every 100ms.
function updateTimes() {
setTimeout(updateTimes, 100);
// requestAnimationFrame(updateTimes);
$("#livedata tbody tr").each(function (index, value) {
var live = $(value).data("base");
$("#" + live.Entry.Id + "_time").text(formatTimeF(moment().subtract(live.Data)));
});
}
//Create Table Row
function UpdateLive(live) {
var e = $("#" + live.Entry.Id);
if (e.length == 0) {
e = $("<tr id='" + live.Entry.Id + "' class='live'/>");
$("<td id='" + live.Entry.Id + "_name" + "'></td>").appendTo(e);
$("<td id='" + live.Entry.Id + "_nr" + "'></td>").appendTo(e);
$("<td id='" + live.Entry.Id + "_time" + "'></td>").appendTo(e);
e.appendTo($("#livedata"));
}
e.data("base", live);
}
The code works and the time is displayed as expected on "normal" PCs, my problem are mobile Devices (Cell Phones). It seems that the update Intervall (100ms) is too fast for most of these devices, so the time starts to "jump". If I only update the times of existing table elements the "jumping" is not that hard, but if I add table rows its getting worst until the row is added.
I use JQuery for manipulating the table.
Does anyone have an idea how i can improve my Performance?
Kind Regards
Manu
Some ideas:
It would certainly help to use direct references to your elements instead of accessing them by $ selectors all the time. E.g. use $(live.childNodes[2]) instead of $("#" + live.Entry.Id + "_time"), or store references to the individual cells. Then you can drop all the id attributes of your cells.
Assuming that the bottleneck is element creation, you could create hidden table rows in advance and show them once you use them, hiding them again after use. That would require a major rewrite of these functions, though.
I'm not sure how much overhead $('<td>') adds. Maybe document.createElement('td') is significantly faster.
(and this should actually come in the beginning of every performance-related change) Profile your code, e.g. using the Profile tab in Chrome's developer tools. It will show where exactly how much time is spent so that you know what the most expensive function calls are. I would assume that even if it's faster on the desktop, what's slowest on mobile will still be slowest on desktop.

TableDnD onDrop event not firing

I'm sure this is something very simple, it usually is.
$('#sort-table').tableDnD({
onDragClass: "dnd_drag",
onDragStart: function(table, row) {
console.log("start drag");
},
onDrop: function(table, row) {
console.log($.tableDnD.serialize());
},
dragHandle: ".dragHandle"
});
I have the above code in action for tableDnD, the jQuery table sorting plugin. This is the exact code from the samples they provide, but it doesn't fire the onDrop event correctly when I drop an item in the table. I get no response in the console. The table does initialize, and the drag handle works properly, so I at least know that portion of the code is correct. The only thing I can't get to work is the onDrop command.
Update:
I updated the code above to add an onDragStart and onDragClass, both of which work perfect, it is only the onDrop function failing.
This is my general table layout:
<table id="sort-table">
<tbody class="sort-items">
<tr class="1">
<td class="dragHandle"></td>
...
</tr>
...
</tbody>
</table>
You must define tr[id] attribute to make onDrop work.
This is because onDrop only fire when row order changed.
However, without specifying tr[id] attribute, tableDnD.serialize() will think there was not any re-order. (Bug for sure)
Well my first question and I got the answer to it. Hope this helps someone in the future.
The issue was with the actual ID's of my table rows. I actually had use of uuid which meant that my rows actually had an ID similar to "26b5122e-bbb8-11e1-9c53-d4856404b576". Apparently TableDnD does some sort of serializing of the data that broke my ID's apart and only grabbed the last group of numbers, which for most items were the same.
The line from the jquery.tablednd.js file that was causing the issue was this (around line 380):
...
var rowId = rows[i].id;
if (rowId && table.tableDnDConfig && table.tableDnDConfig.serializeRegexp) {
rowId = rowId.match(table.tableDnDConfig.serializeRegexp)[0];
}
result += tableId + '[]=' + rowId;
...
I simply removed the serializer since I knew I wouldn't need it for my row IDs. Then I passed the row ID along myself. This was the result.
...
var rowId = rows[i].id;
result += tableId + '[]=' + rows[i].id;
...
So if you use dashes in your row IDs, make sure to change this to make the onDrop fire correctly.
Quick fix.
If you want onDrop to work without having row.id, you can edit plugin.
Replace this (line 255 is where function starts - currentOrder)
var rows = this.currentTable.rows;
return $.map(rows, function (val) {
return ($(val).data('level') + val.id).replace(/\s/g, '');
}).join('');
With this
return $(this.dragObject).index();

Categories