How to efficiently build a document fragment [duplicate] - javascript

I'm currently using AJAX with Django Framework.
I can pass asynchronous POST/GET to Django, and let it return a json object.
Then according to the result passed from Django, I will loop through the data, and update a table on the webpage.
The HTML for the table:
<!-- Modal for Variable Search-->
<div class="modal fade" id="variableSearch" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button>
<h4 class="modal-title" id="myModalLabel">Variable Name Search</h4>
</div>
<div class="modal-body">
<table id="variableSearchTable" class="display" cellspacing="0" width="100%">
<thead>
<tr>
<th> Variable Name </th>
</tr>
</thead>
</table>
<p>
<div class="progress">
<div class="progress-bar progress-bar-striped active" id="variableSearchProgressBar" role="progressbar" aria-valuenow="45" aria-valuemin="0" aria-valuemax="100" style="width: 45%">
<span class="sr-only">0% Complete</span>
</div>
</div>
</p>
<p>
<div class="row">
<div class="col-lg-10">
<button class="btn btn-default" type="button" id="addSearchVariable" >Add</button>
</div>
</div>
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" id="variableSearchDataCloseButton" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
Basically it is a bootstrap 3 modal, with jQuery DataTable, and with a progress bar to show the user the current progress.
The Javascript that is used to get Django results:
$('#chartSearchVariable').click(function(event)
{
$('#chartConfigModal').modal("hide");
$('#variableSearch').modal("show");
var csrftoken = getCookie('csrftoken');
var blockname = document.getElementById('chartConfigModalBlockname').value;
$('#variableSearchProgressBar').css('width', "0%").attr('aria-valuenow', "0%");
event.preventDefault();
$.ajax(
{
type:"GET",
url:"ajax_retreiveVariableNames/",
timeout: 4000000,
data:
{
'csrfmiddlewaretoken':csrftoken,
'blockname':blockname
},
success: function(response)
{
if(response.status == "invalid")
{
$('#chartConfigModal').modal("hide");
$('#variableSearch').modal("hide");
$('#invalid').modal("show");
}
else
{
configurationVariableChart.row('').remove().draw(false);
for (i = 0 ; i < response.variables.length; i++)
{
configurationVariableChart.row.add(
$(
'<tr>' +
'<td>' + response.variables[i] + '</td>' +
'<tr>'
)[0]);
}
configurationVariableChart.draw();
$('#variableSearchProgressBar').css('width', "100%").attr('aria-valuenow', "100%");
}
},
failure: function(response)
{
$('#chartConfigModal').modal("hide");
$('#variableSearch').modal("hide");
$('#invalid').modal("show");
}
});
return false;
});
$('#addSearchVariable').click(function(event)
{
$('#variableSearch').modal("hide");
$('#chartConfigModal').modal("show");
document.getElementById('chartConfigModalVariable').value = currentVariableNameSelects;
});
$('#variableSearchDataCloseButton').click(function(event)
{
$('#variableSearch').modal("hide");
$('#chartConfigModal').modal("show");
});
The problem is with the updating table part:
configurationVariableChart.row('').remove().draw(false);
for (i = 0 ; i < response.variables.length; i++)
{
configurationVariableChart.row.add(
$(
'<tr>' +
'<td>' + response.variables[i] + '</td>' +
'<tr>'
)[0]);
}
configurationVariableChart.draw();
$('#variableSearchProgressBar').css('width', "100%").attr('aria-valuenow', "100%");
Since the response.variables can be over 10k, and it will freeze the web browser, even though it is still drawing.
I'm pretty new to Web Design (less than 4 months), but I assume it's because they are all running on the same thread.
Is there a way in Javascript to do threading/async? I had a search, and the results were deferred/promise which seems very abstract at the moment.

