How to add Rowspan in JQuery datatables - javascript

Im using Jquery datatables to construct a table.
My requirement is like below
This is not a static table, and we are rendering it using json data. Here I'm, rendering the rows dynamically using "aoColumns".
Is there any way to use rowspan so that the cells (1,2,David,Alex) can be spanned.
Does datatables support this kind of table ?

Datatables does not support this kind of grouping out of the box.
But, as in many cases, there is a plugin available.
It is called RowsGroup and is located here: Datatables Forums. A live example is also included.
If you change the JS part in this example to the below you will have your desired output presented to you in the output window.
$(document).ready( function () {
var data = [
['1', 'David', 'Maths', '80'],
['1', 'David', 'Physics', '90'],
['1', 'David', 'Computers', '70'],
['2', 'Alex', 'Maths', '80'],
['2', 'Alex', 'Physics', '70'],
['2', 'Alex', 'Computers', '90'],
];
var table = $('#example').DataTable({
columns: [
{
name: 'first',
title: 'ID',
},
{
name: 'second',
title: 'Name',
},
{
title: 'Subject',
},
{
title: 'Marks',
},
],
data: data,
rowsGroup: [
'first:name',
'second:name'
],
pageLength: '20',
});
} );
Here is a screenshot of the result:

I tried the RowsGroup plugin, but it achieves this just by hijacking the DataTables sort mechanism. If you tell it to group a given column, what it does for you is basically to apply a sort to that column that you can't turn off. So, if you want to sort by another column, you can't. That didn't work in my application.
Instead, here's a working fiddle for a recipe that allows you to achieve this result:
https://jsfiddle.net/bwDialogs/fscaos2n
The basic idea is to flatten all of your multi-row data into a single row. Content in your 2nd, 3rd, etc. rows are stored as a hidden <script> template tag within your first row.
It works by using DataTables' drawCallback function to manipulate the DOM once DataTables has rendered it, without confusing DataTables by having to try parsing rowspan cell content.
Since this modifies the DOM after DataTables has done its magic, your multi-row sections will stick together even with pagination, searching, and sorting.
Cheers.

