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.