I'm preparing arrays with options for Tablesorter's filter_selectSource in Python/Django. Because initially, it worked very strange for me when I switched to server-side filtering. The dropdown options did show up only when I've type one symbol in one of the 'search' filters and then every time it showed select options available one step before. And then I've decided to try and make most of the work on server-side
players.views
def skaters_averages_json(request, page, sort_col, filt_col, rookie_filt):
start = utils.PAGE_SIZE_2*(page - 1)
end = start + utils.PAGE_SIZE_2
skaters = Skater.objects.select_related('team')
filtering = utils.filter_columns(filt_col)
if filtering:
skaters = utils.apply_filters(skaters, filtering)
if utils.rookie_filter(rookie_filt):
skaters = skaters.filter(rookie=True)
sorting = utils.sorting_columns(sort_col)
one_page_slice = utils.sort_table(sorting, skaters)[start:end]
skaters_json = json.loads(serializers.serialize('json', one_page_slice,
use_natural_foreign_keys=True))
domain = request.get_host()
total_rows = skaters.count()
data = utils.process_json(domain, skaters_json, total_rows, page)
data['filter_select'] = {
**utils.filter_select_opts(skaters, 3, 'position_abbr'),
**utils.filter_select_opts(skaters, 4, 'team__abbr'),
**utils.filter_select_opts(skaters, 5, 'nation_abbr'),
}
return JsonResponse(data, safe=False)
players.utils
def filter_select_opts(skaters_query, col_number, field_name):
uniques = list(skaters_query.values_list(field_name, flat=True).distinct())
return {col_number: sorted(uniques, key=lambda x: (x is None, x))}
So my JSONResponse looks like this.
Page 1
{
"total": 41,
"rows": [
[row 1],
...
[row 25]
],
"filter_select": {
"3": [
"C",
"D",
"LW",
"RW"
],
"4": [
"ANA",
"BOS",
"BUF",
"CAR",
"CBJ",
"CGY",
"CHI",
"COL",
"DAL",
"EDM",
"FLA",
"MIN",
"MTL",
"NJD",
"NSH",
"NYI",
"PHI",
"PIT",
"SJS",
"TOR",
"VAN",
"VGK",
"WPG"
],
"5": [
"FIN"
]
}
}
Page 2
{
"total": 41,
"rows": [
[row 26],
...
[row 41]
],
"filter_select": {
"3": [
"C",
"D",
"LW",
"RW"
],
"4": [
"ANA",
"BOS",
"BUF",
"CAR",
"CBJ",
"CGY",
"CHI",
"COL",
"DAL",
"EDM",
"FLA",
"MIN",
"MTL",
"NJD",
"NSH",
"NYI",
"PHI",
"PIT",
"SJS",
"TOR",
"VAN",
"VGK",
"WPG"
],
"5": [
"FIN"
]
}
}
The second page of the applied filter's response shows the same values for filter_select
But when I look at the same pages after it is processed by filter_selectSource
it shows only options available on the currently visible page.
Is there a way to override this behavior?
Read the docs for filter_selectSource and getOptions. Couldn't find what I want.
external.js
let selectOptions;
$("#tab1")
.tablesorter({
headers: {
0: {sorter: false, filter: false},
1: {filter: false},
2: {filter: false},
},
widgets: ['filter'],
widgetOptions : {
filter_selectSource: selectOptions,
filter_reset: '.reset',
filter_external : '.select',
}
})
.tablesorterPager({
container: $(".pager-s"),
size: 25,
output: '{page} / {totalPages}',
savePages: false,
fixedHeight: false,
ajaxUrl: 'http://127.0.0.1:8000/skaters_averages_json/{page+1}/{sort:col}/{filter:fcol}',
customAjaxUrl: function(table, url) {
if ($("#tab1").data('filter_value')) {
return url += '/rookie_filter=' + $("#tab1").data('filter_value');
};
return url += '/rookie_filter=';
},
ajaxObject: {
success: function(data) {
selectOptions = data['filter_select'];
$("#tab1").trigger("updateAll");
console.log(selectOptions);
},
dataType: 'json',
type: 'GET'
},
});
$('.rookie-filter').on('change', function(){
let checked = $(this).is(":checked");
$("#tab1").data('filter_value', checked).trigger('pagerUpdate', 1);
if (checked) {
$(this).attr('title', 'Show all players')
} else {
$(this).attr('title', 'Show rookies only')
};
});
$('.reset').on('click', function(){
$('.rookie-filter').prop('checked', false);
$("#tab1").data('filter_value', $(this).is(":checked")).trigger('pagerUpdate');
});
$('table').bind("sortEnd", function(){
$(this).trigger('pageSet', 1);
});
I'm also logging selectOptions
in my external .js file to make sure I'm getting what's expected.
UPDATE.
Have been trying to make it work with buildSelect
, as @Mottie suggested in the comment.
As it turned out, filter_selectSource
actualldidn't accept my variable in
filter_selectSource: selectOptions, the only line that makes a difference is
$("#tab1").trigger("updateAll")in
ajaxObject`. Tablesorter then automatically shows the options available on the current page.
So, now I'm struggling with triggering updates for a filter row.
I'm able to load expected options for one column at a time, but the table doesn't work because of the too much recursion
error.
$('body').trigger(filter_event)
in every filter_selectSource
causing enormous amount of updating a table. But without it options for dropdown menus are empty. I've tried to bind this update for different events. Like filterInit
. Didn't work for me.
Take a look at my updated script below. Any suggestions of how to make it update a filter row without repeating it multiple times?
$("#tab1").data('statType', 'tot');
let pos = [];
let team = [];
let nation = [];
let filter_event = jQuery.Event("load_filter_select");
$("#tab1")
.tablesorter({
headers: {
0: {sorter: false, filter: false},
1: {filter: false},
2: {filter: false},
},
widgets: ['filter'],
widgetOptions : {
filter_selectSource: {
3 : function(table, column, onlyAvail) {
result = pos;
$.tablesorter.filter.buildSelect(table, column, result, true);
$('body').trigger(event);
},
4 : function(table, column) {
result = team;
$.tablesorter.filter.buildSelect(table, column, result, true);
$('body').trigger(filter_event);
},
5 : function(table, column) {
result = nation;
$.tablesorter.filter.buildSelect(table, column, result, true);
$('body').trigger(filter_event);
},
},
filter_reset: '.reset',
filter_external : '.select',
}
})
.tablesorterPager({
container: $(".pager-s"),
size: 25,
output: '{page} / {totalPages}',
savePages: false,
fixedHeight: false,
ajaxUrl: 'http://127.0.0.1:8000/skaters_averages_json?/{page+1}/{sort:col}/{filter:fcol}',
customAjaxUrl: function(table, url) {
urlParts = url.split('?');
url = urlParts[0] + `/${$("#tab1").data('statType')}` + urlParts[1];
if ($("#tab1").data('filter_value')) {
return url += '/rookie_filter=' + $("#tab1").data('filter_value');
};
return url += '/rookie_filter=';
},
ajaxObject: {
success: function(data) {
pos = data['filter_select'][3];
team = data['filter_select'][4];
nation = data['filter_select'][5];
$('body').trigger(filter_event);
},
dataType: 'json',
type: 'GET'
},
});
$('body').on('load_filter_select', function(){
$("#tab1").trigger('update');
});
With the great help of Mottie I've been able to solve the problem.
I shouldn't have to actually trigger any updates. Just load the options from AJAX callback using buildSelect
function.
So, the full script that makes select filters work properly looks like this:
$("#tab1").data('statType', 'tot');
$("#tab1")
.tablesorter({
widgets: ['filter'],
widgetOptions: {
filter_selectSource : {
".filter-select" : function() { return null; }
},
}
})
.tablesorterPager({
container: $(".pager-s"),
size: 25,
output: '{page} / {totalPages}',
savePages: false,
fixedHeight: false,
ajaxUrl: 'http://127.0.0.1:8000/ajax_players?/{page+1}/{sort:col}/{filter:fcol}',
customAjaxUrl: function(table, url) {
urlParts = url.split('?');
url = urlParts[0] + `/${$("#tab1").data('statType')}` + urlParts[1];
if ($("#tab1").data('filter_value')) {
return url += '/rookie_filter=' + $("#tab1").data('filter_value');
};
return url += '/rookie_filter=';
},
ajaxObject: {
success: function(data) {
table = $("#tab1");
options = data['filter_select'];
for (let column = 3; column <= 5; column++) {
$.tablesorter.filter.buildSelect(table, column, options[column], true);
};
},
dataType: 'json',
type: 'GET'
},
});
UPDATE. I've actually just figured out why this solution was not working as expected in some cases. I forgot to add filter_selectSource : { ".filter-select" : function() { return null; } },
to the widget-options
. Without the function returning null filter_selectSource
was trying to override the options made by buildSelect
. I've edited the code.