Ekstremalna przeróbka ASP.NET - Część 4: Poprawianie JavaScript .png)
Autor: James Kovacs
Kod dostępny do pobrania z witryny MSDN Code Gallery
Witam z powrotem w czwartym odcinku serii artykułów „Ekstremalna przeróbka ASP.NET”. W Części 3 zbadaliśmy, jak XHTML i kaskadowe arksusze stylów (CSS) mogą poprawić układ i stronę wizualną witryn WWW, a w szczególności witryny ScrewTurn Wiki. Ten artykuł będzie się skupiał na refaktoringu używanego przez ScrewTurn Wiki kodu JavaScript przy pomocy jQuery i testowaniu go przy pomocy QUnit.
Lingua Franca programowania WWW
JavaScript to lingua franca wśród języków programowania WWW. Nie ma znaczenia, czy programujemy przy pomocy ASP.NET, Ruby, Python, PHP lub innego języka programowania po stronie serwera, językiem programowania przeglądarki jest JavaScript. Oficjalny standard nosi bardziej poprawną nazwę ECMAScript i jest określony w normie ECMA-262. JavaScript, JScript, ActionScript i inne odmiany, wszystkie są dialektami ECMAScript, ale napisanymi przez różnych producentów. Obecne przeglądarki implementują wersję ECMA-262 Edition 3, która jest standardem od roku 1999. (Trwają prace nad wersją ECMA-262 Edition 5. Wersja Edition 4 została porzucona ze względu na nierozwiązane różnice wewnątrz komitetu standaryzacyjnego.) Dialekt JavaScript jest zarządzany przez Mozilla Foundation i jest implementowany przez Firefox, Google Chrome, Safari i inne przeglądarki. JScript jest dialektem firmy Microsoft i jest implementowany w przeglądarce Internet Explorer. Dialekty te są zgodne z ECMA-262, ale oferują specyficzne rozszerzenia. W tym artykule będę stosował przyjętą praktykę określania tego języka jako JavaScript, choć poprawniej powinien być on nazywany ECMAScript.
Dziedzictwo JavaScript
JavaScript ma reputację języka zabawkowego. Wielu programistów ma mylne wrażenie, że JavaScript jest uproszczoną wersją języka Java bez silnego typowania. W rzeczywistości nie mogliby się bardziej mijać z prawdą. Podczas projektowania nowego języka dla przeglądarki Netscape, Brendan Eich – twórca JavaScript czerpał inspirację z Self (opartego na prototypach języka zorientowanego obiektowo) i Scheme (języka funkcjonalnego). Ten nowy język został nazwany Mocha, a następnie przemianowany na LiveScript. Wraz ze wzrostem zainteresowania językiem Java w późnych latach 90-tych Mocha/LiveScript został przemianowany na JavaScript i zmodyfikowany tak, aby bardziej przypominał język Java z nawiasami klamrowymi i średnikami. W rzeczywistości JavaScript jest bardziej związany ze swoimi funkcjonalnymi korzeniami, gdzie funkcje są traktowane jako pierwszoplanowe elementy języka. Chociaż wielu programistów nadal korzysta z JavaScript w bardzo proceduralny sposób, to jest to w istocie bardzo silny język funkcjonalny, który działa w milionach przeglądarek WWW na całym świecie.
Renesans JavaScript
Ponowne zainteresowanie językiem JavaScript zostało wzbudzone przez najnowsze platformy dla języka JavaScript takie jak jQuery, Prototype, YUI, Dojo i MooTools. jQuery przyciągnął ostatnio dużo uwagi ze względu na swoje możliwości, prostotę i łatwość rozszerzania. Platformę tę wydał po raz pierwszy John Resig w 2006 roku, a od tego czasu zebrała ona aktywną społeczność współuczestników projektu, autorów wtyczek i użytkowników. Według witryny WWW platformy jQuery:
„jQuery jest szybką i zwartą biblioteką JavaScript, która upraszcza korzystanie z dokumentu HTML, obsługę zdarzeń, animacje i interakcje Ajax przy szybkim programowaniu aplikacji WWW. jQuery zaprojektowano z myślą o zmianie sposobu pisania kodu JavaScript.”
Biblioteka jQuery otrzymała dodatkowe wsparcie 28 września 2008, gdy Scott Guthrie, wiceprezes Microsoft Developer Division, ogłosił, że Microsoft został partnerem zespołu programistycznego jQuery i będzie dostarczał odtąd jQuery razem z Visual Studio. Był to historyczny moment, ponieważ biblioteka jQuery jest wydawana na licencji otwartego kodu źródłowego MIT. Microsoft nie tylko dostarcza bibliotekę z otwartym kodem źródłowym wraz z Visual Studio, ale też zobowiązał się do dostarczania tej biblioteki w niezmienionej formie i zgłaszania łatek w taki sam sposób, jak pozostali programiści lub zespoły uczestniczące w projekcie.
Najnowszym wydaniem (w momencie pisania tego artykułu) jest jQuery 1.3.2, które obsługuje większość przeglądarek będących obecnie w użyciu, w tym Internet Explorer 6.0+, Firefox 2.0+, Safari 3.0+, Opera 9.0+ i Chrome. Programowanie WWW i język JavaScript zyskały złą reputację w kręgach programistycznych, w dużej części ze względu na niekonsekwencje, błędy i dziwne zachowania specyficzne dla różnych przeglądarek. Aby uzyskać obsługę wielu przeglądarek, jQuery zapewnia spójny interfejs API do manipulowania modelem obiektowym HTML DOM, przyłączania zdarzeń, animowania elementów oraz interakcji AJAX poprzez wewnętrzne obsługiwanie różnic pomiędzy przeglądarkami.
Testowanie JavaScript
Zanim zabierzemy się za zastępowanie w ScrewTurn Wiki języka JavaScript w stylu klasycznym na JavaScript w stylu jQuery, musimy pamiętać o celu refaktoringu – zmianie wewnętrznej implementacji kodu bez zmiany jego zachowania. Moglibyśmy zmienić kod i ręcznie sprawdzać, czy zmiany nic nie zepsuły, ale powinniśmy przywiązywać tę samą uwagę do naszego kodu JavaScript, jaką przywiązujemy do swojego kodu po stronie serwera. Gdyby to był C#, mógłbym napisać test sprawdzający aktualne zachowanie, zrefaktorować kod i ponownie uruchomić test, aby zapewnić, że wszystko będzie się nadal zachowywać tak, jak przedtem. Dlaczego miałbym przywiązywać mniejszą uwagę do kodu JavaScript po stronie klienta? Pytanie jednak brzmi, jak testować JavaScript? Odpowiedź daje sama biblioteka jQuery.
Podczas rozwijania jQuery, zespół projektowy natrafił na podobne wyzwanie – sprawdzać, że jQuery nadal działa zgodnie z oczekiwaniami w miarę wprowadzania optymalizacji i poprawek. Rozwiązaniem było utworzenie lekkiej platformy testowej dla Java Script o nazwie QUnit. Nie ma nic szczególnego dla jQuery w QUnit i można stosować tę platformę do testowania każdego kodu JavaScript.Prosty test QUnit mógłby wyglądać następująco:
test('Concatenated hello should compare equal to hello', function() {
var actual = 'h' + 'e' + 'l' + 'l' + 'o';
var expected = 'hello';
equals(actual, expected);
});
Ten test jest wbudowany w ramy testowe strony WWW, a wyniki można zobaczyć w przeglądarce, jak pokazano na Rysunku 1.
Rysunek 1: Testy QUnit dla ScrewTurn Wiki.
Następujące wideo przygląda się dokładniej platformie QUnit i temu, jak testy integrują się z ramą testową strony HTML.
Refaktoring JavaScript w aplikacji ScrewTurn Wiki
Pierwszym krokiem w przekształceniu kodu JavaScript dla aplikacji ScrewTurn Wiki jest wyodrębnienie go do oddzielnych plików JavaScript. Nie tylko pozwoli to nam testować JavaScript, ale też poprawi wydajność strony. Wynika to ze zmniejszonego rozmiaru strony, gdy przeglądarka może przechowywać pliki JavaScript w pamięci podręcznej zamiast pobierać je z każdą stroną dynamiczną. Jest to głównie prosta operacja przeniesienia kodu JavaScript z plików MasterPage.master i Default.aspx do pliku ~/JS/ScrewTurnWiki.js oraz dołączenia odwołania do ScrewTurnWiki.js w pliku MasterPage.master (przeniesienie kodu JavaScript z innych plików do pliku ScrewTurnWiki.js pozostawiam jako ćwiczenie dla czytelnika). Plik MasterPage.master w aplikacji ScrewTurn Wiki wywołuje Tools.GetIncludes() w celu automatycznego wygenerowania znaczników <script type=”text/javascript”/> dla wszystkich plików JavaScript w folderach ~/JS/ i ~/Themes/<NAME>/, więc nie musimy ręcznie dodawać pliku ScrewTurnWiki.js.
Po przeniesieniu skryptów do ~/JS/ScrewTurnWiki.js, główna strona ScrewTurn Wiki wygląda jak na Rysunku 2:
Rysunek 2: Główna strona aplikacji ScrewTurn Wiki.
Należy zwrócić uwagę na pole „Name Size”, które pojawia się u dołu strony pod akapitem Warning? Jest to element PageAttachmentsDiv, który powinien być ukryty. Oto kod, który go ukrywa:
var __elem = document.getElementById("PageAttachmentsDiv");
if (document.getElementById("PageAttachmentsLink")) {
__RepositionDiv(document.getElementById("PageAttachmentsLink"), __elem);
}
__elem.style[:display"] = "none";
Przed przeniesieniem kodu, powyższy kod JavaScript był umieszczony po znaczniku <div id=”PageAttachementsDiv”/>. Gdy kod ten był wykonywany, element <div/> istniał i był poprawnie ukrywany. Gdy kod ten został przeniesiony do ScrewTurnWiki.js, to jest wykonywany, zanim element <div/> zostaje utworzony i nic się nie dzieje. Element <div/> jest tworzony później i pozostaje widoczny. Rozwiązaniem jest po prostu przeniesienie powyższego luźnego kodu JavaScript do funkcji doWindowLoad() i wykonanie tej funkcji, gdy okno kończy się ładować:
window.onload = doWindowLoad;
function doWindowLoad() {
var __elem = document.getElementById("PageAttachmentsDiv");
if (document.getElementById("PageAttachmentsLink")) {
__RepositionDiv(document.getElementById("PageAttachmentsLink"), __elem);
}
__elem.style["display"] = "none";
// Dodatkowy kod, który musi być wykonywany, gdy ładowany jest
// model HTML DOM i staje się gotowy do manipulacji
}
ScrewTurn Wiki wygląda i zachowuje się teraz w taki sposób, jak przed przeniesieniem kodu JavaScript, co widać na Rysunku 3.
Rysunek 3: Strona główna ScrewTurn Wiki bez pola „Name Size”.
Teraz gdy kod JavaScript został przeniesiony do oddzielnego pliku, możemy użyć platformy QUnit do przetestowania, czy element PageAttachmentsDiv jest ukryty, gdy strona zostaje załadowana. W pliku QUnitTests.html – naszej ramie testów stron HTML – tworzymy atrapę elementu <div/> z tym samym identyfikatorem:
<div id="PageAttachmentsDiv" style="width:100px;height:100px;"></div>
Następnie w naszym teście jawnie wywołujemy funkcję doWindowLoad() i sprawdzamy, że element PageAttachmentsDiv jest ukryty:
test('PageAttachmentsDiv should be initially hidden', function() {
doWindowLoad();
var display = document.getElementById('PageAttachmentsDiv').style['display'];
equals(display, 'none');
});
Zastosowanie atrap elementów strony w testach jest łatwiejsze niż próba przetestowania faktycznych elementów strony na prawdziwej stronie. Aby uniknąć zanieczyszczania wyników testu przez atrapy elementów, zewnętrzny element <div/> może być oznaczony jako ukryty (display:none;) lub ustawiony w pozycji bezwzględnej poza widoczną zawartością (position:absolute;left:-1000px;top:-1000px;).Wybór techniki zależy od tego, co próbujemy testować, ponieważ ukrycie elementu <div/> czasami wpływa na testy związane z układem strony.
Zabawa z jQuery
Mając na miejscu środowisko testowe QUnit, jesteśmy gotowi do przekształcania kodu JavaScript w ScrewTurn Wiki wykorzystując jQuery. Zacznijmy od przyjrzenia się ukrywaniu elementu PageAttachmentsDiv:
var element = document.getElementById("PageAttachmentsDiv");
element.style["display"] = "none";
Następujący kod jQuery implementuje tę samą funkcjonalność:
$('#PageAttachmentsDiv').hide(); // $() is an alias for jquery()
(Trzeba zwrócić uwagę, że musimy dołączyć odwołanie do biblioteki jQuery na naszej stronie korzystając ze znacznika <script/>.) Wersja w jQuery jest bardziej zwięzła wykorzystując styl CSS „#”, aby dokonywać wyboru w oparciu o identyfikator oraz bardziej deklaratywną metodę „hide()”. Może się to wydawać drobiazgiem, dopóki nie zdamy sobie sprawy z mocy ukrytej za tą pozorną prostotą. Co by było, gdybyśmy chcieli ukryć wszystkie menu na stronie mające klasę CSS „menu”?
$('.menu').hide();
Nie ma potrzeby przeglądania modelu HTML DOM albo iteracji w pętlach for. jQuery obsługuje to wszystko za nas w sposób przeźroczysty.
jQuery wykorzystuje selektory podobne do CSS do działania na kolekcjach elementów HTML DOM. Możemy więc wykonywać jeszcze lepsze sztuczki, na przykład ukrywać wszystkie menu, ale tylko, jeśli ich bezpośrednim obiektem nadrzędnym jest element <div/> z klasą CSS „toplevel”:
$('div.toplevel > .menu').hide();
Aby zobaczyć ten kod w działaniu, należy sprawdzić:
jQuery nie tylko zachęca nas do pisania mniejszej ilości kodu, ale również do bardziej funkcjonalnego stylu kodowania. Aby to było jasne, rozważmy ukrywanie wszystkich menu przy użyciu kodu w stylu proceduralnym:
- Zacznijmy od elementu document.
- Dla każdego elementu podrzędnego.
- Jeśli jest to menu, zmieńmy jego styl na display: none.
- W przeciwnym razie przechodźmy przez jego obiekty podrzędne z kroku 2.
Styl funkcjonalny mówi:
- Ukryjmy wszystkie menu.
Kod w stylu funkcjonalnym mówi, co zrobić, a nie jak to zrobić. Jest to subtelna, ale ważna różnica.
ASIDE: IntelliSense dla jQuery
IntelliSense dla jQuery można włączyć w Visual Studio 2008 SP1 korzystając z poprawki Microsoft, jak to opisał Scott Guthrie w swoim blogu. Następujące wideo przedstawia wskazówki, sztuczki i problemy związane z włączaniem IntelliSense dla jQuery w swoich projektach.
Czy jesteśmy gotowi?
Wspomniałem tylko mimochodem o jednym ważnym problemie z kodem jQuery. jQuery manipuluje modelem HTML DOM, a nie można tego robić, dopóki model HTML DOM nie zostanie załadowany. Zazwyczaj wykonujemy kod JavaScript w odpowiedzi na zdarzenie window.onload, jak w powyższym przykładzie. Niestety zdarzenie window.onload jest wzbudzane dopiero, gdy strona skończy się ładować, co obejmuje wszystkie pliki JavaScript, obrazy i inne zasoby na stronie. jQuery zapewnia wygodny, niezależny od przeglądarki trik pozwalający wykonywać kod jak tylko model HTML DOM jest gotowy, ale bez oczekiwania na załadowanie wszystkich zasobów:
$(document).ready(function() {
// Hookup event handlers and execute HTML DOM-related code
});
Możemy zmienić nasz kod window.onload na następujący:
$(document).ready(function() {
$('#PageAttachmentsDiv).hide();
// Dodatkowy kod związany z HTML DOM
});
Bardzo miłą funkcją jest to, że podłączanie się do zdarzenia ready nie nadpisuje go, natomiast dodaje funkcję do stosu zarejestrowanych funkcji zdarzenia ready. Dzięki temu niezwiązane ze sobą fragmenty kodu mogą rejestrować się w zdarzeniu ready bez wpływania nawzajem na siebie.
Rozdzielanie problemów przy pomocy jQuery
Na typowej stronie WWW, kod JavaScript jest często swobodnie porozrzucany po kodzie HTML. Niewielkie fragmenty wbudowanego kodu podłączają procedury obsługi zdarzeń do wyłapywania kliknięć, ruchów kursora myszy, naciśnięć klawiszy, itd. Na przykład menu narzędzi administracyjnych ScrewTurn Wiki jest przełączane przez kliknięcie łącza Admin, co uruchamia wywołanie funkcji __ToggleAdminToolsMenu() (aby zobaczyć łącze Admin, trzeba zalogować się korzystając z poświadczeń admin/password.)
<a id="AdminToolsLink" title="Administration Tools" href="#" onclick="javascript:return __ToggleAdminToolsMenu();">Admin</a>
Zamiast przyłączać zdarzenie onclick w kodzie HTML, moglibyśmy użyć następującego kodu HTML:
<a id="AdminToolsLink" title="Administration Tools" href="#">Admin</a>
A następnie podłączyć zachowanie łącza w znaczniku script w sekcji <head/> lub jeszcze lepiej w oddzielnym pliku JavaScript:
$(document).ready(function() {
$('#AdminToolsLink').click(function() {
return __ToggleAdminToolsMenu();
});
});
jQuery zachęca do oddzielania kodu obsługującego zachowanie elementów strony od znaczników HTML.
Nie ogranicza się do przenoszenia wywołań funkcji. Możemy użyć jQuery, aby dalej przetwarzać i upraszczać kod JavaScript w ScrewTurn Wiki. Kod związany z pokazywaniem/ukrywaniem elementów strony takich jak narzędzia administracyjne i załączniki do stron ma około 100 wierszy długości, w tym komentarze i puste wiersze, ale bez podłączania onclick, onhover i innych zdarzeń. Może to wyglądać na dużo kodu, ale możemy osiągnąć ten sam efekt dla narzędzi administracyjnych korzystając z następującego kodu jQuery:
$(document).ready(function() {
$('#AdminToolsLink').click(function() {
$('#AdminToolsDiv').css({ 'z-index': '1',
'position': 'absolute',
'top': link.position().top + link.outerHeight() + 'px',
'left': (link.position().left
- (div.outerWidth() - link.outerWidth())) + "px"
});
$('#AdminToolsDiv').toggle();
}).click();
});
Gdy klikamy łącze AdminToolsLink, używamy wbudowanej w jQuery funkcji css(), aby prawidłowo ustawić pozycję elementu AdminToolsDiv pod łączem AdminToolsLink a następnie wykorzystujemy wbudowaną funkcję toggle(), aby przełączyć element AdminToolsDiv pomiędzy stanami visible a hidden. Warto zwrócić uwagę na bezparametrową funkcję click() na końcu. W jQuery, click(function() {}) przyłącza procedurę obsługi zdarzenia, natomiast click() ją wywołuje. Konfigurujemy więc procedurę obsługi zdarzenia i natychmiast ją wywołujemy, aby przełączyć menu do stanu ukrytego. Możemy używać tej samej techniki dla załączników do strony, ale możemy pójść o krok dalej, jak pokazano na Rysunku 4.
Rysunek 4: Funkcja Toggle() w jQuery.
$(document).ready(function() {
$('.dropdownMenu').click(function() {
var link = $(this);
var div = link.next('div');
div.css({ 'z-index': '1',
'position': 'absolute',
'top': link.position().top + link.outerHeight() + 'px',
'left': (link.position().left
- (div.outerWidth() - link.outerWidth())) + "px"
});
div.toggle();
}).click();
});
Stosujemy klasę CSS „dropdownMenu” wobec każdego elementu strony, który chcemy wykorzystać jako menu rozwijane. Powyższy kod przyłącza zdarzenie click. Przy kliknięciu jQuery znajduje następny element <div/> po klikniętym elemencie i przełącza ten element <div/> pomiędzy stanem widocznym, a ukrytym. Przeszliśmy od proceduralnego stylu określania, jak poszczególne elementy strony są pokazywane i ukrywane do stylu deklaratywnego, w którym stosowanie klasy CSS „dropdownMenu” wobec elementu sprawia, że jego elementy podrzędne są przełączane automatycznie. Jeśli postanowimy, że nie podoba nam się efekt toggle(), to jQuery zapewnia rozmaite inne efekty wizualne, w tym wygaszanie, przesuwanie i niestandardowe animacje.
Możemy zrobić znacznie więcej przy pomocy jQuery, między innymi stosować naprzemienne style wierszy w tabelach, efekty aktywowane wskazaniem elementu kursorem myszy, ładowanie w tle i wyświetlanie danych AJAX oraz wiele więcej. Na bazie jQuery powstała zaawansowana biblioteka do obsługi interfejsu użytkownika nazwana jQueryUI. Rozbudowuje ona podstawowe elementy i animacje jQuery zapewniając bogate obiekty po stronie klienta takie jak pola wyboru daty, niestandardowe okna dialogowe, paski postępu, suwaki, karty, a także bogate interakcje takie, jak umożliwianie przeciągania elementów strony, zmieniania ich rozmiarów i sortowania!
Podsumowanie
Ten artykuł skupiał się na wykorzystaniu jQuery i QUnit do refaktoringu kodu JavaScript w ScrewTurn Wiki.Korzystając z jQuery byliśmy w stanie dramatycznie ograniczyć ilość wymaganego kodu JavaScript. Pokazaliśmy tylko podstawy tego, co można uzyskać przy pomocy jQuery. Więcej na temat jQuery można się dowiedzieć z artykułu Getting Started with jQuery autorstwa Jörn Zaefferer, jak również z serii artykułów Dino Esposito w MSDN Magazine dotyczących jQuery: „Explore Rich Client Scripting With jQuery, Part 1”, „Explore Rich Client Scripting With jQuery, Part 2” oraz „Build Rich User Interfaces with jQuery”. Następnym razem przyjrzymy się zastosowaniu kilku bardziej zaawansowanych technik jQuery do rozszerzenia funkcjonalności witryny przy pomocy jQueryUI.
James Kovacs jest niezależnym architektem, programistą, szkoleniowcem i złotą rączką, mieszkającym w Calgary w stanie Alberta, specjalizującym się w programowaniu zwinnym przy użyciu .NET Framework. Ma tytuł Microsoft MVP for Solutions Architecture i uzyskał stopień magistra na Harvard University. Można się z nim skontaktować pod adresem jkovacs@post.harvard.edu lub www.jameskovacs.com.

.png)
.png)