Try processing retrieved data incrementally.
At piece below , elements generated in blocks of 250 , primarily utilizing jQuery deferred.notify() and deferred.progress().
When all 10,000 items processed , the deferred object is resolved with the collection of 10,000 elements. The elements are then added to document at single call to .html() within deferred.then()'s .done() callback ; .fail() callback cast as null .
Alternatively , could append elements to the document in blocks of 250 , within deferred.progress callback ; instead of at the single call within deferred.done , which occurs upon completion of the entire task.
setTimeout is utilized to prevent "freeze the web browser" condition .
$(function() {
// 10k items
var arr = $.map(new Array(10000), function(v, k) {
return v === undefined ? k : null
});
var len = arr.length;
var dfd = new $.Deferred();
// collection of items processed at `for` loop in blocks of 250
var fragment = [];
var redraw = function() {
for (i = 0 ; i < 250; i++)
{
// configurationVariableChart.row.add(
// $(
fragment.push('<tr>' +
'<td>' + arr[i] + '</td>' +
'</tr>')
// )[0]);
};
arr.splice(0, 250);
console.log(fragment, arr, arr.length);
return dfd.notify([arr, fragment])
};
$.when(redraw())
// `done` callbacks
.then(function(data) {
$("#results").html(data.join(","));
delete fragment;
}
// `fail` callbacks
, null
// `progress` callbacks
, function(data) {
// log , display `progress` of tasks
console.log(data);
$("progress").val(data[1].length);
$("output:first").text(Math.floor(data[1].length / 100) + "%");
$("output:last").text(data[1].length +" of "+ len + " items processed");
$("#results").html("processing data...");
if (data[0].length) {
var s = setTimeout(function() {
redraw()
}, 100)
} else {
clearTimeout(s);
dfd.resolve(data[1]);
}
})
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<progress min="0" max="10000"></progress><output for="progress"></output>
<output for="progress"></output><br />
<table id="results"></table>
jsfiddle http://jsfiddle.net/guest271314/ess28zLh/

Deferreds/promises won't help you here. JS in the browser is always single-threaded.
The trick is not to build up DOM elements via JS. That is always going to be expensive and slow. Rather than passing data in JSON from Django and building up a DOM dynamically, you should get Django to render a template fragment on the server side and pass that whole thing to the front-end, where the JS can simply insert it at the relevant point.

Related

Deserializing Json dictionary obj to display multiple values

I have a view with a modal pop-up that displays "parameters" or rather data from a Dictionary being passed to the front end. With my current JS, it appears my function will only deserializing one key and value at a time. However, I need to edit the function so that It can deserialize more than one key and value, if the dictionary is passing in more than one key and value..
Below is my code. If you want to know more about the back end please let me know.
Controller is returning:
var parameters = new Dictionary<string, string>();
return Json(parameters, JsonRequestBehavior.AllowGet);
To reiterate, parameters is a Dictionary that can have either one key/value OR it could hold multiple key/value pairs.
JS:
$("button[name='paramsBtn']").click(function () {
/* Grabs ID from col selected */
var $col = $(this).closest('.row').find('.requestId');
var jobRequestId = $col.data('id');
$.ajax({
url: '#Url.Action("JobPollerParameters", "Tools")',
data: { "jobRequestId": jobRequestId},
success: function (results) {
$modal = $('#paramsModal');
$modal.modal("show");
var arr = results;
//loop through arr created from dictionary to grab key(s)
for (var key in arr) {
if (arr.hasOwnProperty(key)) {
var myKey = key;
}
}
var name = myKey;
var value = results[myKey];
$('#modalName').text(name);
$('#modalMessage').text(value);
}
});
});
Here is the modal:
<div class="modal fade" id="paramsModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header modal-header-primary">
<a class="btn btn-xs btn-primary pull-right" data-dismiss="modal" aria-label="Close"><span class="glyphicon glyphicon-remove"></span></a>
<h4 class="modal-title" id="modalTitleText">Job Parameters</h4>
</div>
<div class="modal-body">
<div class="list-group">
<div class="row list-group-item list-group-item-heading container divTableHeading" style="width:inherit; margin-bottom:0px;">
<div class="col-md-6 font-weight-bold"> Parameter: </div>
<div class="col-md-6 font-weight-bold"> Value: </div>
</div>
<div class="row list-group-item container" style="width:inherit;">
<div class="col-md-6 text-break" id="modalName"></div>
<div class="col-md-6 text-break" id="modalMessage"></div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
This line is confusing:
var myKey = key;
After the loop completes, myKey will be equal to the last index in your array, so 2 if results had length 3.
So, name will equal 2 and value will be equal to the last element in results
Maybe you're looking for something like this, since results is {string, string}:
// sample results array from server
var arr = ["val1", "val2", "val3"];
var displayString = "";
for (var key in arr) {
if (arr.hasOwnProperty(key)) {
displayString += arr[key] + ","; // note, there will be a trailing comma
}
}
console.log(displayString);
See the comments below, essentially you arent doing all of the work inside the loop thus your function appears to produce 1 variable (the last one in the dictionary)
$("button[name='paramsBtn']").click(function () {
/* Grabs ID from col selected */
var $col = $(this).closest('.row').find('.requestId');
var jobRequestId = $col.data('id');
$.ajax({
url: '#Url.Action("JobPollerParameters", "Tools")',
data: { "jobRequestId": jobRequestId},
success: function (results) {
$modal = $('#paramsModal');
$modal.modal("show");
var arr = results;
//loop through arr created from dictionary to grab key(s)
for (var key in arr) {
if (arr.hasOwnProperty(key)) {
var myKey = key;
}
}
//Move these variables inside the loop. you are setting them once
//they are in essence being set to the last value in the dictionary
var name = myKey;
var value = results[myKey];
$('#modalName').text(name);
$('#modalMessage').text(value);
}
});
});

Symfony - Fullcalendar event description undefined

I have a controller that gets data from database and turns it in events for fullcalendar, when I display a modal clicking in one of this events, it shows that description and email are undefined.
Image of modal undefined fields
But in XHR I can see that im receiving all the data from the controller.
Image of XHR GET
Modal:
<div id="fullCalModal" class="modal fade" style="z-index: 9999;>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 id="modalTitle" class="modal-title"></h4>
</div>
<div id="modalBody" class="modal-body">
<p id="ev_start" class="modal-body"></p>
<p id="ev_end" class="modal-body"></p>
<p id="ev_mail" class="modal-body"></p>
<p id="ev_desc" class="modal-body"></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button class="btn btn-primary"><a id="eventUrl" target="_blank">Event Page</a></button>
</div>
</div>
</div>
Javascript:
eventClick: function(info) {
var eventObj = info.event;
alert('Clicked ' + eventObj.title + ' with id: ' + eventObj.id + eventObj.description);
$('#modalTitle').html(eventObj.title);
$('#ev_titulo').html(eventObj.title);
$('#ev_start').html('Fecha inicio: ' + eventObj.start);
$('#ev_end').html('Fecha fin: ' + eventObj.end);
$('#ev_desc').html('Descripcion: ' + eventObj.description);
$('#ev_mail').html('Mail: ' + eventObj.mail);
$('#fullCalModal').modal('show');
},
Controller:
public function loadAction()
{
$em = $this->getDoctrine()->getManager();
$eventos = $em->getRepository('App:Evento')->findAll();
$data = array();
foreach ($eventos as $evento)
{
$events['id'] = $evento->getId();
$events['title'] = $evento->getTitle();
$events['start'] = $evento->getBeginAt()->format('Y-m-d');;
$events['end'] = $evento->getEndAt()->format('Y-m-d');;
$events['color'] = $evento->getColor();
$events['description'] = $evento->getDescription();
$events['mail'] = $evento->getMail();
array_push($data, $events);
}
return $this->json($data);
}
The event parsing documentation states that
Every other non-standard prop will be transferred over to the
extendedProps hash in the Event Object.
Since description and mail are not standard event properties in fullCalendar (as listed in that documentation), then they will be placed under the "extendedProps" object in the final event object which fullCalendar creates based on the data it receives from your server.
Therefore in your eventClick code you should be able to write
$('#ev_desc').html('Descripcion: ' + eventObj.extendedProps.description);
$('#ev_mail').html('Mail: ' + eventObj.extendedProps.mail);
and populate the properties successfully into your modal.
P.S. This behaviour is also mentioned in the event object documentation as well. If you have previously used fullCalendar version 3 or below, then this is a change from how these earlier versions worked.

Dynamic jQuery unslider populated from Ajax not clearing children in <ul>

I have this unslider which display tweets polled from some sources via Ajax which refreshes every 8 sec. The Unslider works fine for the first time but subsequently when new ajax queries are fired, it is supposed to clear the first tags and repopulate with new one. For some reason, it doesn't clear out the old tags and instead appends the new tags to the old.
Here are the screenshots:
var flag = false;
(function setTweets() {
$.ajax({
type: "GET",
url: "../tweets/get_latest_tweets",
success: function(data) {
//clear all children first
$('#tweets-list').children().remove();
for (var i = 0; i < data.length; i++) {
$('<li>' + data[i].text + '<div class="card-footer bg-twitter"><div class="card-profile-image"><img src="' + data[i].pic + '" class="rounded-circle img-border box-shadow-1" alt="Card Image"></div>' +
'<footer class="blockquote-footer bg-twitter white"><strong>#' + data[i].screen_name + '</strong></footer></div></li>').appendTo($('#tweets-list'))
}
if (!flag) {
$('#tweet-slider').unslider({ //initialize unslider only once
autoplay: true,
arrows: true,
speed: 1000,
delay: 7000,
});
flag = true;
}
},
error: function(err) {
console.log(err);
}
}).then(function() {
setTimeout(setTweets, 8000); //Todo: Check how to do this async (dynamic adding of points)
});
})();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="card bg-twitter white">
<div class="card-content p-2">
<div class="card-body">
<div class="text-center mb-1">
<i class="ft-twitter font-large-3"></i>
</div>
<div class="tweet-slider" id="tweet-slider">
<ul id='tweets-list' class="text-center">
</ul>
</div>
</div>
</div>
Similar to .empty(), the .remove() method takes elements out of the DOM. Use .remove() when you want to remove the element itself, as well as everything inside it. In addition to the elements themselves, all bound events and jQuery data associated with the elements are removed. To remove the elements without removing data and events, use .detach() instead.
According to this, you are removing # tweets-list as an element, I think if you use .empty () you will clean it
uses $('#tweets-list').empty();
I was reading the unslider website and it says that if you delete or add slides you should use the method slider.unslider('calculateSlides');
var flag = false;
(function setTweets() {
$.ajax({
type: "GET",
url: "../tweets/get_latest_tweets",
success: function(data) {
//clear all children first
$('#tweets-list').children().remove();
for (var i = 0; i < data.length; i++) {
$('<li>' + data[i].text + '<div class="card-footer bg-twitter"><div class="card-profile-image"><img src="' + data[i].pic + '" class="rounded-circle img-border box-shadow-1" alt="Card Image"></div>' +
'<footer class="blockquote-footer bg-twitter white"><strong>#' + data[i].screen_name + '</strong></footer></div></li>').appendTo($('#tweets-list'))
}
if (!flag) {
$('#tweet-slider').unslider('calculateSlides');
flag = true;
}
},
error: function(err) {
console.log(err);
}
}).then(function() {
setTimeout(setTweets, 8000); //Todo: Check how to do this async (dynamic adding of points)
});
})();
apparently there is no need to reinitialize the unslider, try it but, you restart it

Clicking anchor unexpectedly calls alert()

I have some code that retrieves users from a server (ajax) and I use some <a> tags to display it, and when you click on an <a> tag with a user, it's supposed to add it to an array in_group. The first one works, the second one goes to the alert() function AND also adds the user to the array, which confuses me. The remove button doesn't work either. What am I doing wrong? I want the user to be added to the in_group array only if doesn't exist, and to be deleted when the button is pressed.
var in_group = [];
$("#students-body").on('click', 'a', function() {
var modal = $("#manageGroupMembers");
var student_id = $(this).attr('student-id');
var student_name = $(this).html();
var student = {
id: student_id,
name: student_name
};
console.log(in_group.length);
if (in_group.length > 0)
{
for (i = 0; i < in_group.length; i++)
{
console.log(in_group[i].id);
if (in_group[i].id === student_id)
{
alert('in grp');
return;
}
else
{
in_group.push(student);
}
}
}
else
{
in_group.push(student);
}
RefreshGroup();
//modal.modal('hide');
});
function RefreshGroup()
{
var students_group = $("#students-group");
var html = "";
if (in_group.length > 0)
{
for (i = 0; i < in_group.length; i++)
{
html += "<span>"+in_group[i].name+"</span>";
html += "<button class='btn btn-danger' onclick='event.preventDefault();RemoveFromGroup("+i+")'>x</button>";
}
students_group.append(html);
}
}
function RemoveFromGroup(index) {
in_group.splice(index, 1);
RefreshGroup();
}
Html:
<div class="form-group">
<label for="group_members">Members</label>
<button class="btn btn-primary" style="display: block;" onclick="event.preventDefault()" id="add-student-btn">Add Member</button>
<div id="students-group"></div>
</div>
Modal:
<!-- Modal -->
<div class="modal fade" id="manageGroupMembers" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">Add Member to Group</h4>
</div>
<div class="modal-body">
<div id="students-body"></div>
<div id="pagination-students"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
That's because you are inserting the id in a for loop. Change your code:
...your code before this....
var student_id = $(this).attr('student-id');
var student_name = $(this).html();
var student = {
id: student_id,
name: student_name
};
var index = in_group.findIndex(function(std) {
return std.id == student.id;
});
if(index === -1) {
in_group.push(student);
} else {
alert('student was already in array')
}
This way, you check if the student is inside the array. If not (-1) you insert it. Else you alert
In this section of your code, you’re looping the in_group array to find a student entry with a specific ID.
if (in_group.length > 0)
{
for (i = 0; i < in_group.length; i++)
{
console.log(in_group[i].id);
if (in_group[i].id === student_id)
{
alert('in grp');
return;
}
else
{
in_group.push(student);
}
}
}
else
{
in_group.push(student);
}
RefreshGroup();
If you find it, you alert it, and then you stop the loop and the entire function. If you don’t find it, you push it, but you don’t break the loop. You keep looping, the check in_group[i].id === student_id is re-executed and the alert is executed as well.
You could add a return; after calling RefreshGroup, however, why not get rid of the loops and make things easier?
let isInGroup = in_group.find(studentEntry => studentEntry.id === student_id);
if(isInGroup){
alert("in grp");
}
else{
in_group.push(student);
RefreshGroup();
}
This will replace the above section completely and it will work.
As to why the removal doesn’t work:
event isn’t defined. It’ll likely produce a ReferenceError. (Same problem for your other <button>.)
Instead of inline event attributes, look into event delegation and standard event listeners (jQuery has the click method—use it!)
Look into data- attributes or jQuery’s data to hold the value of the index.
Event delegation could look like this:
var students_group = $("#students-group");
students_group.on("click", "button", function(e){
RemoveFromGroup($(this).attr("data-index"));
});
And then, when generating the HTML:
html += "<button class='btn btn-danger' data-index='" + i + "'>x</button>";
I’d also recommend reading about Array.prototype methods, particularly the iteration methods.

Server side pagination with tablesorter - How to refresh it?

I have added a server side pagination with table sorter successfully. I just would like to know how can I refresh it? I would like to create a button to call a refresh function. Does anyone know if there is any method to do it? I do not want to reload the page for it.
UPDATE:
ajaxProcessing: function(data){
if (data && data.hasOwnProperty('rows')) {
var r, row, c, d = data.rows,
total = data.total_rows,
headers = data.headers,
rows = [],
len = d.length;
for ( r=0; r < len; r++ ) {
row = []; // new row array
// cells
for (c in d[r]) {
if (typeof(c) === "string") {
row.push(d[r][c]); //add each table cell data to row array
}
}
rows.push(row); // add new row array to rows array
}
var items="";
$("#tabelaTickets tr:has(td)").remove();
if (rows!==null && rows.length!== 0) {
$.each(rows,function(index,item) {
$("#tabelaTickets").append('<tr class="danger"><td align="center" style="width: 70px"><a type="button" class="btn btn-primary btn-xs" data-placement="right" title="Visualizar ticket" data-toggle="modal" class="btn btn-primary" href="visualizar.php?ticket='+item[3]+'"> #' + item[3] + '</a></td><td><div style="text-overflow:ellipsis;overflow:hidden;width:250px">' + item[4] + '</div></td><td><div style="text-overflow:ellipsis;overflow:hidden;width:350px;">' + item[5] + '</div></td><td><div style="text-overflow:ellipsis;overflow:hidden;width:250px;">' + item[6] + '</div></td><td><div style="text-overflow:ellipsis;overflow:hidden;width:60px;">' + item[7] + '</div></td><td><div style="text-overflow:ellipsis;overflow:hidden;width:70px;">' + item[8] + '</div></td></tr>');
});
}else{
$("#tabelaTickets").append('<tr><td colspan = "6" align="center">SEM RESULTADO A SER EXIBIDO</td></tr>');
}
$("#tabelaTickets").trigger("update");
$("#tabelaTickets").trigger("appendCache");
$("#pleaseWaitDialog").modal('hide');
// in version 2.10, you can optionally return $(rows) a set of table rows within a jQuery object
return [ total];
}
},
Thanks since now,
Erik
your repsonse is JSON, it's easy with a little AJAX function.
example your HTML is look like :
<div class="wrapper">
<div class="item">
<span>item 01</span>
</div>
<div class="item">
<span>item 02</span>
</div>
<div class="item">
<span>item 03 </span>
</div>
</div>
<button class="btn refresh-btn" type="submit"></button>
your response JSON maybe look like :
response = {
{ content : item11 },
{ content : item12 },
{ content : item13 }
};
your HTML render function with AJAX will be look like :
$('.refresh-btn').on('click', function() {
var url = 'yourUrl/?param=refresh&example=true';
var $wrapper = $('.wrapper'); // a div that wrap your new HTML.
$.get(url, {}) //call AJAX GET new item.
.done(function(data) {
$wrapper.html(''); // clear old list;
var $template = $('<div/>', {class : 'item'} ); // create item's HTML.
data.arrayItemList.forEach(function(item) {
var itemTemplate = $template.clone();
itemTemplate.append($('<span/>').text(item.content));
$wrapper.append(itemTemplate); // add new item in list.
});
});
})
that's mean : you create new HTML, and fill it with your data, everything worked fine.
Some time I create a empty template some where in view and clone it.
<div class="sample-template">
<div class="item">
<span> </span>
</div>
</div>
when I need it, I call the jQuery var $template = $('.sample-template').clone(); then fill data with $template.find('span').text(item.content);

Categories