Strange behavoir in tablesorter:
$(document).ready(function() {
var timeInSec = function(node) { return $(node).attr("time") };
var sortDigit = { sorter : "digit" };
$("#agentRapport").tablesorter({
headers: {
1: sortDigit,
2: sortDigit
},
textExtraction: {
1: timeInSec,
2: timeInSec
},
sortList: [[1,1]]
});
});
Resulting sorted table looks like this (sorted the following myself like you would see it in the browser):
...
<tr>
<td>Banana</td>
<td time="1411252">23 min 31 sec</td>
<td time="352813">5 min 52 sec</td>
<td>4</td>
<td>225</td>
</tr>
<tr>
<td>Apple</td>
<td time="1391952">23 min 11 sec</td>
<td time="347988">5 min 47 sec</td>
<td>4</td>
<td>86</td>
</tr>
<tr>
<td>Plum</td>
<td time="1427192">23 min 47 sec</td>
<td time="356798">5 min 56 sec</td>
<td>4</td>
<td>119</td>
</tr>
<tr>
<td>Pear</td>
<td time="1381072">23 min 1 sec</td>
<td time="345268">5 min 45 sec</td>
<td>4</td>
<td>108</td>
</tr>
...
Is this a bug?
If you are using the original tablesorter (v2.0.5b) from tablesorter.com, the above code will not work since the textExtraction function will not allow the targetting of specific columns.
However, you can use my fork of tablesorter which does allow this option (demo)
The fork should work without any extra functions since it also has an alpha-numeric sort (demo):
$(function () {
$("#agentRapport").tablesorter({
sortList: [
[1, 1]
]
});
});
But if you want to continue using the original version of tablesorter, you can use a parser, and save yourself from adding a time="#" attribute to every cell (demo):
$.tablesorter.addParser({
id: "min&sec",
is: function (s) {
return false;
},
format: function (s, table) {
var min = (parseInt(s.match(/(\d+)(?:\s+min)/), 10) || 0) * 60,
sec = (parseInt(s.match(/(\d+)(?:\s+sec)/), 10) || 0);
return min + sec;
},
type: "numeric"
});
$(function () {
$("#agentRapport").tablesorter({
headers: {
1: { sorter: "min&sec" },
2: { sorter: "min&sec" }
},
sortList: [
[1, 1]
]
});
});
Related
I am using tablesorter on a page where I display products. One of the column is the status displayed in a drop-down. When I click a product I can edit it and change its status. When I go back to the product list, the list is still filtered which is OK but if the product was the last with this status, the product list is, of course, empty and the drop-down shows no selection because the status does not exist anymore. I want to clear the filter on the drop-down when I get back on the product list page and there is no more product with this status. How can I achieve this?
My code:
<table cellspacing="1" cellpadding="1" id="ProductTable" class="tablesorter">
<thead>
<tr>
<th class="reorder-false reorder-block-left">Product Name</th>
<th class="">Description</th>
<th class="filter-select filter-onlyAvail">Status</th>
<th class="">Status Change Date</th>
</tr>
</thead>
<tbody id="ProductList">
<tr class="ProductRows" id="P_1906">
<td ><a style="text-decoration:none" href="/Product/Index/1906">Product #1</a></td>
<td><span class="">Test #1</span></td>
<td><span class="">Active</span></td>
<td><span class="time">2015/07/13 16:41:03</span></td>
</tr>
<tr class="ProductRows" id="P_1993">
<td ><a style="text-decoration:none" href="/Product/Index/1993">Test #2</a></td>
<td><span class="">Test #2</span></td>
<td><span class="">Backorder</span></td>
<td><span class="time">2015/08/25 10:39:23</span></td>
</tr>
</tbody>
</table>
$("#ProductTable").tablesorter({
theme: 'blue',
widthFixed: false,
widgets: [ "zebra", "filter", "resizable" ],
widgetOptions: {
filter_childRows: false,
filter_columnFilters: true,
filter_filteredRow: 'filtered',
filter_hideFilters: true,
filter_ignoreCase: true,
filter_liveSearch: true,
filter_onlyAvail: 'filter-onlyAvail',
filter_reset: 'button.reset',
filter_saveFilters: true,
filter_searchDelay: 300,
filter_serversideFiltering: false,
filter_startsWith: false,
filter_useParsedData: false,
filter_defaultAttrib: 'data-value'
}
});
Thank you.
I made it using the following code:
var FilterCallOnce = false;
$("#ProductTable").tablesorter({
theme: 'blue',
widthFixed: false,
widgets: [ "zebra", "filter", "resizable" ],
widgetOptions: {
filter_childRows: false,
filter_columnFilters: true,
filter_filteredRow: 'filtered',
filter_hideFilters: true,
filter_ignoreCase: true,
filter_liveSearch: true,
filter_onlyAvail: 'filter-onlyAvail',
filter_reset: 'button.reset',
filter_saveFilters: true,
filter_searchDelay: 300,
filter_serversideFiltering: false,
filter_startsWith: false,
filter_useParsedData: false,
filter_defaultAttrib: 'data-value'
}
}).bind('filterEnd', function(e, filter){
var table = document.getElementById("ProductList");
var nrow = 0;
var nfiltered = 0;
if (FilterCallOnce == false)
{
FilterCallOnce = true;
if (table != null)
{
for (var i = 0, row; row = table.rows[i]; i++)
{
nrow++;
if (!hasClass(row, 'filtered'))
nfiltered++;
}
if ((nrow > 0) && (nfiltered == 0))
$('#ProductTable').trigger('filterReset');
}
}
});
function hasClass(element, cls)
{
return (' ' + element.className + ' ').indexOf(' ' + cls + ' ') > -1;
}
I use jQueryUI resizable for resizing columns in the table.
When you change the width of one column, the other columns must remain unchanged. Should only varies the width of the table
Why do you change the width of one column, while others twitch? If all columns to make the minimum width, and then begin to increase the width of the last column (right column), all the others begin to twitch.
<div class="table-wrapper">
<table id="table">
<tbody>
<tr>
<td class="gray">Zagreb</td>
<td class="gray">Kinshasa</td>
<td class="gray">Kishinev</td>
<td class="gray">Krakow</td>
<td class="gray">Lima</td>
<td class="gray">Lisbon</td>
</tr>
<tr>
<td>Zagreb</td>
<td>Kinshasa</td>
<td>Kishinev</td>
<td>Krakow</td>
<td>Lima</td>
<td>Lisbon</td>
</tr>
<tr>
<td>Zagreb</td>
<td>Kinshasa</td>
<td>Kishinev</td>
<td>Krakow</td>
<td>Lima</td>
<td>Lisbon</td>
</tr>
<tr>
<td>Zagreb</td>
<td>Kinshasa</td>
<td>Kishinev</td>
<td>Krakow</td>
<td>Lima</td>
<td>Lisbon</td>
</tr>
<tr>
<td>London</td>
<td>Los Angeles</td>
<td>Luxembourg</td>
<td>Madrid</td>
<td>Manila</td>
<td>Mexico</td>
</tr>
<tr>
<td>Milan</td>
<td>Montreal</td>
<td>Mumbai</td>
<td>Nairobi</td>
<td>Nicosia</td>
<td>New York</td>
</tr>
<tr>
<td>Osaka</td>
<td>Oslo</td>
<td>Ottawa</td>
<td>Paris</td>
<td>Prague</td>
<td>Riga</td>
</tr>
<tr>
<td>Rome</td>
<td>Rotterdam</td>
<td>Salvador</td>
<td>Samarkand</td>
<td>Sydney</td>
<td>Singapore</td>
</tr>
<tr>
<td>Sofia</td>
<td>Istanbul</td>
<td>Taipei</td>
<td>Tbilisi</td>
<td>Zurich</td>
<td>Chicago</td>
</tr>
</tbody>
</table>
</div>
JS code:
$(document).ready(function(e)
{
var $table = $("#table");
var startW = 0;
var startW_neighbor = 0;
var td_index_neighbor = 0;
var ui_minColWidth = 0;
$table.find("tr:first th, tr:first td").resizable(
{
handles: 'e',
minWidth: 30,
start : function(event,ui)
{
ui_minColWidth = $(this).resizable( "option", "minWidth");
startW = $(this).width();
startW_neighbor = ui.element.parents('.table-wrapper').find('table#table').width();
},
stop: function (event, ui)
{
},
resize: function (event, ui)
{
var td_width = ui.element.width();
var table = ui.element.parents('.table-wrapper').find('table#table');
var d = Number(td_width) - Number(ui.originalSize.width);
td_width = Number(startW_neighbor) + Number(d) - 2;
table.width(td_width);
}
});
});
In example: http://jsfiddle.net/djmartini/08p3Lqcm/
You're at the same time interfering with normal behavior of resizable and relying on it to calculate some values, so at some point it breaks. In that case you're calculating the width difference based on resizable behavior and applying it to change the behavior. But when resizable doesn't have any more place to resize, the difference is 0, hence nothing moves anymore.
If you want to change the behavior of resizable, you could work with mouse position and micro manage the change in width. In that case you'll need to handle cases where your mouse moves, but nothing gets resized. This happens when columns cannot reduce size anymore.
Something like this seems to work:
$table.find("tr:first th, tr:first td").resizable({
handles: 'e',
minWidth: 30,
start: function (event, ui) {
ui_minColWidth = $(this).resizable("option", "minWidth");
startW = $(this).width();
td_width = startW;//this to check if width can change
startW_neighbor = ui.element.parents('.table-wrapper').find('table#table').width();
startMouseX = event.pageX;//to calculate mouse diffrence
},
stop: function (event, ui) {
},
resize: function (event, ui) {;
var mouseDiff = event.pageX - startMouseX;//mouse mouvement
//If the mouse is negative, it can resize only if column
//isn't at min width. When mouse positive resize always.
if (ui.element.width() != td_width || mouseDiff > 0) {
td_width = ui.element.width();
var table = ui.element.parents('.table-wrapper').find('table#table');
var d = Number(td_width) - Number(ui.originalSize.width);
table_width = Number(startW_neighbor) + mouseDiff;
table.width(table_width);
}
}
});
fiddle: http://jsfiddle.net/wLbn5qjk/1/
EDIT:
You can push this logic and work with alsoResize, the result is a bit better:
$table.find("tr:first th, tr:first td").resizable({
handles: 'e',
minWidth: 30,
alsoResize: 'table',
start: function (event, ui) {
ui_minColWidth = $(this).resizable("option", "minWidth");
startW = $(this).width();
td_width = ui.element.width();
startW_neighbor = ui.element.parents('.table-wrapper').find('table#table').width();
startMouseX = event.pageX;
},
stop: function (event, ui) {
},
resize: function (event, ui) {;
var mouseDiff = event.pageX - startMouseX;
if (ui.element.width() == td_width && mouseDiff < 0) {;
$('table').width(table_width);
}
td_width = ui.element.width();
table_width = $('table').width()
}
});
http://jsfiddle.net/hrzj68wp/1/
I thought this would be relatively simple but I cannot find an answer anywhere, I have had a good hunt in Stack Overflow. Apologies if I have missed something obvious!
How on earth do you set the table width in jsPDF? I simply want my table to stretch to 100% of the page width. Here is the code I am using:
<script>
function demoFromHTML() {
var pdf = new jsPDF('p', 'pt', 'a4');
source = $('#pdf')[0];
specialElementHandlers = {
'#bypassme': function (element, renderer) {
return true
}
};
margins = {
top: 80,
bottom: 40,
left: 40,
width: 600
};
pdf.fromHTML(
source,
margins.left,
margins.top, {
'width': margins.width,
'elementHandlers': specialElementHandlers
},
function (dispose) {
pdf.output('datauri');
}, margins);
}
</script>
I am using standard, unformatted HTML table.
<table class="reference">
<tbody><tr>
<th>Firstname</th>
<th>Lastname</th>
<th>Points</th>
</tr>
<tr>
<td>Jill</td>
<td>Smith</td>
<td>50</td>
</tr>
<tr>
<td>Eve</td>
<td>Jackson</td>
<td>94</td>
</tr>
<tr>
<td>John</td>
<td>Doe</td>
<td>80</td>
</tr>
<tr>
<td>Adam</td>
<td>Johnson</td>
<td>67</td>
</tr>
</tbody></table>
The table only stretches about 60% across the PDF, which just seems to be a random amount. In addition the text inside the table is a different font and size to the text outside the table.
Any help would be greatly appreciated.
I have a MVC site where I use Kendo UI and knockout.js to display the pages. One scenario is to get the database information from server through $.getJSON and then display this information on a KendoUI grid.
<div data-bind="kendoGrid:{sortable:true, data:users, rowTemplate:'userRowTemplate'}>
<table>
<thead>
<tr>
<th>Username</th>
<th>First Name</th>
<th>Last Name</th>
</tr>
</thead> </table>
</div>
<script type="text/html">
<tr>
<td data-bind="text: Username"></td>
<td data-bind="text: FirstName"></td>
<td data-bind="text: LastName"></td>
<tr>
</script>
and the javascript :
<script type="text/javascript">
var ViewModel = function () {
var self=this;
self.users=ko.mapping.fromJS([]);
$getJSON("/UserManagementController/GetUsers",function(data){
ko.mapping.fromJS(data,{},self.users);
});
};
$(document).ready(function(){
var newViewModel=new ViewModel();
ko.applyBindings(newViewModel);
});
</script>
I want this data to be sortable on specific columns (the ones specified here are for example's sake), but I haven't been able to achieve this successfully. I have tried the solution from this knockout-kendo plugin issue post, which works well on simple objects, but does not work on observables. So my question is : how to map data from database through MVC controller to observables in knockout and displaying them in a Kendo grid, but still be able to sort them?
Thanks,
Alex Barac
You can do this by creating a JS view model to represent the data coming back from the server, and mapping the data into the view model. Then you can have simple objects that get set by subscribing to the matching observable properties to implement the sort.
Here is an example:
http://jsfiddle.net/R4Jys/1/
HTML:
<div data-bind="kendoGrid: gridOptions(myList)"></div>
<script id="rowTmpl" type="text/html">
<tr>
<td>
<span data-bind="text: firstName" />
</td>
<td>
<span data-bind="text: lastName" />
</td>
<td>
<span data-bind="text: userName" />
</td>
</tr>
</script>
JavaScript:
var gridItem = function () {
var self = this;
self.firstName = ko.observable();
self.firstNameSort;
self.firstName.subscribe(function (value) {
self.firstNameSort = value;
});
self.lastName = ko.observable();
self.lastNameSort;
self.lastName.subscribe(function (value) {
self.lastNameSort = value;
});
self.userName = ko.observable();
self.userNameSort;
self.userName.subscribe(function (value) {
self.userNameSort = value;
});
self.other = ko.observable('test');
return self;
};
var vm = function() {
var self = this;
self.myList = ko.observableArray();
self.test = ko.observable();
self.gridOptions = function (data) {
return {
data: data,
rowTemplate: 'rowTmpl',
useKOTemplates: true,
scrollable: true,
sortable: true,
columns: [
{
field: "firstNameSort",
title: "First Name",
width: 130
},
{
field: "lastNameSort",
title: "Last Name",
filterable: true
},
{
field: "userNameSort",
title: "Username"
}
]
}
};
var data = [{'firstName':'Steve', 'lastName':'Jones', 'userName': 'steve.jones'},
{'firstName':'Janet', 'lastName':'Smith', 'userName': 'janet.smith'},
{'firstName':'April', 'lastName':'Baker', 'userName': 'april.baker'},
{'firstName':'Dave', 'lastName':'Lee', 'userName': 'dave.lee'},
{'firstName':'Jack', 'lastName':'Bennet', 'userName': 'jack.bennet'},
{'firstName':'Chris', 'lastName':'Carter', 'userName': 'chris.carter'}];
self.myList(ko.utils.arrayMap(data, function(item) {
var g = new gridItem();
ko.mapping.fromJS(item, {}, g);
return g;
}));
};
var pageVm = new vm();
ko.applyBindings(pageVm);
As an alternative you can modify the kendo.web.js (version 2013.3.1119) on line 6844 & 6844.
Replace
compare: function(field) {
var selector = this.selector(field);
return function (a, b) {
a = selector(a);
b = selector(b);
With
compare: function(field) {
var selector = this.selector(field);
return function (a, b) {
a = ko.utils.unwrapObservable(selector(a));
b = ko.utils.unwrapObservable(selector(b));
By using the knockout utility "ko.utils.unwrapObservable" you can get the value of the observable for use when kendo compares the values of the column
DEFINE CUSTOM COMPARE FUNCTION will serves solution, where you can override the comparing function in field definition.
So you can do something like this:
$("#grid").kendoGrid({
dataSource: dataSource,
sortable: true,
columns: [{
field: "item",
sortable: {
compare: function(a, b) {
var valueA = a["item"];
var valueB = b["item"];
if (typeof valueA === "function") valueA = valueA();
if (typeof valueB === "function") valueB = valueB();
if (this.isNumeric(valueA) && this.isNumeric(valueB))
{
valueA = parseFloat(valueA);
valueB = parseFloat(valueB);
}
if (valueA && valueA.getTime && valueB && valueB.getTime)
{
valueA = valueA.getTime();
valueB = valueB.getTime();
}
if (valueA === valueB)
{
return a.__position - b.__position;
}
if (valueA == null)
{
return -1;
}
if (valueB == null)
{
return 1;
}
if (valueA.localeCompare)
{
return valueA.localeCompare(valueB);
}
return valueA > valueB ? 1 : -1;
}
}
}]
});
private isNumeric(input)
{
return (input - 0) == input && ('' + input).trim().length > 0;
}
The compare method is stolen from kendo script, but change is where the property is typeof function (what ko.observable is), it unwraps the value. Plus, I added support for numbers.
I'm trying to make a custom binding based on this code, http://jsfiddle.net/rniemeyer/WpnTU/ , Mine after a field checkbox is selected then open a jQueryUI dialog. Here is the code: http://jsfiddle.net/superjohn_2006/UFEg6/ , another question is it posible to acomplish this without a template.
<table>
<tbody data-bind="foreach: records">
<tr data-bind="foreach: fields">
<th align="left">
<input type="checkbox" data-bind="checked: chkedValue" /><span data-bind=" text: field"></span>
</th>
</tr>
<tr data-bind="foreach: fields">
<th align="left"><a data-bind="click: $root.addFormatting" href="#">Add Formatting</a></th>
</tr>
<tr data-bind="foreach: row">
<td data-bind="text: value"></td>
</tr>
</tbody>
</table>
<div id="details" data-bind="jqDialog: { autoOpen: false, resizable: false, modal: true }, template: { name: 'editTmpl', data: selectedField }, openDialog: selectedField">
</div>
<script id="editTmpl" type="text/html">
<p>
<label>Selected Field: </label>
<span data-bind="value: field" />
</p>
<button data-bind="jqButton: {}, click: $root.accept">Accept</button>
<button data-bind="jqButton: {}, click: $root.cancel">Cancel</button>
</script>
**The model
// custom binding
ko.bindingHandlers.jqDialog = {
init: function(element, valueAccessor) {
var options = ko.utils.unwrapObservable(valueAccessor()) || {}; // initialize a jQuery UI dialog
$(element).dialog(options);
// handle disposal
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).dialog("destroy");
});
}
};
//custom binding handler that opens/closes the dialog
ko.bindingHandlers.openDialog = {
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
if (value) {
$(element).dialog("open");
} else {
$(element).dialog("close");
}
}
};
//custom binding to initialize a jQuery UI button
ko.bindingHandlers.jqButton = {
init: function(element, valueAccessor) {
var options = ko.utils.unwrapObservable(valueAccessor()) || {};
//handle disposal
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).button("destroy");
});
$(element).button(options);
}
};
var resultsData = [
{ fields: [{ field: "Field1", chkedValue: false }, { field: "Field2", chkedValue: false }] },
{ row: [{ value: "1" }, { value: "True" }] },
{ row: [{ value: "2" }, { value: "False" }] }
];
var TableModel = function (records) {
var self = this;
self.records = ko.observableArray(ko.utils.arrayMap(records, function (record) {
return { fields: ko.observableArray(record.fields), row: ko.observableArray(record.row) };
}));
self.selectedField = ko.observable();
self.addFormatting = function (formatToAdd) {
self.selectedField();
};
};
this.accept = function() {
},
this.cancel = function() {
}
ko.applyBindings(new TableModel(resultsData));
the following couple of lines need to be changed.
span data-bind="value: field"
for:
span data-bind="text: $data.field"
and,
self.selectedField();
for:
self.selectedField(formatToAdd);
modified code is in the same jsFiddle, jus add: /1/
to the end of the url address.