add a below code and modify according to your requirement
$(window).on("load",function() {
MakeRows();
addRowspan();
$(".paginate_button").on("click", function() {
MakeRows();
addRowspan();
});
});
function MakeRows() {
var tmp_tbl = $("#dashboardDetails");
var _l = tmp_tbl.find("tr");
var _td = "",_t_td = "", old_txt = "",_t_txt = ""; _tr_count = 1;_tr_countadd = 1;
for(i = 0;i< _l.length; i ++) {
_t_td = tmp_tbl.find("tr").eq(i).find("td").eq(0).find("span");
_t_txt = $(_t_td).text();
_t_txt = _t_txt.replace(/\//,"_");_t_txt = _t_txt.replace(/\//,"_");
if (_t_txt.length > 0) {
if(_t_txt != old_txt) {
if($(_l).eq(i).hasClass(_t_txt) == false) {
_tr_count = 1;_tr_countadd = 1;
$(_l).eq(i).addClass("" + _t_txt + "").addClass(_t_txt + "_" + i);
}
old_txt = _t_txt;
} else {
_tr_count = _tr_count + 1;
if (_tr_countadd == 1) {
$(_l).eq(i).addClass("" + _t_txt + "").addClass(_t_txt + "_" + i)
.addClass("hiddenClass").addClass("maintr").attr("trcount", _tr_count).attr("addedtrcount", "maintr");
_tr_countadd = _tr_countadd + 1;
} else {
$(_l).eq(i).addClass("" + _t_txt + "").addClass(_t_txt + "_" + i)
.addClass("hiddenClass").attr("trcount", _tr_count)
}
}
}
_t_td = "";
}
}
function addRowspan() {
var t_txt = "";
var _alltrmain = $(".maintr");
var _h_td = ["0","10","11","12","13"];
for (i = 0; i <= _alltrmain.length; i ++) {
for (j = 0; j <= _h_td.length; j ++) {
t_txt = $(_alltrmain).eq(i).attr("trcount");
$(_alltrmain).eq(i).prev().find("td").eq(_h_td[j]).attr("rowspan", t_txt);
}
}
}

Related

Crawlling html and save json

im crawlling a html page, and is working fine but im having a issue, when im looping a rows from a table, and saving it in a json file, when finishing the loop instead of having 7 records i get the one 1 record and the last one from the loop.
My code:
finalResult[date] = {};
$(this).find('tbody tr').each(function (i, el) {
var $tds = $(this).find('td')
var counter = i +1;
$tds.each(function(index, element){
var $th = $(this).closest('table').find('th').eq($(this).index());
if($th.text() != ""){
var temp = { [counter] : $(this).text()};
finalResult[date][$th.text()] = { ...temp };
}
});
});
Im getting this result:
{
'Satuday, 31 Jan 2021': { PTM: { '7': '402 - 1' }, PT: { '7': '905 - 2' } }
}
When it should be like:
{
'Satuday, 31 Jan 2021': { PTM: { '1': '442 - 1','2': '442 - 1',.... ,'6': '402 - 1','7': '402 - 1' }, PT: { '1': '442 - 1','2': '442 - 1',.... ,'6': '402 - 1','7': '905 - 2' } }
}
I'm looping throught tbody tr and getting the text from "td"
Hem... okay. Below is just a guess...
Just reading the code without any HTML to rely on, I still have to suggest.
finalResult[date] = {};
$(this).find('tbody tr').each(function (i, el) {
// That is all td for one row
var $tds = $(this).find('td')
// So that is a row counter
var counter = i +1;
// Now looping tds of ONE row
$tds.each(function(index, element){
// Retreiving the corresponding column element (for the column name)
var $th = $(this).closest('table').find('th').eq($(this).index());
if($th.text() != ""){
// If not already existing, set that as an empty object
// If undefined, it set an empty object
// If existing, it does not change anything
finalResult[date][$th.text()] = finalResult[date][$th.text()] || {};
// The td text as an object {column+number:"text"}
var temp = { [counter] : $(this).text()};
// "merge" that new "td object" to the existing column object
finalResult[date][$th.text()] = { ...finalResult[date][$th.text()], ...temp };
}
});
});
In your code, finalResult[date][$th.text()] = { ...temp }; was just overwriting the previous object...

retrieving multiple levels, API

I've accessed 5 followers of a GitHub user using AJAX. I'm trying to go three levels deep on each of the first 5 followers returning 5 followers of each. So, initially, return 5 followers then for each of the five followers go three levels deep returning 5 more followers at each level.
How would I go about this? Recursion? Nested for loops?
Also, when I render the first five followers they render all on the same line in my html. Trying to simply append an html break tag on each loop but doesn't seem to work.
Thanks.
$(function() {
$("#submitbtn").on("click", function(e) {
e.preventDefault();
var username = $("#userID").val();
console.log("username " + username);
var userFollower =
"https://api.github.com/users/" + username + "/followers";
$.getJSON(userFollower, function(data) {
console.log(data);
for (i = 0; i < 5; i++) {
var br = "<br>";
var follower = data[i].login;
console.log("Follower " + follower);
$(".follower").append(follower);
$(".container").append(br);
}
});
});
});
Here is an example using some basic recursion. Note that I also modified the DOM manipulation so that each follower was on a new line, in its own div.
I set the depth to 3 so that I could limit the hits to github.
UPDATE [1]
Noticed that you wanted depth = 3 and 5 followers each, and those numbers were not linked. Modified the snippet to unlink those numbers from each other.
var depth = 3;
var number_per_level = 5;
//var tst = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
$(function() {
$("#submitbtn").on("click", function(e) {
e.preventDefault();
var username = $("#userID").val();
getFollowers(0, $(".container"), username);
});
});
function getFollowers(count, $container, username) {
if (count >= depth) {
return false;
}
//console.log("username " + username);
var userFollower =
"https://api.github.com/users/" + username + "/followers";
$.getJSON(userFollower, function(data) {
//console.log(data);
for (let i = 0; i < number_per_level; i++) {
var follower = data[i].login; //tst[i]; //data[i].login;
var $mine = $("<div class='follower'>");
//console.log(`Follower ${follower} follows ${username}`);
$mine.append(follower).appendTo($container);
getFollowers(count + 1, $mine, follower);
}
});
}
.follower {
padding-left: 1em;
border: 1px solid #c0c0c0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input id="userID" value="tim" /><button id="submitbtn">GO</button>
<div class="container"></div>
If you know how many levels you have to deep dive, then its better to take nested loops rather than recursions.
var git = [{
name: 'a',
fol: [{
name: 'b',
fol: [{
name: 'c'
}]
}]
}, {
name: 'r',
fol: [{
name: 't',
fol: [{
name: 'e'
}]
}]
}];
git.forEach((firstlevel) => {
console.log(firstlevel.name);
firstlevel.fol.forEach((seclevel) => {
console.log(seclevel.name);
seclevel.fol.forEach((thirdlevel) => {
console.log(thirdlevel.name);
});
});
});
Improper handling of recursion leads to infinite calls and may lead to javascript heap out of memory. And also loops are faster than recursions.

Hiding columns and then showing all jumbles up cell values with array datasource

I based my logic on the code suggested here http://jsfiddle.net/LkLkd405/4/
I added 2 new items to the default context menu (code from my helpers module)
addColHideItemsToContextMenu: function(hot, colsToHide, globalColumns, globalHeaders){
var items = hot.contextMenu.defaultOptions.items;
//console.log("items: "+JSON.stringify(items));
//hide_col based on existing remove_col
var hideColItem = {
key: 'hide_col',
name: 'Hide column',
callback: function(key, selection) {
//remove_col code
//var amount = selection.end.col - selection.start.col + 1;
//this.alter("remove_col", selection.start.col, amount);
//hide_col custom code
colsToHide.push(selection.start.col);
var newCols = [];
var newHeaders = [];
for (var i = 0; i < globalHeaders.length; i++) {
if (colsToHide.indexOf(i) === -1) {
newCols.push(globalColumns[i]);
newHeaders.push(globalHeaders[i]);
}
}
hot.updateSettings({
columns: newCols,
colHeaders: newHeaders
});
},
disabled: function() {
var selected = this.getSelected(),
entireRowSelection = [selected[0], 0, selected[0], this.countCols() - 1],
rowSelected = entireRowSelection.join(',') == selected.join(',');
return (selected[1] < 0 || rowSelected);
}
};
items.push(hideColItem);
var showAllColItem = {
key: 'showall_cols',
name: 'Show all columns',
callback: function(key, selection) {
//var amount = selection.end.col - selection.start.col + 1;
//this.alter("remove_col", selection.start.col, amount);
colsToHide = [];
hot.updateSettings({
columns: globalColumns,
colHeaders: globalHeaders
});
},
disabled: function() {
return false;
}
};
items.push(showAllColItem);
},
I then have a server side paging module can receives json objects from the server and converts them to an array of arrays, else new columns cannot be added.
EnhancementPager: function(paramObj) {
var handsontableIntegration = function(rows){// called line 283
paramObj.handsonDataObjects = rows;//received from the server and not used again
paramObj.handsonDataArrays = help.buildArrayFromObjs(rows);
paramObj.globalHeaders = help.extractColHeaders(rows[0]);
paramObj.globalColumns = help.buildArrayDataSourceColumnConfig(paramObj.handsonDataArrays[0]);
paramObj.colsToHide = [];
if(paramObj.table){
var hot = paramObj.table;
hot.updateSettings({
colHeaders: paramObj.globalHeaders,
columns: paramObj.globalColumns
});
help.addColHideItemsToContextMenu(hot, paramObj.colsToHide, paramObj.globalColumns, paramObj.globalHeaders);
hot.loadData(paramObj.handsonDataArrays);
}
}
This is what the grid looks like on load - notice the values on the top row
This is what the top row changes to after hiding the column
Then is I select the show all context item
The top row changes to this
I've only been working with handsontable for a few days, it's a really well written and organised js api. But I'm pretty mystified by this behaviour.

Dynamically created Tablesorter not working

Updated: Please take a look at this fiddle:
I want to use the tablesorter and its sticky header plugin for the dynamically created tables. But I have trouble getting the code to work, despite the inclusion of $('.tablesorter').trigger('updateAll'); and $(".tablesorter").tablesorter(options); at the end of the loop.
Can anyone point out what's wrong with the following code?
var options = {
widthFixed : true,
showProcessing: true,
headerTemplate : '{content} {icon}', // Add icon for jui theme; new in v2.7!
widgets: [ 'uitheme', 'zebra', 'stickyHeaders', 'filter' ],
widgetOptions: {
// extra class name added to the sticky header row
stickyHeaders : '',
// number or jquery selector targeting the position:fixed element
stickyHeaders_offset : 0,
// added to table ID, if it exists
stickyHeaders_cloneId : '-sticky',
// trigger "resize" event on headers
stickyHeaders_addResizeEvent : true,
// if false and a caption exist, it won't be included in the sticky header
stickyHeaders_includeCaption : true,
// The zIndex of the stickyHeaders, allows the user to adjust this to their needs
stickyHeaders_zIndex : 2,
// jQuery selector or object to attach sticky header to
stickyHeaders_attachTo : null,
// scroll table top into view after filtering
stickyHeaders_filteredToTop: true,
// adding zebra striping, using content and default styles - the ui css removes the background from default
// even and odd class names included for this demo to allow switching themes
zebra : ["ui-widget-content even", "ui-state-default odd"],
// use uitheme widget to apply defauly jquery ui (jui) class names
// see the uitheme demo for more details on how to change the class names
uitheme : 'jui'
}
};
var data = [{
number: '1'
}, {
number: '2'
}, {
number: '3'
}, {
number: '4'
}, {
number: '5'
}, {
number: '6'
}, {
number: '7'
}, {
number: '8'
}, {
number: '9'
}, {
number: '10'
}];
var chunks = [];
var item_html = "";
for (var i = 0; i < data.length;) {
chunks.push(data.slice(i, i += 3));
}
for (var i = 0; i < chunks.length; i++) {
item_html += "<table class='tablesorter'><thead><tr>";
chunks[i].map(function (v, key) {
item_html += "<th>" + key + "</th>";
});
item_html += "</tr></thead><tbody><tr>";
chunks[i].map(function (v) {
item_html += "<td>" + v.number + "</td>";
});
item_html += "</tr></tbody></table>";
$(".tablesorter").tablesorter(options);
$('.tablesorter').trigger('updateAll');
}
$('#area').append(item_html)
The problem is that tablesorter is being called on elements that don't exist.
Move the $(".tablesorter").tablesorter(options); to be called after the HTML has been appended to the area div. The updateAll method isn't needed at all (demo):
chunks[i].map(function (v) {
item_html += "<td>" + v.number + "</td>";
});
item_html += "</tr></tbody></table>";
// $(".tablesorter").tablesorter(options);
// $('.tablesorter').trigger('updateAll');
}
$('#area').append(item_html);
$(".tablesorter").tablesorter(options);

JSON-Object as string: Use of concat(), eval() and/or JSON.parse()?

What I want to do: I want to display a diagram using Highcharts and the best code style.
Problem: My code is unreadable / hard to debug because I store it within variables.
Okay I have a Javascript-Object containing all the information I need for later use (categories, y axis value and the series itself which is split into positive, neutral and negative).
var testDia =
{
name : 'Testname',
'chartCategories': ['Golf', 'Polo', 'Passat'],
'chartSeries':
{
'positive': [
{y:341, url:'http://golf.com?q=positive'},
{y:487, url:'http://polo.com?q=positive'},
{y:180, url:'http://passat.com?q=positive'}
],
'neutral': [
{y:12, url:'http://golf.com?q=neutral'},
{y:3, url:'http://polo.com?q=neutral'},
{y:9, url:'http://passat.com?q=neutral'}
],
'negative': [
{y:222, url:'http://golf.com?q=negative'},
{y:115, url:'http://polo.com?q=negative'},
{y:321, url:'http://passat.com?q=negative'}
]
}
}
My approach is to loop over the categories and add positive, neutral and negative to seperate strings which I evaluate later on in the highcharts-setup.
var allPositiveData = '';
var allNeutralData = '';
var allNegativeData = '';
for(var i=0; i < categories.length; i++) {
var diaPositive = series['positive'][i]['y'];
var diaNeutral = series['neutral'][i]['y'];
var diaNegative = series['negative'][i]['y'];
urlPositive = series['positive'][i]['url'];
urlNeutral = series['neutral'][i]['url'];
urlNegative = series['negative'][i]['url'];
allPositiveData += "{'y': " + diaPositive + ", 'url': '" + urlPositive + "'}, ";
allNeutralData += "{'y': " + diaNeutral + ", 'url': '" + urlNeutral + "'}, ";
allNegativeData += "{'y': " + diaNegative + ", 'url': '" + urlNegative + "'}, ";
} // end of loop
allPositiveData = eval( "[" + allPositiveData.slice(0, -2) + "]" );
allNeutralData = eval( "[" + allNeutralData.slice(0, -2) + "]" );
allNegativeData = eval( "[" + allNegativeData.slice(0, -2) + "]" );
Highcharts-setup
newChart = new Highcharts.Chart({
chart : {
renderTo : 'container',
type: 'column'
},
[...] // skipping the rest of the setup
series: [
{
name: 'Positive',
data: allPositiveData
}, {
name: 'Neutral',
data: allNeutralData
}, {
name: 'Negative',
data: allNegativeData
}]
});
I figure there are a few ways to achieve what I want but I want to know a better (maybe object orientated) solution.
http://jsfiddle.net/x8455/
I think you are trying to do something what you already have done.
You want to get display three series from your JSON, for respective categories, then let's to that:
Your code:
var categories = testDia['chartCategories'];
var series = testDia['chartSeries'];
Great! Now use that variables:
newChart = new Highcharts.Chart({
chart : {
renderTo : portletContainer,
type: 'column'
},
...
xAxis: {
categories: categories,
labels: {
rotation: -45,
align: 'right',
}
},
...
series: [{
name: 'Positive',
data: series.positive // testDia.chartSeries.positive is the same
}, {
name: 'Neutral',
data: series.neutral
}, {
name: 'Negative',
data: series.negative
}]
});
And working demo: http://jsfiddle.net/x8455/1/
Here's how you should do it.
var allPositiveData = []; // You want an array, so start off with an array.
for(var i=0; i < categories.length; i++) {
var diaPositive = series['positive'][i]['y'];
urlPositive = series['positive'][i]['url'];
allPositiveData.push({'y':diaPositive, 'url':urlPositive}); // Add the element to the array.
}
That's all there is to it. Your Highcharts-setup piece of code can remain the same.
To make the sample a little shorter, I only edited the code for the positive data, the rest is the same, just a different name.

Categories