W poprzednim tygodniu opublikowany został artykuł poświęcony problemom napotykanym w trakcie pracy z ExtJS. W tym tygodniu dalszy ciąg przygód z tym ładnym, efektownym, wygodnym, ale często też kapryśnym i wymagającym nieco okiełznania frameworkiem do tworzenia interfejsów aplikacji webowych.
Przypomnijmy, że artykuł niniejszy powstał na bazie doświadczeń wyniesionych w trakcie prac nad oprogramowaniem wspomagającym procesy księgowe w jednej z firm informatycznych. Projekt ten wykorzystuje ExtJS, co okazało się strzałem w dziesiątkę, zarówno pod względem wygody użytkowników, aspektów wizualnych, intuicyjności, jak i zmniejszenia do minimum koniecznej komunikacji z serwerem. I choć ExtJS posiada całe mnóstwo udogodnień, pewne z nich nie zawsze są widoczne na pierwszy rzut oka.
W poprzednim artykule skoncentrowaliśmy się na podniesieniu wydajności oraz obejściu uciążliwych problemów z pisaniem kodu dla przeglądarki Internet Explorer. Tym razem skoncentrujemy się na właściwej obsłudze błędów oraz dodawaniu własnych filtrów.
Własny typ walutowy
Charakterystyczne dla nasze projektu, czyli systemu wspierającego księgowość jest to, że musi pracować ściśle w realiach danego kraju. Dlatego konieczne było przystosowanie ExtJS do obsługi polskiej waluty. Twórcy frameworka przewidzieli wprawdzie specjalne mechanizmy do obsługi walut, jak jednak łatwo się domyślić – ograniczono się do kilku najważniejszych walut na świecie. Złotówek wśród nich zabrakło.
Nie jest to jednak problem. ExtJS pozwala na tworzenie własnych filtrów i podpinanie ich tam gdzie powinny być formatowane określone dane – dotyczy to przede wszystkim gridów, czyli tabel, od których oczekujemy prezentacji danych we właściwym formacie.
I choć prezentowany przykład dotyczy formatowania danych do typu walutowego, w podobny sposób możesz zrealizować formatowanie jakichkolwiek innych danych, do takiego formatu jakiego tylko zapragniesz.
Filtr formatujący jest zwykłą funkcją JS. Aby zrealizować założone zadanie, napisana została krótka funkcja plMoney:
function plMoney (val) {
val = Math.round(val*100)/100;
return val+\' zł\';
}
Przy okazji funkcja ta dokonuje zaokrąglenia wartości do dwóch miejsc po przecinku. Ponieważ JavaScript nie posiada tak wygodnej implementacji funkcji round jak większość innych języków programowania, nie ma możliwości podania ilości miejsc po przecinku przy zaokrągleniu jako parametr funkcji. Zamiast tego należy zastosować sztuczkę. Funkcja Math.round() dokonuje zaokrąglenia do liczby całkowitej. Jeśli więc dokonane zostanie zaokrąglenie liczby pomnożonej przez 100, a następnie – już po zaokrągleniu – podzielonej przez 100, wynik stanowić będzie liczba z co najwyżej dwoma miejscami po przecinku.
W jaki sposób możesz taką funkcję dołączyć do grida? Odpowiednim miejscem będzie to, w którym grid jest definiowany:
var grid = new Ext.grid.GridPanel({
store: store,
layout: \'fit\',
title: \'Cennik\',
cm: new Ext.grid.ColumnModel([
new Ext.grid.RowNumberer(),
{header: \"Produkt\", dataIndex: \'Produkt\', width: 200, sortable: true },
{header: \"Cena\", dataIndex: \'Cena\', width: 60, renderer: plMoney, sortable: true }
]),
});
Zwróć uwagę na parametr renderer podczas definiowania modelu kolumny. To jest odpowiednie miejsce do podpięcia własnej funkcji formatującej.
Funkcja formatująca może dokonywać znacznie więcej przekształceń – występuje tu pełna dowolność, ograniczona tylko potrzebami programisty. Poniżej znajduje się jeszcze jeden przykład formatowania danych w gridzie. Funkcja ta pobiera jako parametr liczbę całkowitą, oznaczającą wartość procentową podatku. Zadaniem funkcji jest dodanie znaku procenta. Ponadto – ponieważ w ustawie o podatku VAT występują specjalne stawki podatku oznaczane symbolami – dokonuje interpretacji danych i zamienia wartość liczbową ujemną na odpowiednie symbole. Taką przyjęto nomenklaturę dla zapisu tych symboli w tworzonym systemie księgowym.
function formatProcent (val) {
if ((val == \'-2.00\') || (val == \'-2\') || (val == \'ZW\') )
return \'ZW\';
else if ((val == \'-1.00\') || (val == \'-1\') || (val == \'NP\') )
return \'NP\';
else
return val+\' %\';
}
Jak widać za pomocą funkcji formatujących można dokonać takiego przekształcenia danych w gridach, jakie jest akurat w danym projekcie potrzebne.
Obsługa błędów po stronie przeglądarki
Im bardziej skomplikowany program, tym ważniejsze jest, aby pozostawać z użytkownikiem w stałej interakcji. Oznacza to między innymi informowanie go o występujących błędach i nieścisłościach, a także ułatwianie wprowadzania poprawnych danych.
Systemy typu księgowego muszą być podporządkowane setkom zależności – tak prawnych, jak i technicznych. Często w wielu miejscach ocierając się o brak logiki w określonych procedurach. Dlatego sprawny system informowania o występujących problemach to podstawa. Najlepiej, jeśli użytkownik jest informowany o potencjalnym problemie (np. błędnej kwocie) od razu lub tak szybko jak jest to możliwe. Pozwala to usprawniać pracę z systemem.
Na szczęście ExtJS dostarcza bardzo dobrych rozwiązań pozwalających na implementację obsługi błędów. Technologie te można podzielić według miejsca dokonywania analizy danych. Na tej podstawie można wyróżnić błędy obsługiwane wyłącznie po stronie przeglądarki (w ramach frameworku ExtJS) oraz błędy obsługiwane po stronie serwera.
Zaczniemy od tych pierwszych. Już w samej przeglądarce można dokonać prostej analizy danych. Warto zaczynać od dokładnego definiowania dozwolonej zawartości pól w obrębie formularza.
var form = new Ext.FormPanel({
labelWidth: 100
url:\'akcja.php\',
title: \'Przykladowy formularz\',
width: 350,
defaultType: \'textfield\',
items: [{
fieldLabel: \'Telefon\',
name: \'telefon\',
allowBlank: false,
vtype: \'telefon\'
}],
buttons: [{
text: \'Zapisz\'
},{
text: \'Anuluj\'
}]
});
Powyższy kod definiuje przykładowy formularz, zawierający tylko jedno pole – numer telefonu. Zwróć uwagę, że dzięki zastosowaniu parametru allowBlank o wartości false, wymuszamy wypełnienie pola – w przeciwnym razie w ogóle nie uda się użytkownikowi przesłanie formularza – framework samoczynnie wyświetli komunikat o tym, że należy uzupełnić pole z numerem telefonu.
To nie koniec działań weryfikacyjnych, które możesz dokonać na tym etapie. ExtJS dostarcza mechanizmu masek i typów zawartości, które pozwalają na bardzo precyzyjne sprawdzanie wartości pola. Jeśli pole zawiera dane inne od oczekiwanych – system wyświetli błąd. Rodzaj użytego w danym polu typu danych określasz poprzez parametr vtype. W powyższym przykładzie jest to vtype o nazwie telefon. Czas na jego zdefiniowanie:
Ext.form.VTypes[\"telefon\"] = /^\+[0-9]{2}\.[0-9]{9,14}$/;
Ext.form.VTypes[\"telefonMask\"] = /[0-9+.]/;
Ext.form.VTypes[\"telefonText\"] = \'Zły format numeru telefonu. Wprowadź format zgodny z międzynarodowym, np. +48.22000000.\';
Zawartością danych wprowadzanych do danego pola możesz sterować dwojako. Po pierwsze posiadasz możliwość określenia jakie znaki mogą być do danego pola wprowadzane. ExtJS będzie pilnować, aby do danego pola można było wprowadzić tylko wyszczególnione znaki.
Maskę taką określasz stosując nazwę typu danych z dodatkiem sufiksu \”Mask\”. Dla pola \”telefon\” jest to \”telefonMask\”. Sama definicja jest złożona z wyrażenia regularnego. Zgodnie z przykładem, do pola wprowadzane będą cyfry, znak plusa oraz znak kropki. Pozostałe znaki będą ignorowane.
W tym miejscu czas na małą uwagę. W polach, które zawierają tylko dane liczbowe, bez liter, warto do maski dodawać mimo wszystko literę \”v\”. W przeciwnym wypadku system nie dopuści do wklejania danych do pola. Stanie się tak, ponieważ kombinacja Ctrl+V zostanie zinterpretowana jako wstawienie małej litery \”v\” – co spowoduje jej zignorowanie w przypadku gdy znak ten nie został dopuszczony.
Z kolei blokowanie wklejania danych doprowadzi do zmniejszenia użyteczności systemu – praca z nim będzie mniej wygodna. Z drugiej jednak strony, jeśli wklejanie danych zostanie dopuszczone, dane wprowadzane poprzez wklejenie nie będą filtrowane. Jest to sposób na obejście mechanizmu masek. Z obu niedogodności warto zdawać sobie sprawę. Miejmy nadzieję, że twórcy ExtJS poprawią te mankamenty.
Na szczęście maski są jedynie podstawowym sposobem filtrowania danych. Do ostatecznej weryfikacji zawartości pola może służyć format danych. Jest to pierwsza linia powyższego przykładu. O ile maski powodują jedynie prostą analizę znaków, bezpośrednio w momencie ich wprowadzania z klawiatury, o tyle format danych jest sprawdzany dopiero po zakończeniu pracy w danym polu. Dzięki temu można doprecyzować sposób użycia znaków dozwolonych w masce.
W zastosowanym przykładzie pozwoliliśmy na użycie cyfr, znaku plusa i kropki. Chcemy jednak, aby numer telefonu był podawany w formacie międzynarodowym, według standardu charakterystycznego dla systemu domen internetowych (np. +48.220000000). Stąd format danych został ustalony w sposób zezwalający jedynie na powyższy format i żaden inny (wyjątkiem jest dopuszczenie dłuższych numerów telefonu dla potrzeb niektórych krajów). Definicji takiej dokonano ponownie z wykorzystaniem wyrażeń regularnych. Sprawdzenie poprawności nastąpi po zakończeniu wprowadzania danych do tego pola.
W praktyce tak restrykcyjne zawężanie formatu numeru telefonu zwykle nie jest potrzebne, jednak tam gdzie numer ma być przetwarzany automatycznie – tak jak na przykład przy obsłudze domen internetowych – może okazać się to koniecznością. Istnieje jednak wiele innych rodzajów danych, gdzie ścisłe zachowanie formatu jest ważne.
Wreszcie w ostatniej linii przykładu ustawiany jest komunikat, który zostanie wyświetlony jeśli wprowadzone przez użytkownika dane nie będą zgodne z oczekiwanym formatem. Ze względu na użyteczność systemu, komunikat taki powinien jasno poinformować, że format danych jest błędny oraz jak powinna wyglądać zawartość pola.
Sam komunikat jest przez ExtJS wyświetlany bezpośrednio przy polu, w którym błąd wystąpił. Występuje kilka wariantów wyświetlani. Oto przykładowe z nich:
Dymek informujący o błędzie, pokazujący się po najechaniu na określone pole. Samo pole jest oznaczone czerwonym pokreśleniem.
W tym przypadku błąd również pojawia się w postaci dymku, ale dodatkowo z prawej strony pola umieszczona jest ikonka symbolizująca występujący problem. Dymek jest widoczny po najechaniu na ikonkę.
Błąd wyświetlany jest pod polem. Komunikat widoczny jest od razu, bez konieczności najeżdżania na miejsce wystąpienia błędu. Jest to zaleta, ale tylko przy krótszych komunikatach. Należy też pamiętać, że dodanie takiej informacji pod spodem spowoduje rozsunięcie formularza w pionie – należy przewidzieć na to miejsce, projektując formularz.
Sposób wyświetlania komunikatu jest ustawiany parametrem msgTarget podczas definiowania pól formularza:
items: [{
fieldLabel: \'Telefon\',
name: \'telefon\',
allowBlank: false,
vtype: \'telefon\',
msgTarget: \'under\'
}],
Wyżej wymienione metody wyświetlania błędów można ustawić używając kolejno wartości: \”qtip\” (domyślna), \”side\” oraz \”under\”. Istnieje ponadto możliwość wyświetlenia komunikatu o błędzie jako zawartość dowolnego elementu. Może to być na przykład inne pole tekstowe lub warstwa. Wystarczy podać ID takiego elementu.
Poprawność danych można sprawdzić także programowo – definiując odpowiednie zdarzenia (na przykład po edycji określonego pola). Jest też inna możliwość – ostateczna weryfikacja danych tuż przed wysłaniem formularza. Sam formularz może być wysłany poprzez zdefiniowanie funkcji przechwytującej kliknięcie w przycisk (np. \”Zapisz\”). Poniższy formularz zawiera taką funkcję:
var formularz = new Ext.FormPanel({
defaultType: \'textfield\',
items: [
{fieldLabel: \'Podaj hasło\', name: \'haslo1\', allowBlank:false, inputType: \'password\'},
{fieldLabel: \'Potwierdź hasło\', name: \'haslo2\', allowBlank:false, inputType: \'password\'}
],
buttons: [{
text: \'Zapisz\',
handler: function() {
var haslo1 = formularz.getForm().getValues().haslo1;
var haslo2 = formularz.getForm().getValues().haslo2;
if (haslo1 != haslo2)
{
Ext.Msg.alert(\'Błąd\', \'Hasło oraz weryfikacja hasła powinny być takie same\');
return false;
}
formularz.getForm().submit({
method : \'POST\',
url: \'action.php\',
waitTitle : \'Połączenie\',
waitMsg : \'Trwa wysyłanie danych...\'
});
}
}]
});
Powyższy formularz zawiera dwa pola, do których użytkownik wpisuje hasło. Przed wysłaniem formularza dokonywane jest porównanie zawartości obu pól. Jeśli są różne – kończone jest wykonywanie funkcji wysyłającej, zwracając błąd. W przeciwnym razie wykonany zostanie dalszy kod, wysyłający dane do serwera.
Obsługa błędów po stronie serwera
Nadszedł więc moment, w którym dane wysłane z formularza znajdują się już po stronie serwera. Samo zweryfikowanie poprawności wprowadzonych danych pod względem znaków nie zabezpiecza przed wszystkimi potencjalnymi błędami. Wprost przeciwnie – dopiero po przesłaniu danych do serwera, w miejscu gdzie rozpoczyna się ich rzeczywiste przetwarzanie, rodzi się większość okazji do wystąpienia błędu. Dlatego projektując systemu w ExtJS należy je wyposażyć w mechanizm przechwytywania informacji o błędach zwracanych przez serwer. Na szczęście tutaj również framework wysoce te czynności wspomaga.
Sposób interpretowania błędu będzie zależny od jego charakteru. Możesz mieć do czynienia z błędami o charakterze ogólnym (np. problem z zapisem do bazy danych) – wówczas warto na przykład wyświetlić ogólne okno z komunikatem o błędzie otrzymanym z serwera, nie związane z żadnym konkretnym polem.
Błąd jednak może dotyczyć także bezpośrednio któregoś z pól. Przykładem z życia projektu księgowego są sytuacje, w których dokonuje się wprowadzenia daty wystawienia dokumentu księgowego, która po weryfikacji po stronie przeglądarki spełnia wymagania co do formatu wprowadzenia daty. Jednak po stronie serwera może okazać się, że data choć pod względem walidacyjnym jest poprawna, w praktyce jest nieprawidłowa – na przykład może dotyczyć innego miesiąca niż powinna.
W takim przypadku zamiast wyświetlania ogólnego komunikatu, można dokonać podświetlenia błędnego pola oraz wyświetlenia przy nim komunikatu o błędzie, za pomocą jednego z omawianych już sposobów. W praktyce dla użytkownika taki sposób obsługi błędu nie będzie się różnić od błędu, który wystąpił po stronie przeglądarki podczas analizy formatu pola. Jedyna różnica polega na tym, że w międzyczasie dokonane zostało połączenie z serwerem, który zwrócił informację o tym jakie pole zawiera błąd oraz treść komunikatu do wyświetlenia. Cały ten proces po stronie ExtJS odbywa się niemal całkowicie automatycznie. Serwer musi jedynie zwrócić dane w odpowiednim formacie JSON, oto przykład:
{ success: false, errors: { haslo1: \'Użyłeś już podanego hasła w przeszłości. Musisz podać nowe hasło.\' } }
Jeśli po wysłaniu formularza ExtJS otrzyma od serwera odpowiedź zgodną z powyższym wzorcem, będzie to dla niego znak, że coś poszło nie tak i przetwarzanie formularza zakończyło się niepowodzeniem (informuje o tym parametr \”success\” o wartości \”false\”). Ponadto przesłane zostały wartości komunikatów (tablica asocjacyjna \”errors\”). Są one przetwarzane automatycznie. Przy polu o nazwie \”hasło1\” pojawi się komunikat o tym, że hasło zostało już użyte w przeszłości – przydatna funkcja, gdy oczekujemy od użytkowników regularnej zmiany hasła na nowe.
Przetestuj taki sposób obsługi błędów – będziesz zaskoczony w jak wygodny sposób możesz wyświetlać komunikaty przy poszczególnych polach, generując jedynie odpowiedni format odpowiedzi po stronie serwera. Resztę ExtJS zrobi sam!
Jednak co w sytuacji, gdy chcesz wyświetlić błąd nie związany z żadnym konkretnym polem? Na przykład, jeśli operacja w bazie danych zakończyła się niepowodzeniem? Wówczas również powinieneś trzymać się powyższego formatu komunikatu o błędzie, samą jednak treść umieść pod nazwą parametru, który nie pokrywa się z żadnym polem w twoim formularzu. Na przykład:
{ success: false, errors: { komunikat: \'Wystąpił błąd w komunikacji z bazą danych. Spróbuj ponownie później.\' } }
Teraz musisz dokonać odpowiedniej obsługi błędu po stronie przeglądarki. Czynności, które zostaną wykonane po otrzymaniu powyższej odpowiedzi określasz podczas definiowania formularza – w ramach specjalnej metody failure:
failure: function(form, action) {
obj = Ext.util.JSON.decode(action.response.responseText);
Ext.Msg.alert(\"Wystąpił błąd\", obj.errors.komunikat);
}
W powyższym przykładzie wykorzystywana jest klasa JSON wbudowana w ExtJS do sparsowania kodu otrzymanego z serwera. Wartość parametru komunikat jest następnie odczytywana i wyświetlana w postaci informacji o błędzie.
Mamy nadzieję, że dzięki przedstawione w tym artykule sztuczki, pozwolą tworzyć ciekawsze aplikacje w ExtJS, pozbawione szeregu mankamentów.