Nie wszystko otrzymujemy w pakiecie, inaczej nie nazywalibyśmy się programistami. Nieprawdaż? Takie wyzwanie stanęło niedawno przede mną podczas „chwilowych” prac z biblioteką jQuery EasyUI. Zadanie było proste – grid. Grid z danymi, grid z filtrami. Wszystko pięknie. Sam grid jest wsparty natywnie biblioteką, filtry obsługiwane są przez rozszerzenie. Filtr daty również istnieje. Ale, no właśnie – filtrowanie daty po przedziale to już miejsce na popis programistów. Nawet nie w całości, jeśli dokładnie śledzimy sieć. Zachęcam to lektury.
O datagridzie przeczytacie tutaj. O rozszerzeniach – filtrowaniu grida – dowiecie się więcej z tej strony. Zmierzamy w dobrym kierunku. Przyglądając się domyślnym operatorom filtrowania, można wywnioskować, że działają one typowo wspierając treść (String) lub liczby (Number). Dociekliwych odsyłam do źródła otwartego kodu – tutaj.
Zatem możemy dodać filtr daty, ale wyłącznie porównując wartość komórki do jednej wskazanej wartości podanej w filtrze. To nas nie interesuje, więc musimy coś dodać. Najpierw chwila poszukiwań rozwiązań po sieci i … mamy jeden traf. Jest rozwiązanie, ale jednak niekompletne. To nie problem. W tym artykule sprawimy, by stało się kompletne.
Wspomniane rozwiązanie pochodzi z posta na stronie forum jeasyui. Nie czytając przedstawionego kodu, można się spodziewać iż zadziała on tak, jak chcieliśmy. Niemałe musi być zdziwienie osób, które tak podeszły do problemu 🙂
Brak reguły – operator
Brak reguły operatora to największy problem w tym momencie. Wyzwanie … czyli to co lubimy 🙂 Kod operatora jest niezbędny. Wnioskując z operatorów domyślnych, musi on posiadać właściwość text określającą nazwę operatora, oraz isMatch określającą funkcję dopasowania.
$.extend($.fn.datagrid.defaults.operators, {
inrange: {
text: 'In range',
isMatch: function(source, value) {
if(value == ':' ) {
return true;
}
if(value != ':' && (source == undefined || source == '')) {
return false;
}
var date = value.split(':' );
var sourcedate = new Date(source);
if(date[0] == '' && date[1] != '') {
var enddate = new Date(date[1]);
return sourcedate <= enddate;
}
if(date[1] == '' && date[0] != '') {
var startdate = new Date(date[0]);
return sourcedate >= startdate;
}
var startdate = new Date(date[0]);
var enddate = new Date(date[1]);
return (sourcedate >= startdate && sourcedate <= enddate);
}
}
});
Jeśli wartość filtra jest dwukropkiem (brak przedziału) uznajemy źródło jako dopasowane i wiersz przechodzi do wyświetlenia (jeśli pozostałe filtry przeszły pomyślnie). Gdy przedział jest podany (nawet jednostronnie), a źródło jest puste odrzucamy wiersz nie przechodząc do algorytmu (działamy tam na określonych wartościach źródła, które nie może być nieokreślone).
Następnie wykonujemy operacje na datach, po kolei: dla przedziału domkniętego z prawej strony, z lewej strony, obustronnego w zależności od tego, jaki przedział jest podany w filtrze.
Operator jest kompletny, ale to jeszcze nie rozwiązanie.
Inicjalizacja filtra
O ile w tym momencie mamy już działający filtr o tyle aktualizacja danych następuje wyłącznie po kliknięciu na typ filtra po każdorazowej zmianie wartości przedziału. To jest uciążliwe i niezgodne z zasadami użyteczności. Nie ma co do tego żadnych wątpliwości.
Zatem do dzieła – rozszerzamy, podane na stronie forum, rozwiązanie:
$.extend($.fn.datagrid.defaults.filters, {
dateRange: {
init: function(container, options){
var c = $('<div style="display:inline-block"><input class="d1"><input class="d2"></div>').appendTo(container);
c.find('.d1,.d2').datebox({
onSelect: function(date){
var name = $(this).closest('div.datagrid-filter').attr('name');
var value = c.find('.d1').datebox('getValue')+':'+c.find('.d2').datebox('getValue');
$('#dg').datagrid('removeFilterRule', name);
$('#dg').datagrid('addFilterRule', {
field: name,
op: 'inrange',
value: value
});
$('#dg').datagrid('doFilter');
},
onChange: function(dateN, dateO){
if(dateN == '') {
var name = $(this).closest('div.datagrid-filter').attr('name');
var value = c.find('.d1').datebox('getValue')+':'+c.find('.d2').datebox('getValue');
$('#dg').datagrid('removeFilterRule', name);
$('#dg').datagrid('addFilterRule', {
field: name,
op: 'inrange',
value: value
});
$('#dg').datagrid('doFilter');
}
}
});
$('.d1+span>input[type="text"], .d2+span>input[type="text"]').addClass('datagrid-filter-range').on('blur', function() {
var name = $(this).closest('div.datagrid-filter').attr('name');
var value = c.find('.d1').datebox('getValue')+':'+c.find('.d2').datebox('getValue');
var dv = ($('#dg').datagrid('getFilterRule', name) ? $('#dg').datagrid('getFilterRule', name).value : ':' );
if(dv != value) {
$(this).closest('span.datebox').prev().datebox('setValue', value);
$('#dg').datagrid('removeFilterRule', name);
$('#dg').datagrid('addFilterRule', {
field: name,
op: 'inrange',
value: value
});
$('#dg').datagrid('doFilter');
}
});
return c;
},
destroy: function(target){
$(target).find('.d1,.d2').datebox('destroy');
},
getValue: function(target){
var d1 = $(target).find('.d1');
var d2 = $(target).find('.d2');
return d1.datebox('getValue') + ':'+d2.datebox('getValue');
},
setValue: function(target, value){
var d1 = $(target).find('.d1');
var d2 = $(target).find('.d2');
var vv = value.split(':' );
d1.datebox('setValue', vv[0]);
d2.datebox('setValue', vv[1]);
},
resize: function(target, width){
$(target)._outerWidth(width)._outerHeight(22);
$(target).find('.d1,.d2').datebox('resize', width/2);
}
}
});
Inicjalizacja została rozszerzona o obsługę zdarzeń onSelect i onChange. Nie ma tam nic innego jak aktywacja określonej reguły filtru opartej o wybraną wartość przedziału. Funkcja onChange jest konieczna, gdy zerujemy wartość przedziału (wtedy nie można wybrać wartości pustej, trzeba z klawiatury wyczyścić pole) lub zmianiamy wartość przy pomocy klawiatury – wtedy jednak kontrolka datebox waliduje wartość i gdy dopasuje wywołuje zdarzenie onSelect. Dlatego zdarzenie onChange wyklucza taki przypadek podczas wykonywania funkcji. Gdyby nie było tam bloku if, pod uwagę byłyby brane wszystkie zdarzenia – również te, które przyszły po programowym wykonaniu kodu zmiany wartości – taka pętelka 🙂
Pozostały kod onBlur działa na kontrolkach widocznych (te które wspierają przedział bezpośrednio są ukryte!). Otóż gdy zmianimy przedział za pomocą klawiatury, a nie kontrolki datebox występują problemy przy wywoływaniu zdarzenia odświeżania filtrowania – nie zawsze to działało. Stąd ten fix. Pobieramy wartość reguły filta – jeśli jest inna niż wartość po zdarzeniu onBlur to ustalana jest nowa reguła filtrowania. Krótko i na temat.
Działający kompletny przykład znajdziecie pod tym linkiem. Przykład jest rozszerzeniem demo DataGrid Filter Row ze strony demo.