I'm using tablesorter plugin for column sorting and dragtable plugin for rearranging the column order. Also, I'll update the table content using ajax on some button clicks.
So the problem here is, when I rearrange the column order using drag and drop and then update the using ajax, the column sorting is not updated according to the column order. That is, when we click on say Column1, the sorting is done based on data in Column2.
I'm using this piece of code for ajax update.
jQuery("#Table").trigger('updateRows');
I have also tried updating with
jQuery("#emailListTable").trigger('updateAll');
In this case, column drag and drop stopped working.
Any other way to tell tablesorter plugin that column order is changed?
Plugin details:
TableSorter : http://tablesorter.com/docs/
Dragtable : http://akottr.github.io/dragtable/
Update: version 2.19.0 has been released. It includes a modified version of the dragtable widget - see the demo here.
The updateRows and updateAll methods are not available in the original tablesorter plugin, but are part of my fork of tablesorter.
In order to get the dragtable widget to work with the fork of tablesorter, you'll need to trigger an updateAll event from within the dragtable persistState callback function. Try out this demo - I had to add some extra HTML & CSS to add a dragable handle so the demo is working 100% (I hope).
CSS
.table-handle {
background-image: url('data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAIAAAANAQMAAAC5Li2yAAAABlBMVEXd3d2ZmZl1DvVeAAAADklEQVQI12MAAQcsmAEAEFoBQSzdkZ8AAAAASUVORK5CYII=');
background-repeat: repeat-x;
height: 18px;
margin: 0 1px;
cursor: move;
}
HTML (single header cell example)
<th>
<div class="table-handle"></div>
<div class="sort">Header</div>
</th>
Script
$(function () {
var $table = $('table')
.dragtable({
dragHandle: '.handle',
persistState: function (table) {
// remove div wrapper, or swapped header
// contents will be replaced
$table.find('thead .tablesorter-header-inner').contents().unwrap();
$table.trigger('updateAll', false);
}
})
.tablesorter({
theme: 'blue',
selectorSort: '.sort',
widthFixed: true
});
});
Make sure to also include jQuery UI (jQuery UI css is optional) because it is a dependency of the dragtable widget.
Related
DataTables allows you to create "complex headers" (which entails spanning multiple columns or rows). The Responsive plugin is compatible with this if you add some extra CSS as per the documentation.
Here is a working fiddle: https://jsfiddle.net/hmr9qtx3/1/
As you can see, resizing the rendered output frame correctly removes the <th> tags from the row. This is with versions 1.10.1 of DataTables and 1.0.0 of Responsive.
The most current DataTables build is 1.10.12, and the version of Responsive it comes packaged with is 2.1.0. Here is an identical fiddle with those versions swapped out: https://jsfiddle.net/hmr9qtx3/
Between the working and non-working version numbers, usage of datatables and the responsive plugin is identical.
You will notice that the responsive plugin functions correctly for the non-spanning table headers and the body of the table. However, the spanning headers are not removed from the DOM when the page is resized enough that they would add a scrollbar/overflow.
How can I fix or patch my code so the spanning headers are responsive like in the working fiddle? I'd prefer to not use older versions of the plugins.
Complex headers are not supported with Responsive plug-in 2.0, see this thread or this issue #59.
As a workaround you can continue using Responsive plugin 1.0 with the most recent version of jQuery DataTables.
Per author's post:
Unfortunately yes, this is a limitation in Responsive 2.0. (...) The plan is to resolve it for 2.1. (...) The only option at the moment is to roll back to Responsive 1.x I'm afraid.
Although you're using v2.1.0, maybe it wasn't yet added because issue #59 on GitHub remains open.
I created a hot fix on the fly for this issue for the responsive plugin.
Issue: (last column disappears)
DataTables 1.10.13 hot-fix → datatables.responsive v2.1.1
Adds responsive support to datatables.net Complex Headers
This hot-fix works perfectly well on my page where I have different types of datatables,
but nevertheless, be careful with this patch as it is not tested with all possible dt features/types.
Here is a working demo: jsBin-Demo
_setColumnVis: function (col, showHide) {
var dt = this.s.dt;
var display = showHide ? '' : 'none'; // empty string will remove the attr
$(dt.column(col).header()).css('display', display);
$(dt.column(col).footer()).css('display', display);
dt.column(col).nodes().to$().css('display', display);
var parentrow = $(dt.column(col).header()).parent().prev("tr");
var visibleSiblingCount = $(dt.column(col).header()).siblings("th").filter(function (idx, el) {
return $(el).is(":visible");
}).length;
if (parentrow.length > 0 && visibleSiblingCount != 1) {
if (parentrow.find("th:nth-child(" + col + ")").attr("rowspan") == 1) {
parentrow.find("th:nth-child(" + col + ")").css('display', display);
} else {
parentrow.find("th:nth-child(" + (col + 1) + ")").css('display', display);
}
}
},
A clean solution which works well is to add a duplicate, empty row of zero-height columns before the complex header row, followed by the actual row of columns.
<thead>
<tr><th></th><th></th><th></th></tr>
<tr><th colspan="2">Complex!</th><th>yeah</th></tr>
<tr><th>One</th><th>Two</th><th>Three</th></tr>
</thead>
This is because FixedHeader targets the first row it finds in thead for sizing. If you size the dummy row correctly, all the others will follow.
I prefer this solution before there is an official fix because it doesn't require us to maintain a patched version of FixedHeader, and when an official fix is released would degrade gracefully and be removable at our leisure.
This function counts the number of visible columns. Then loops through the headers to make them match. I hope this helps as a patch for someone until Responsive is updated. You will have to put this inside of a document load and window resize function.
function makeColumnsResponsive() {
const visibleColumnCount = $('tbody tr:first-child td:visible').length - 1;
$('thead tr th').show();
for (let i = 1; i <= $('thead tr').length; i++) {
$('thead tr:nth-child(' + i + ') th:gt(' + visibleColumnCount + ')').hide();
}
}
I am trying to apply CSS to each of the DataTable cells based on its value, using the drawCallback().The CSS gets applied to a few cells and not all. Here is the JsFiddle to my issue.Has anyone come across this issue and found a solution or have any ideas on this.Please suggest!
"drawCallback": function( settings ) {
var api = this.api();
var visibleRows=api.rows( {page:'current'} ).data();
if(visibleRows.length >= 1){
for(var j=1;j<visibleRows[visibleRows.length -1].length;j++){
$("td:eq("+j+")", settings.nTBody.childNodes[visibleRows.length -1]).addClass(visibleRows[visibleRows.length -1][j]);
}
}
},
Like #charlietfl said, you don't really want to be using drawCallback to format the rows, and you would probably be better off using createdRow (rowCallback) to do this formatting.
drawCallback is called on every draw event, meaning that it's really meant to be used to update rows that have just been added or work with data that has just been updated.
createdRow on the other hand, is designed to be called whenever a row is created, which seems to be what you really want. In this documentation (example), you can see that the author displays how to use this option to add a class to certain rows, which seems to be the closest to what you want to do.
As far as I can tell, you want to make every cell have a CSS class that is the same as the text in the cell (correct me if I'm wrong). The easiest way to do that with createdRow would be as follows:
"createdRow": function ( row, data, index ) {
for(var i = 0;i<data.length;i++){
$('td', row).eq(i).addClass(data[i]);
//The above line assumes that you want to add a CSS class named "red" to a
//field that has the text "red" in it, if not, you can change the logic
}
}
Just include this in your initialization options for the .DataTables() call.
I had to make some assumptions about the exact logic for what classes get added to what columns, but if they are correct, then this should add a class to each field that is named the same as the text in that field.
Is there a way to put actual html code inside a title attribute on a table row element? My goal is to pop-up not only text but some info-graphics along with it, so a mouseover event thats not a modal would be great. Am I going in the wrong direction?
This table is already using jquery datatables but I don't believe it can do that sort of event.
<tr title='This activity will be open to registration on April 31st' >
.....
</tr>
Nope. You'd need to create your own title substitute with JavaScript.
No.
HTML can't be placed in an attribute.
If the goal is to have a pop-up with rich content, then you need to handle this via javascript. In addition, from an accessibility standpoint, you likely don't want to put that amount of content into the title attribute anyways, so going the JS route is going to solve a few problems for you. Google 'JS Tooltip' for dozens of options.
Native tooltips do not use HTML. jQuery UI tooltips would be very useful here.
Demo: http://jqueryui.com/tooltip/
EDIT: You would need to use the content option to use markup instead of the title attribute.
$(".text")
.tooltip({ content: '<b style="color: red">Tooltip</b> <i>text</i>' });
Here's a Fiddle demonstrating this: http://jsfiddle.net/acbabis/64Q2m/
You can use jquery ui tooltip plugin for showing custom title
There is no direct way to render HTML code written inside a tooltip. However, if you are using jQueryUI (or, if you can) then the code below will show the HTML effect (render) and not the HTML code in the tooltip.
Requirements: jQuery, jQueryUI.js, jQueryUI.css
HTML Code:
<tr data-title='This activity will be open to registration on <b>April 31st</b>'>.....</tr>
JavaScript:
$(function() {
$( document ).tooltip({
items: '[title], [data-title]',
track:true,
content: function(){
var element = $( this );
if ( element.is( "[title]" ) ) {
return element.attr( "title" );
}
if ( element.is( "[data-title]" ) ) {
return element.attr( "data-title" );
}
}
});
});
Instead of focusing on the title attribute, enclose a popup message with tags inside the target <td></td> (table data element). Then put a class in that td to control the div with CSS, hiding it first, then making its contents visible when the mouse hovers over the specific table data element. Something like this:
<tr><td class="info-tooltip">This activity will be open to registration on April 31st <div>[ *the contents you would want to popup here* ]</div></td></tr>
Your CSS then might be something like:
td.info-tooltip div {
display:none;
}
td.info-tooltip:hover {
position:relative;
cursor:pointer;
}
td.info-tooltip:hover div {
position:absolute; /* this will let you align the popup with flexibility */
top: 0px; /* change this depending on how far from the top you want it to align */
left: 0px; /* change this depending on how far from the left you want it align */
display:block;
width: 500px; /* give this your own width */
}
Using Bootstrap Tooltips one can do the following
<span title="Some <b>bold words</b>" data-toggle='tooltip' data-html='true'>this has an html supported tooltip</span>
for me it happens automatically but you might need to trigger it with javascript.
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
docs
I think a good option is to put that content inside a data attribute, something like this:
<div data-tooltip="Some information I want to show">
Actual content
</div>
And then, write a simple jQuery plugin that shows that content inside an element upon hover over.
Is it possible to select the text (i.e. have it highlighted so that it can be copy+pasted) of every cell in one vertical column of an HTML table.
Is there a JavaScript method, or perhaps an equivalent in some browsers to the Alt-Click-Drag shortcut used in many text editors?
Or is this impossible?
What you're looking for is called Range object (TextRange in IE).
Update:
Here's a working code to do what you're suggesting: http://jsfiddle.net/4BwGG/3/
While capturing cell contents, you can format them in any manner you wish. I'm simply appending a new line every time.
Note:
Works fine in FF 3 and above
IE (before 9) and Chrome do not support multiple selection.
Chrome doesn't highlight all cells (but captures all content). Same goes for IE9
IE 7 & 8 will throw an error.
An alternative is apply a CSS style that simulates highlighting on click of column header and loop through all cells to capture their content. Look and feel of this approach may differ from native selection's look (unless you somehow capture select event and alter the appearnce).
Then use jQuery copy plugin to copy them to clipboard.
Some code review tools implement this to allow copying & pasting code from one side of a side-by-side diff. I looked into how ReviewBoard pulls it off.
The gist is:
When a column selection begins, style the cells in all other columns with user-select: none (and its prefixed variants, if necessary). This creates the appearance of a column selection. The other columns are still secretly selected, so you have to...
Intercept the copy event and change its payload to reflect the contents of the selected column.
The ReviewBoard code to do this consists of this CSS and this JavaScript.
I pulled it out into a fairly minimal jsbin demo.
Here's the CSS to create the appearance of a single-column selection (you add the selecting-left class to the table when the left column is being selected, or selecting-right for the right):
.selecting-left td.right,
.selecting-left td.right *,
.selecting-right td.left,
.selecting-right td.left *,
user-select: none;
}
.selecting-left td.right::selection,
.selecting-left td.right *::selection,
.selecting-right td.left::selection,
.selecting-right td.left *::selection,
background: transparent;
}
Here's the JavaScript to intercept the copy event and plug in a single column's worth of data:
tableEl.addEventListener('copy', function(e) {
var clipboardData = e.clipboardData;
var text = getSelectedText();
clipboardData.setData('text', text);
e.preventDefault();
});
function getSelectedText() {
var sel = window.getSelection(),
range = sel.getRangeAt(0),
doc = range.cloneContents(),
nodes = doc.querySelectorAll('tr'),
text = '';
var idx = selectedColumnIdx; // 0 for left, 1 for right
if (nodes.length === 0) {
text = doc.textContent;
} else {
[].forEach.call(nodes, function(tr, i) {
var td = tr.cells[tr.cells.length == 1 ? 0 : idx];
text += (i ? '\n' : '') + td.textContent;
});
}
return text;
}
There's also some less interesting code to add the selecting-left and selecting-right classes at the start of a selection. This would require a bit more work to generalize to n-column tables.
This seems to work well in practice, but it's surprising how hard it is!
Here is a hack that doesn't involve javascript at all:
Step 1: open the inspector
For Chrome on mac, press command + option + J.
Step 2: select a random cell using the selector tool
For Chrome on mac, click the selector icon on the top left corner of the inspector to enter the selector mode.
Then click a random cell in the table.
Step 3: hide all cells by editing CSS
Click the New Style Rule button (see image below)
then enter this rule (you may want to modify it a little bit depending on your HTML)
tr td {
display: none; # hide all cells
}
Now all cells should have disappeared.
Step 4: display only the column that you want by editing CSS
Go ahead and add another rule above that one:
tr td:nth-child(2) { # replace 2 with the index of the column you want to copy. 2 means the second column
display: table-cell; # display that column
}
Now the column you want to copy from should have reappeared.
All the other columns should be invisible and can't be selected.
Step 5: just copy that column!
Note
You can restore the page by refreshing.
I find this work perfectly if you just want to select one column or two.
You could have a div which gets populated with the column data on click and apply a css class to give the columns the appearence of being selected
something like this:
var $mytable = $("#mytable"),
$copydiv = $("#copy_div");
$mytable.find("td").click(function(){
//get the column index
var $this = $(this),
index = $this.parent().children().index($this);
//find all cells in the same column
$mytable.find("tr:nth-child(" + index + ")").removeClass("selected").each(function () {
var $this = $(this);
$this.addClass("selected");
$copydiv.html($this.html() + "<br />");
});
});
or you could have a separate table for each column, but I don't think that would be worth it.
WIP: CSS only solution using :has() selector
The new :has() selector gave me hope in solving this issue without JS. The idea was to disable text selection for all cells, and only activate it for cells of a column that is hovered.
So you would have rule like this:
table:has(tr td:nth-child(1):hover) tr td:nth-child(1) {
-webkit-user-select: auto;
user-select: auto;
}
A complete sample can be found here: https://codepen.io/catlan/pen/XWELegW
This is work in progress, because in the current version of Safari (15.6.1), the display of the text range disappears after the selection is done, only to reappear after moving the cursor for a few pixel. See https://bugs.webkit.org/show_bug.cgi?id=244445
It seems to work fine in Chrome starting with Version 105.
When I use $('#mygrid').jqGrid('GridUnload'); my grid is destroyed: no pager/ no header.
In a wiki I found:
The only difference to previous method is that the grid is destroyed, but the
table element and pager (if any) are left ready to be used again.
I can't find any difference between GridUnload/ GridDestroy or do I something wrong?
I use jqGrid 3.8.
To be able to create jqGrid on the page you have to insert an empty <table> element on the place of the page where you want see the grid. The simplest example of the table element is <table id="mygrid"></table>.
The empty <table> element itself will be not seen on the page till you call $('#mygrid').jqGrid({...}) and the grid elements like column headers will be created.
The method GridDestroy works like jQuery.remove. It deletes all elements which belong to the grid inclusve the <table> element.
The method GridUnload on the other hand delete all, but the empty <table> element stay on the page. So you are able to create new grid on the same place. The method GridUnload is very usefull if you need create on one place different grids depend on different conditions. Look at the old answer with the demo. The demo shows how two different grids can by dynamically created on the same place. If you would be just replace GridUnload in the code to GridDestroy the demo will be not work: after destroying of the first grid no other grids will be created on the same place.
In addition to Oleg's answer I would like to point out that GridUnload does a little more that just remove the grid from the table. It removes the original HTML table element(and the pager), and ads an identical one in its place(at least in 4.5.4 it does).
This means that if you attached some event handlers to the table HTML element(i.e with jquery on, like ('#gridID').on('event','selector',handler)) they will also be removed. Consiquently the events will not fire on the new grid if you replace the old grid with a new one...
Oleg's answer works fine for me as long as I have no Group headers.
When I add group header row with 'setGroupHeaders'
the results of a 'GridUnload' followed by a $('#mygrid').jqGrid({...}) are not consistent.
It works fine in Chrome but not in IE11.
In IE11, each 'jqg-third-row-header' item ends up rendered on different rows (diagonally).
I am using free-jqGrid:query.jqgrid.src.js version 4.13.4 for debugging.
I traced the problem down to code, in this file, that begins with line 9936:
if (o.useColSpanStyle) {
// Increase the height of resizing span of visible headers
$htable.find("span.ui-jqgrid-resize").each(function () {
var $parent = $(this).parent();
if ($parent.is(":visible")) {
this.style.cssText = "height:" + $parent.height() + "px !important; cursor:col-resize;";
//this.style.cssText = "height:" + $parent.css('line-height'); + "px !important;cursor:col-resize;";
}
});
// Set position of the sortable div (the main lable)
// with the column header text to the middle of the cell.
// One should not do this for hidden headers.
$htable.find(".ui-th-column>div").each(function () {
var $ts = $(this), $parent = $ts.parent();
if ($parent.is(":visible") && $parent.is(":has(span.ui-jqgrid-resize)") && !($ts.hasClass("ui-jqgrid-rotate") || $ts.hasClass("ui-jqgrid-rotateOldIE"))) {
// !!! it seems be wrong now
$ts.css("top", ($parent.height() - $ts.outerHeight(true)) / 2 + "px");
// $ts.css("top", ($parent.css('line-height') - $ts.css('line-height')) / 2 + "px");
}
});
}
$(ts).triggerHandler("jqGridAfterSetGroupHeaders");
});
This code sets the height and top css values related to each 'jqg-third-row-header' item. This leads to a tall and diagonal layout of the 'jqg-third-row-header'
Potential Bug:.
The $parent.height() and $ts.height() methods, above, return the former jqGrid table height in IE11. In Chrome they return the 'th' computed height(top = 0).
I added and tested the 2 commented lines that use line-height.
IE11 works fine when line-height is used.
I do not completely understand the JqGrid resize logic, so this may not be a fix.
Alternate Solution:
If you specify.
colModel:
{
label: 'D',
name: 'W',
width: 6,
align: 'center',
resizable:false //required for IE11 multiple calls to this init()
},
When resizable is false the code above is not encountered and the height and top are not set.
Oleg's jqGrid is a very nice control. Perhaps he can test his demo grid with a groupheader on IE11.