I'm trying to iterate through JSON data and populate the content dynamically using jQuery.
First I do an Ajax request
Then I loop through the data and create the HTML tags dynamically
I create a div container with a class of "product-wrapper" for each JSON object (in this case 2 divs product-wrapper). As you can see on the example of my JSON data, each object has an array of objects "details".
From my second foreach loop - how can I populate the data of "details" within its appropriate "product-wrapper" div?
JavaScript
myApp.init = function(){
var rates = {
products: $('.products-container'),
init: function(){
rates.showRates();
},
showRates: function(){
$.each(utils.rates, function(index, value){
rates.products.append(
'<div class="product-wrapper">' +
'<div class="product-heading">' + value.name + '</div>' +
'<div class="product-details"></div>' +
'</div>'
);
$.each(value.details, function(i,val){
$('.product-details').append('<div class="detail-row">' +
'<div class="detail detail-balance">' + val.balance + '</div>' +
'<div class="detail detail-gross-rate">' + val.grossRate + '</div>' +
'<div class="detail detail-aer">' + val.aer + '</div>' +
'</div>');
});
});
}
};
var utils = {
rates: '',
url: $('.business-interest-rates').attr('data-rates-json'),
init: function(){
utils.ajaxRequest();
},
ajaxRequest: function(){
$.ajax({
url: utils.url,
dataType: 'json',
success:function(data){
utils.rates = data;
if($('#interest-rates').length > 0){
rates.init();
}
},
error: function(e){
console.log(e.message);
}
});
}
};
utils.init();
};
JSON
[
{
"name":"Australian dollar (AUD)",
"currency":"AUD",
"details":[
{
"balance": "1+",
"grossRate": "1.700",
"aer": "0.5"
},
{
"balance": "500,000+",
"grossRate": "2.100",
"aer": "2.00"
},
{
"balance": "1,500,000+",
"grossRate": "2.450",
"aer": "1.00"
}
]
},
{
"name":"EURO (EU)",
"currency":"EU",
"details":[
{
"balance": "1+",
"grossRate": "1.700",
"aer": "1.50"
},
{
"balance": "500,000+",
"grossRate": "2.100",
"aer": "2.20"
}
]
}
]
Either don't use a CSS class to append your stuff but unique ids, or first generate the whole HTML code (wrapper and details all together) in one step and then append it.
// use unique IDs for appending
function () {
$.each(utils.rates, function (index, value) {
rates.products.append(
'<div class="product-wrapper">' +
'<div class="product-heading">' + value.name + '</div>' +
'<div id="product-details-' + value.name + '"></div>' +
'</div>'
);
$.each(value.details, function (i, val) {
$('#product-details-' + value.name).append('<div class="detail-row">' +
'<div class="detail detail-balance">' + val.balance + '</div>' +
'<div class="detail detail-gross-rate">' + val.grossRate + '</div>' +
'<div class="detail detail-aer">' + val.aer + '</div>' +
'</div>');
});
});
}
// first generate all html code, then append
function () {
$.each(utils.rates, function (index, value) {
var html = '<div class="product-wrapper">' +
'<div class="product-heading">' + value.name + '</div>' +
'<div class="product-details">';
$.each(value.details, function (i, val) {
html += '<div class="detail-row">' +
'<div class="detail detail-balance">' + val.balance + '</div>' +
'<div class="detail detail-gross-rate">' + val.grossRate + '</div>' +
'<div class="detail detail-aer">' + val.aer + '</div>' +
'</div>';
});
html += '</div>' +
'</div>';
rates.products.append(
html
);
});
}
This is how I am creating my table & rendering into the aspx page after getting the data from from database by ajax call. I checked the stored procedure execution Time which is only 1.5 seconds where table in my page renders about in 7- 8 seconds. I need to optimize the speed. Any suggestion how I can do it?
$.ajax({
method: 'POST',
//async: false,
data: json_data,
dataType: 'json',
url: json_url
}).success(function (response) {
if (response.IsError) {
showError('Did not receive values from XRM.');
} else {
var webResponse = response;
var dataSet = webResponse["Orders"];
var statuses = [];
//console.log(dataSet);
if (table_role == 'pending') {
statuses = [
{
'id': 39,
'name': 'Pending Scheduling',
'class': 'schedule',
'color': '#7C44BC',
'order': 0
},
{
'id': 40,
'name': 'Pending Screening',
'class': 'screening',
'color': '#8e8e00',
'order': 1
},
{
'id': 41,
'name': 'Pending Pre-Auth',
'class': 'preauth',
'color': '#cc8500',
'order': 2
},
{
'id': 42,
'name': 'Ready For Confirmation',
'class': 'confirm',
'color': '#cc0070',
'order': 3
},
{
'id': 1,
'name': 'Open',
'class': 'open',
'color': null,
'order': 4
},
{
'id': 2,
'name': 'New Order',
'class': 'neworder',
'color': null,
'order': 5
},
{
'id': 3,
'name': 'Changed',
'class': 'changed',
'color': '#cd4e3e',
'order': 6
},
{
'id': 5,
'name': 'Approved',
'class': 'approved',
'color': '#6aa745',
'order': 7
},
{
'id': 6,
'name': 'On Hold',
'class': 'onhold',
'color': '#009acd',
'order': 8
},
{
'id': 11,
'name': 'In Queue',
'class': 'queue',
'color': null,
'order': 9
}
];
// For customers, change labels
if (is_customer) {
statuses[5]['name'] = 'Submitted to SMS';
statuses[9]['name'] = 'Approved';
}
} else if (table_role == 'preauth') {
statuses = [
{
'id': 29,
'name': 'More Info Needed',
'class': 'moreinfo',
'order': 0
},
{
'id': 43,
'name': 'Phone Order',
'class': 'phone',
'order': 1
},
{
'id': 30,
'name': 'Received Order',
'class': 'received',
'order': 2
},
{
'id': 31,
'name': 'Waiting on Documentation',
'class': 'waiting',
'order': 3
},
{
'id': 32,
'name': 'Courtesy Call Pending',
'class': 'courtesy-pending',
'order': 4
},
{
'id': 33,
'name': 'Courtesy Call Complete',
'class': 'courtesy-complete',
'order': 5
},
{
'id': 34,
'name': 'In Pre-Authorization',
'class': 'preauth',
'order': 6
},
{
'id': 35,
'name': 'Additional Docs Needed',
'class': 'addtldocs',
'order': 7
},
{
'id': 36,
'name': 'Pending Authorization',
'class': 'pending',
'order': 8
}
];
}
// Remove any "sorting" backgrounds from headers
$th.removeClass('otable__sort');
// If no orders available
if ($.isEmptyObject(dataSet)) {
$table.find('.otable__empty').css('display', 'table-cell');
} else {
// Loop through orders
$.each(dataSet, function (key, value) {
if (table_role == 'pending') {
var id = (value['ID'] == null) ? '' : value['ID'],
date = (value['ID'] == null) ? '' : value['ExamDate'],
billing = (value['BillCode'] == null) ? '' : value['BillCode'],
scanner = (value['ScannerCode'] == null) ? '' : value['ScannerCode'],
units = (value['Unit'] == null) ? '' : value['Unit'],
by = (value['EmployeeName'] == null) ? '' : value['EmployeeName'],
status = value['StatusID'],
customer = value['Customer'],
location = '';
// Build location string
if (value['Facility']) {
location += value['Facility'] + '<br>';
}
if (value['City'] && value['State']) {
location += value['City'] + ', ' + value['State'];
}
} else if (table_role == 'preauth') {
var id = (value['ID'] == null) ? '' : value['ID'],
poid = (value['POID'] == null) ? '' : value['POID'],
site = (value['Site'] == null) ? '' : value['Site'],
patient = (value['PatientName'] == null) ? '' : value['PatientName'],
insurance_co = (value['InsuranceCo'] == null) ? '' : value['InsuranceCo'],
received_date = (value['ReceivedDate'] == null) ? '' : value['ReceivedDate'],
exam_date = (value['ExamDate'] == null) ? '' : value['ExamDate'],
updated_date = (value['UpdatedDate'] == null) ? '' : value['UpdatedDate'],
status = value['StatusID'],
priority = (value['Priority'] == null) ? 0 : value['Priority'],
custid = (value['custID'] == null) ? '' : value['custID'];
}
// Find status from ID
var object = $.grep(statuses, function (element, index) {
return element['id'] == status;
});
object = object[0];
// Get status name
var status_name = object['name'],
status_pos = object['order'],
status_class = object['class'],
datestring = '',
received_datestring = '',
exam_datestring = '',
updated_datestring = '';
// Build date strings
if (date) {
datestring = splitDate(date);
}
if (received_date) {
received_datestring = splitDate(received_date);
}
if (exam_date) {
exam_datestring = splitDate(exam_date);
}
if (updated_date) {
updated_datestring = splitDate(updated_date);
}
// Build Time strings
if (updated_date) {
updated_timestring = splitTime(updated_date);
}
else {
updated_timestring = '';
}
// Convert priority for sorting
var priority_order;
if (priority) {
if (priority == 0) {
priority_order = 2;
} else if (priority == 5) {
priority_order = 1;
} else if (priority == 10) {
priority_order = 0;
}
}
// HTML for table row
var $row;
if (table_role == 'pending') {
if (!is_customer && (is_scheduler || is_retail)) {
$row = $('<tr class="otable__row otable__row--' + status_class + '">' +
'<td class="otable__id"><a href=/Customer/FDGOrders.aspx?oid=' + id + '>' + id + '</a></td>' +
'<td class="otable__date" data-sort-value="' + date + '"><a href=/Customer/FDGOrders.aspx?oid=' + id + '>' + datestring + '</a></td>' +
'<td class="otable__billing"><a href=#rptPendingGroupByOrder onclick=showDialog(\'/CustomerProfile.aspx?cid=' + customer + '\')>' + billing + '</a></td>' +
'<td class="otable__scanner"><a href=/Customer/FDGOrders.aspx?oid=' + id + '>' + scanner + '</a></td>' +
'<td class="otable__loc"><a href=#rptPendingGroupByOrder onclick=showDialog(\'/CustomerProfile.aspx?cid=' + customer + '\')>' + location + '</a></td>' +
'<td class="otable__units"><a href=/Customer/FDGOrders.aspx?oid=' + id + '>' + units + '</a></td>' +
'<td class="otable__by"><a href=/Customer/FDGOrders.aspx?oid=' + id + '>' + by + '</a></td>' +
'<td class="otable__status" data-status-id="' + status + '" data-sort-value="' + status_pos + ' ' + date + '"><a href=/Customer/FDGOrders.aspx?oid=' + id + '>' + status_name + '</a></td>' +
'</tr>');
} else if (!is_customer && (!is_scheduler || !is_retail)) {
$row = $('<tr class="otable__row otable__row--">' +
'<td class="otable__id"><a href=/Customer/FDGOrders.aspx?oid=' + id + '>' + id + '</a></td>' +
'<td class="otable__date" data-sort-value="' + date + '"><a href=/Customer/FDGOrders.aspx?oid=' + id + '>' + datestring + '</a></td>' +
'<td class="otable__billing"><a href=#rptPendingGroupByOrder onclick=showDialog(\'/CustomerProfile.aspx?cid=' + customer + '\')>' + billing + '</a></td>' +
'<td class="otable__scanner"><a href=/Customer/FDGOrders.aspx?oid=' + id + '>' + scanner + '</a></td>' +
'<td class="otable__loc"><a href=#rptPendingGroupByOrder onclick=showDialog(\'/CustomerProfile.aspx?cid=' + customer + '\')>' + location + '</a></td>' +
'<td class="otable__units"><a href=/Customer/FDGOrders.aspx?oid=' + id + '>' + units + '</a></td>' +
'<td class="otable__by"><a href=/Customer/FDGOrders.aspx?oid=' + id + '>' + by + '</a></td>' +
'<td class="otable__status" data-status-id="' + status + '" data-sort-value="' + status_pos + ' ' + date + '"><a href=/Customer/FDGOrders.aspx?oid=' + id + '>' + status_name + '</a></td>' +
'</tr>');
} else {
$row = $('<tr class="otable__row otable__row--customer otable__row--' + status_class + '">' +
'<td class="otable__id"><a href=/Customer/FDGOrders.aspx?oid=' + id + '>' + id + '</a></td>' +
'<td class="otable__date" data-sort-value="' + date + '"><a href=/Customer/FDGOrders.aspx?oid=' + id + '>' + datestring + '</a></td>' +
'<td class="otable__units"><a href=/Customer/FDGOrders.aspx?oid=' + id + '>' + units + '</a></td>' +
'<td class="otable__by"><a href=/Customer/FDGOrders.aspx?oid=' + id + '>' + by + '</a></td>' +
'<td class="otable__status" data-status-id="' + status + '" data-sort-value="' + status_pos + ' ' + date + '"><a href=/Customer/FDGOrders.aspx?oid=' + id + '>' + status_name + '</a></td>' +
'</tr>');
}
} else if (table_role == 'preauth') {
var insurance_sort_val = priority_order;
if (exam_date && exam_date.indexOf('0001-01-01') < 0) {
insurance_sort_val = priority_order + ' ' + exam_date;
} else if (received_date) {
// Force items with null exam date to bottom, then sort by received date
insurance_sort_val = priority_order + ' Z ' + received_date;
}
$row = $('<tr class="otable__row otable__row--' + status_class + '">' +
'<td class="otable__id">' + id + '</td>' +
'<td class="otable__site">' + site + '</td>' +
'<td class="otable__by">' + patient + '</td>' +
'<td class="otable__insurance otable__priority--' + priority + '" data-sort-value="' + insurance_sort_val + '">' + insurance_co + '</td>' +
'<td class="otable__date" data-sort-value="' + received_date + '">' + received_datestring + '</td>' +
'<td class="otable__date" data-sort-value="' + exam_date + '">' + exam_datestring + '</td>' +
'<td class="otable__status" data-status-id="' + status + '" data-sort-value="' + status_pos + ' ' + exam_date + '">' + status_name + '</td>' +
'<td class="otable__date--wide" data-sort-value="' + updated_date + '">' + updated_datestring + ' ' + updated_timestring + '</td>' +
'</tr>');
$row.find('a').on('click', function (e) {
if (!$(this).parent('.otable__site').length) {
e.preventDefault();
showDialog('/Customer/FDGPatientOrder.aspx?oid=' + id + '&poid=' + poid);
}
if ($(this).parent('.otable__site').length) {
e.preventDefault();
showDialog('/CustomerProfile.aspx?cid=' + custid);
}
});
}
// Append to table body
$table.find('tbody').append($row);
});
}
// Fire event
//$table.trigger('all-rows-added');
//$table.off('all-rows-added');
// Table sorting functionality
if (table_role == 'pending') {
initTableSorting($table, 7);
} else if (table_role == 'preauth') {
initTableSorting($table, 3);
}
// Status filter functionality
var $list = $table.find('.otable__status__list');
// Clear previous filters
$list.find('ul>li:not(:first)').remove();
// Loop through statuses to see which are contained in the table
$.each(statuses, function (key, value) {
if ($table.find('tbody').has('.otable__status[data-status-id="' + value.id + '"]').length > 0) {
var $li;
if (table_role == 'pending') {
$li = $('<li>' +
'<button data-filter="' + value.id + '" data-color="' + value.color + '">' +
'<div class="otable__legend"></div>' +
value.name +
'</button>' +
'</li>');
} else {
$li = $('<li>' +
'<button data-filter="' + value.id + '">' +
'<div class="otable__legend"></div>' +
value.name +
'</button>' +
'</li>');
}
// Add the list item
$li = $li.appendTo($list.find('ul'));
// Set the legend color
var $btn = $li.find('button'),
filter = $btn.data('filter'),
color = $btn.data('color');
if (color !== null) {
$li.find('.otable__legend').css('background', color);
}
}
});
$th.on('click', function (e) {
// Check if main filter is being clicked, otherwise don't sort
if ($list.has(e.target).length > 0) {
var $target = $(e.target).closest('button'),
filter = $target.data('filter');
// Sort all rows by ascending status
$table.find('th').eq(7).stupidsort('asc');
// Style actively selected item
$list.find('li').removeClass('otable__status__checked');
$target.parent('li').addClass('otable__status__checked');
if (filter == 'all') {
$table.find('tbody > tr').css('display', 'table-row');
} else {
$table.find('tbody > tr').css('display', 'none');
$table.find('tbody > tr').has('.otable__status[data-status-id="' + filter + '"]').css('display', 'table-row');
}
// Force hide list when option clicked
$list.css({
'clip': 'rect(0 0 0 0)',
'opacity': 0
});
// Restore default behavior once mouse is moved
$table.mousemove(function () {
$list.removeAttr('style');
});
// Prevent main sorting action
return false;
}
});
// Sticky table header
$table.stickyTableHeaders();
// Remove loading indicator
$table.find('.otable__loading').css('display', 'none');
}
}).fail(function (jqxhr, textStatus, error) {
var err = textStatus + ", " + error;
console.log("Failed to get XRM initialization values. Message: " + err);
});
We ran out in similar scenarios, where we had more than 30000 records to render on the same page, API was doing good but UI became pathetic in terms of performance. Chrome and Firefox survived somehow and rendered records with delays of 10 to 15 seconds, but IE was a nightmare, where the browser just froze everytime we accessed that page. The solution to this problem was gradual but elegant, we had to switch to the basics. I would break down a subset of my solution here
Replace $.each and _.each loops with native for(var i = 0; i < lenght; i += 1) loops. Doing this created a major boost in terms of performance. To prove this concept, at that time I created a jsperf (check here) performance benchmark, which showed huge performance gains. In your case, for example, replace $.each(dataSet, function (key, value) { and $.each(statuses, function (key, value) { with a native for loops. See this image comparing the loops
Reducing the number of JQuery selectors also proved to be beneficial, and instead of creating an in memory jquery elements like $('<div>bla bla bla</div>');, use string concatenation and at the end using for example $el.html() proved to be very efficient. In your case instead of doing things like $row = $('<tr class ... and then binding function like $row.find('a').on('click', you only need to create a long string and append everything in it, and at the end append that long string to the DOM. If you are just doing this to bind the click event to every single row (OMG), you can achieve the similar behavior by using JQuery on click on the parent with child as a filter, like $('table').on('click', 'tr', myClickFunc);. This will only bind a single click event instead of thousands of click events, resulting in a great performance gain.
By avoiding JQuery filtering methods like $.grep or $.map or $.filter etc, and using native loops, also resulted in drastic improvements.
By using jQuery element caching and avoiding frequent queries made to the DOM significant gains were achieved in different pages. I think you are doing caching fine, but try avoiding frequent DOM querying.
Avoiding multiple method calling from the loops was also eating up the overall loading time, so combining multiple method calls to a single method call and doing common things within improved performance. Like you can make splitDate take an array and return an array, instead of multiple method calls.
Finally the gem of all, and it's my favorite, I was using custom data grids to populate tabular data, but since the time I have encountered W2UI Grid, I have fallen in love with it. It can render millions of records at a single time, and it works by rendering only few records to display and as the scroll happens it renders other records and removes the records from DOM that are no longer visible. It's super easy to configure, so you can also use this grid instead of your custom table (if you are not using pagination).
So this is just a brief summary of the immediate measures that you can make to improve the load-time and rendering performance.
One more important tip is that you should also check the size of the response coming from server, if its big and is in mega-bytes, then you will have to optimize your API also, and only provide UI with the things that it needs, don't provide excessive things, as it slows up stuff at the UI.
Applying pagination is best solution, else you need to optimise your code (though the performance is negligible, but help to prevent browser crash if you have more data)
For example :
$.each(dataSet, function (key, value) {
// Append to table body
$table.find('tbody').append($row);
}
should be write as
var $row='"";
$.each(dataSet, function (key, value) {
$row +="something";
}
// Append to table body
$table.find('tbody').append($row);
You are using .append() inside $.each() this mean append() trigger for each loop.
It good if you append it outside loop.
Instead of $table.find('tbody').append($row) you can cache .find() in a variable and then direct append it
I have selectize js code. It runs well and shows right option after i type key on select box.
Here is the code :
$('#select-links').selectize({
maxItems: diffItems,
valueField: 'nick_name',
labelField: 'full_name',
searchField: ['nick_name', 'full_name'],
options: [],
create: false,
render: {
item: function(item, escape) {
return '<div id="' + escape(item.id) + '">' +
'<span class="nick_name">' + escape(item.full_name) + '</span>' +
'<span class="full_name"> <' + escape(item.nick_name) + '></span>' +
'</div>';
},
option: function(item, escape) {
return '<div class="box-user clearfix">' +
'<div class="thumb col-xs-1 mr10">' +
'<img class="lazyload" src="'+escape(item.avatar)+'" data-src="' + escape(item.avatar)+'">' +
'</div>' +
'<div class="col-xs-8">' +
'<span class="label" style="color: #000;">' + escape(item.full_name) + '</span>' +
'<span class="caption">/' + escape(item.nick_name) + '</span>' +
'</div>' +
'</div>';
}
},
load: function(query, callback) {
if (!query.length) return callback();
$.ajax({
url: '/ajax/origin/search',
type: 'GET',
dataType: 'json',
data: {
q: query
},
error: function() {
callback();
},
success: function(res) {
callback(res);
}
});
}
});
But, i got the problem.
After document ready, i want selectize add item automatically after document / page has already loaded without i have to type the key on select box. How is the code?
#if(isset($_POST['nick']))
$(".chat__list--active").show();
$(".edit").hide();
$('.chat__list').hide();
$('#cancel-participants').show();
if(diffItems != 49) {
$('#save-participants').show();
}
alert("{{$_POST['nick']}}");
// This is the code's place to add item automatically and the key is $_POST['nick']
#endif
Thank you.
Sorry for bad english
You can add options to existing selectize by following approach:
$(document).ready(function(){
var selectize_element = $("#select-links")[0].selectize;
selectize_element.addOption({
text:'First Item',
value: 'First Item'
});
selectize_element.refreshOptions() ;
selectize_element.addItem('First Item');
});
I am trying to apply filtering to a dataTables table using select boxes. I found the following code which allows me to setup the select boxes and filter based on the column data:
https://datatables.net/examples/api/multi_filter_select.html
This code worked perfectly, however I am now using the render method to pull all the columns together into one. We are doing this so we can style each row to create a single 'Ticket'.
Unfortunately now the filtering does not work. I imagine it may be a result of the columns no longer displaying but would appreciate some direction and help :)
Current Code:
$('#ticket_list').DataTable( {
"columnDefs": [
{
"render": function ( data, type, row ) {
return '<span class="client-data"> ' + data + ' </span>'
+ '<span class="priority-data"> ' + row[1] + ' </span>'
+ '<span class="status-data"> ' + row[2] + ' </span>'
+ '<div class="subject-data"> ' + row[3] + ' </div>'
+ '<i class="fa fa-user"></i><span class="agent-data"> ' + row[4] + ' </span>'
+ '<span class="date-data"> ' + row[5] + ' </span>';
},
"targets": 0
},
{ "visible": false, "targets": [ 1,2,3,4,5 ]}
],
"columns": [
{ "title": "" }
],
"pageLength": setPageLength(),
"dom": '<"#search-box"f> rt <"#pagination.col-xs-12"p> <"#table-information.col-xs-12"i>',
language: {
search: "_INPUT_",
searchPlaceholder: "Search"
},
initComplete: function () {
this.api().columns([0,1,2]).every( function () {
var column = this;
var select = $('<select><option value=""></option></select>')
.appendTo( ".ticket-filtering" )
.on( 'change', function () {
var val = $.fn.dataTable.util.escapeRegex(
$(this).val()
);
console.log(val)
column
.search( val ? '^'+val+'$' : '', true, false )
.draw();
} );
column.data().unique().sort().each( function ( d, j ) {
select.append( '<option value="'+d+'">'+d+'</option>' )
} );
} );
},
} );
CAUSE
You render function returns HTML string unconditionally disregarding the type argument but this function is used to get the data for multiple purposes: displaying, ordering, filtering, type detection.
SOLUTION
Use type detection in render function and only return HTML when data is needed to be displayed (type === 'display').
"render": function ( data, type, row ) {
if(type === 'display'){
data = '<span class="client-data"> ' + data + ' </span>'
+ '<span class="priority-data"> ' + row[1] + ' </span>'
+ '<span class="status-data"> ' + row[2] + ' </span>'
+ '<div class="subject-data"> ' + row[3] + ' </div>'
+ '<i class="fa fa-user"></i><span class="agent-data"> ' + row[4] + ' </span>'
+ '<span class="date-data"> ' + row[5] + ' </span>';
}
return data;
},
DEMO
See this jsFiddle for code and demonstration.