This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
SPIS TREŚCI OD REDAKCJI..................................................................................................................................9 WSTĘP..............................................................................................................................................10 CO POWINIENEŚ WIEDZIEĆ ..............................................................................................................10 UŻYCIE CZCIONEK...........................................................................................................................10 UKŁAD KSIĄŻKI ..............................................................................................................................11 Wymagania programu ...............................................................................................................11 Struktura programu ...................................................................................................................11 Techniki języka JavaScript.........................................................................................................11 Kierunki rozwoju........................................................................................................................11 O KODZIE ........................................................................................................................................11 PROGRAMOWANIE I TESTOWANIE ...................................................................................................11 PODZIĘKOWANIA ............................................................................................................................12 WPROWADZENIE .........................................................................................................................13 ZALETY JĘZYKA JAVASCRIPT ..........................................................................................................13 Prostota, szybkość i efektywność ...............................................................................................13 Wszechobecność.........................................................................................................................13 Redukcja obciążenia serwera ....................................................................................................14 JavaScript rozwija się ................................................................................................................14 Być może nie ma wyboru............................................................................................................14 I wiele innych zalet.....................................................................................................................14 PODSTAWOWA STRATEGIA PROGRAMOWANIA W JAVASCRIPT ........................................................14 Co może aplikacja?....................................................................................................................15 Kim są nasi odbiorcy .................................................................................................................15 Jak radzić sobie z przeszkodami? ..............................................................................................16 Uwzględniaj wszelkie używane przeglądarki ........................................................................16 Dyskretnie obniżaj jakość ......................................................................................................16 Mierz nisko ............................................................................................................................16 Mierz wysoko.........................................................................................................................16 Udostępniaj wiele wersji jednej aplikacji ..............................................................................16 UŻYCIE JĘZYKA JAVASCRIPT W PREZENTOWANYCH APLIKACJACH ................................................16 Wielokrotne użycie kodu przyszłością narodu ...........................................................................17 Wydzielanie JavaScriptu ............................................................................................................17 Deklarowanie zmiennych globalnych i tablic na początku........................................................17 Deklarowanie konstruktorów po zmiennych globalnych ...........................................................17 Definiowanie funkcji zgodnie z porządkiem „chronologicznym”..............................................17
2
Każda funkcja realizuje jedno zadanie ......................................................................................17 W miarę możliwości używaj zmiennych lokalnych.....................................................................17 NASTĘPNY KROK .............................................................................................................................18
WYSZUKIWANIE DANYCH PO STRONIE KLIENTA..........................................................19 WYMAGANIA PROGRAMU................................................................................................................21 STRUKTURA PROGRAMU .................................................................................................................22 Plik nav.html ..............................................................................................................................22 Plik records.js ............................................................................................................................25 Zmienne globalne.......................................................................................................................25 Funkcje.......................................................................................................................................26 validate() ................................................................................................................................27 convertString() .......................................................................................................................27 allowAny() .............................................................................................................................28 requireAll() ............................................................................................................................29 verifyManage() ......................................................................................................................31 noMatch()...............................................................................................................................31 formatResults() ......................................................................................................................32 Nagłówek i tytuł dokumentu HTML .....................................................................................32 Wyświetlanie tytułów, opisów i adresów URL dokumentów................................................33 Dodanie przycisków „Poprzedni” i „Następny” ....................................................................34 prevNextResults() ..................................................................................................................34 Tylko przycisk „Następne” ....................................................................................................34 Przyciski „Następne” i „Poprzednie”.....................................................................................35 Tylko przycisk „Poprzednie” .................................................................................................35 Kod HTML .................................................................................................................................38 TWORZENIE BAZY DANYCH W JĘZYKU JAVASCRIPT ........................................................................38 KIERUNKI ROZWOJU ........................................................................................................................39 Zgodność z językiem JavaScript 1.0...........................................................................................39 NICTJDO ...................................................................................................................................39 Odporność na błędy ...................................................................................................................40 Wyświetlanie reklam ..................................................................................................................40 Rozszerzenie możliwości wyszukiwania .....................................................................................40 Zapytania predefiniowane .........................................................................................................41
TEST SPRAWDZANY NA BIEŻĄCO.........................................................................................42 WYMAGANIA PROGRAMU................................................................................................................45 STRUKTURA PROGRAMU .................................................................................................................45 index.html – ramki .....................................................................................................................46
3
Spis treści
question.js – plik źródłowy JavaScript.......................................................................................47 administer.html ..........................................................................................................................49 Treść HTML ..........................................................................................................................51 Zmienne globalne...................................................................................................................52 Funkcje...................................................................................................................................53 itemReset() .........................................................................................................................53 shuffle()..............................................................................................................................53 buildQuestion() ..................................................................................................................54 gradeTest() .........................................................................................................................56 printResults()......................................................................................................................57 chickenOut() ......................................................................................................................59 KIERUNKI ROZWOJU ........................................................................................................................59 Uodpornienie na oszustwa .........................................................................................................59 Usuwanie odpowiedzi z tablicy .............................................................................................59 Usuwanie gradeTest() i modyfikacja buildQuestion()...........................................................60 Modyfikacja printResults() ....................................................................................................60 Przekształcenie na ankietę .........................................................................................................60
INTERAKTYWNA PREZENTACJA SLAJDÓW ......................................................................61 WYMAGANIA PROGRAMU................................................................................................................63 STRUKTURA PROGAMU....................................................................................................................63 Zmienne......................................................................................................................................66 Ustawienia domyślne warstwy DHTML ...............................................................................67 Zmienne związane z przeglądarkami.....................................................................................67 Zmienne związane z obrazkami.............................................................................................68 Zmienne automatycznego pokazu..........................................................................................68 Funkcje aplikacji........................................................................................................................68 Funkcje związane z warstwami..............................................................................................69 genLayer()..........................................................................................................................69 slide() .................................................................................................................................69 genScreen() ........................................................................................................................71 Elementy tablicy slideShow...............................................................................................74 Funkcje związane z obsługą obrazków..................................................................................75 preLoadImages() ................................................................................................................75 imageSwap() ......................................................................................................................75 Funkcje nawigacji ..................................................................................................................76 refSlide(), hideSlide(), showSlide(), menuManager() .......................................................76 changeSlide() .....................................................................................................................77 setSlide() ............................................................................................................................78 autoPilot() ..........................................................................................................................78 automate() ..........................................................................................................................79 KIERUNKI ROZWOJU ........................................................................................................................79 Losowy dobór slajdów w trybie automatycznym .......................................................................79 Animowane GIF-y i suwaki slajdów ..........................................................................................80 Animacja samych slajdów..........................................................................................................80
IMAGEMACHINE..........................................................................................................................97 WYMAGANIA PROGRAMU................................................................................................................99 STRUKTURA PROGRAMU ...............................................................................................................100 Krok 1. Załadowanie strony.....................................................................................................107 Krok 2. Określenie liczby par obrazków i ustawień domyślnych.............................................107 Krok 3. Określenie nazw plików, atrybutów HREF i tak dalej ................................................108 captureDefaultProfile() ........................................................................................................108 generateEntryForm()............................................................................................................109 genJavaScript() ....................................................................................................................110 Czas na decyzje....................................................................................................................112 Generowanie kodu ...............................................................................................................113 Krok 4. Wybór Podglądu w celu obejrzenia działania kodu ...................................................114 Krok 5. Wybór Zmiany danych w celu zrobienia poprawek ....................................................114 KIERUNKI ROZWOJU: DODANIE ATRYBUTÓW DO SZABLONU .........................................................114 Krok 1. Dodanie pól.................................................................................................................115 Krok 2. Tworzenie tablic w setArrays() ...................................................................................115 Krok 3. Pobieranie nowych ustawień domyślnych ..................................................................115 Krok 4. Dodanie pól tekstowych w generateEntryForm() .......................................................116 Krok 5. Odwołanie się do nowych wartości w genJavaScript() i ich użycie ..........................116 Krok 6. Generacja dodatkowego HTML w genJavaScript()....................................................116
SHOPPING BAG – WÓZEK SKLEPOWY STWORZONY W JAVASCRIPCIE ..............169 SHOPPING BAG W DWÓCH SŁOWACH ............................................................................................169 Etap 1. Ładowanie aplikacji ....................................................................................................170 Etap 2. Przeglądanie towarów i wybór....................................................................................171 Etap 3: Przeglądanie zamówienia i zmiany w nim ..................................................................173 Etap 4. Płacenie .......................................................................................................................175 WYMAGANIA PROGRAMU..............................................................................................................175 STRUKTURA PROGRAMU ...............................................................................................................176 ETAP 1. ŁADOWANIE APLIKACJI ....................................................................................................178 Elementy najwyższego poziomu ...............................................................................................179 inventory.js...............................................................................................................................180 Cechy produktów .................................................................................................................183 Cechy kategorii produktów..................................................................................................184 Tworzenie produktów i kategorii.........................................................................................185 Tworzenie koszyka na zakupy .............................................................................................186 ETAP 2. POKAZANIE TOWARÓW ....................................................................................................187 manager.html ...........................................................................................................................187 Zmienne ...............................................................................................................................194 display() ...............................................................................................................................195 Wyjątki od reguły ................................................................................................................195 Tworzenie wyświetlanej strony ...........................................................................................196 ETAP 3. POKAZANIE WSZYSTKICH KATEGORII...............................................................................197 Wyświetlenie pierwszego produktu ..........................................................................................197 Gdzie tu jest DHTML? ........................................................................................................198 ETAP 4. DODAWANIE PRODUKTÓW DO KOSZYKA ..........................................................................198 Wyszukiwanie produktów.........................................................................................................199 Odwzorowanie produktów i kategorii......................................................................................199 Przeszukiwanie istniejącej bazy danych ..................................................................................200 Obsługa nawigacji między produktami i kategoriami .............................................................200 Kod w łączach ..........................................................................................................................202 ETAP 5. ZMIANA ZAMÓWIENIA, PŁACENIE ....................................................................................202 Tworzenie list wyboru ..............................................................................................................205 Zapisywanie rachunku .............................................................................................................206 Opakowanie showBag(): pokazywanie podsumowań ..............................................................207 Przycisk Do kasy..................................................................................................................208 Koniec wyświetlania ................................................................................................................209 A po stronie serwera? ..............................................................................................................210 Przycisk Wyzeruj ilości ............................................................................................................210 Przycisk Zmiana koszyka .........................................................................................................210 Zapomniane funkcje .................................................................................................................210 KIERUNKI ROZWOJU ......................................................................................................................211 Inteligentniejsze towary ...........................................................................................................211 Zwiększenie możliwości wyszukiwania ....................................................................................211 Obsługa ciasteczek...................................................................................................................212
SZYFRY W JAVASCRIPCIE......................................................................................................213 JAK DZIAŁAJĄ SZYFRY ..................................................................................................................216 Kilka słów o łamaniu kodów ....................................................................................................216 Szyfr Cezara .............................................................................................................................217 Szyfr Vigenere ..........................................................................................................................217 WYMAGANIA PROGRAMU..............................................................................................................218 STRUKTURA PROGRAMU ...............................................................................................................218 Definiowanie szyfru .................................................................................................................222 Tworzenie szyfru z podstawianiem...........................................................................................224 Podstawowa metoda podstawiania..........................................................................................224 Różne podstawienia do różnych szyfrów..................................................................................225 Algorytm szyfru Cezara .......................................................................................................226 Algorytm szyfru Vigenere ...................................................................................................226 Jak działa shiftIdx ....................................................................................................................227 Obiekty SubstitutionCipher też są obiektami Cipher ...............................................................228 Tworzenie nowych obiektów SubstitutionCipher .....................................................................228 Dobór odpowiedniego szyfru ...................................................................................................230 Na koniec .................................................................................................................................230 KIERUNKI ROZWOJU ......................................................................................................................231
ELEKTRONICZNE ŻYCZENIA: POCZTA ELEKTRONICZNA METODĄ PRZENIEŚ I UPUŚĆ..........................................................................................................................................232 WYMAGANIA PROGRAMU..............................................................................................................234 STRUKTURA PROGRAMU ...............................................................................................................234 Pozostałe dwa dokumenty ........................................................................................................237 Co już wiemy ............................................................................................................................239 Proszę zająć miejsca!...............................................................................................................240 Śledzenie położenia myszy .......................................................................................................242 Wywoływanie wszystkich ikon..................................................................................................242 Przenoszenie ikon.....................................................................................................................242 Kiedy dokumenty już się załadują ............................................................................................243 Poznaj zmienne ........................................................................................................................247 Wyświetlanie życzeń.................................................................................................................248 Obrazki po kolei .......................................................................................................................250 Utrzymanie ikon na miejscu.....................................................................................................252 Sprawdzanie, co otrzymaliśmy.................................................................................................254 Ostateczne tworzenie kartki .....................................................................................................254 Wysyłanie życzeń......................................................................................................................256 Uwaga ......................................................................................................................................257 PO STRONIE SERWERA ...................................................................................................................257 KIERUNKI ROZWOJU ......................................................................................................................257 Dodanie łącza „wstecz” ..........................................................................................................257 Dodanie obrazków tematycznych.............................................................................................258 Banery reklamowe....................................................................................................................258
8
Życzenia bardziej interaktywne................................................................................................258
POMOC KONTEKSTOWA.........................................................................................................259 WYMAGANIA PROGRAMU..............................................................................................................261 STRUKTURA PROGRAMU ...............................................................................................................261 Pomoc kontekstowa..................................................................................................................262 Pokazywanie i ukrywanie dodatkowych informacji .................................................................264 Tworzenie warstw ....................................................................................................................266 KIERUNKI ROZWOJU ......................................................................................................................268 Spis treści .................................................................................................................................268 Przeszukiwanie plików pomocy................................................................................................269 Pytanie do specjalisty ..............................................................................................................269 Pomoc telefoniczna ..................................................................................................................269
9
Od redakcji Niniejsza książka to gotowy zestaw receptur – podobnie jak książka kucharska. O ”wirtualnym koszyku na zakupy” można myśleć jako o ”ciasteczkach cebulowych z pastą łososiową”. W każdym rozdziale podano kod i dokumentację przydatnej aplikacji zwykle napisanej całkowicie w JavaScripcie. Można wszystkie dania przygotowywać tak, jak to podał autor książki, ale można też sięgnąć do pewnych sugestii, aby wykorzystać je w swoich pomysłach. Na rynku znajdują się książki zawierające proste przepisy, jak zrobić jakieś drobiazgi i jak ozdobić JavaScriptem swoją stronę sieciową, natomiast w tej książce znajdziemy całe aplikacje sieciowe napisane w jedynym języku skryptowym, rozumianym przez przeglądarki. Skoro tyle już sobie wyjaśniliśmy, zastanówmy się, co należy rozumieć przez książkę z recepturami? Jej zadaniem nie jest na pewno podawanie treści w mało elastycznej formie, ale pomoc w tworzeniu kodu. Zapewne takie pozycje książkowe, zawierające receptury, będzie można spotkać coraz częściej. Richard Koman, Redaktor
Wstęp Czegoś dotychczas brakowało. Zgłębiałem stosy książek o JavaScripcie, oglądałem kolejne witryny sieciowe wprost ociekające kodem i pomysłami. Jednak kiedy już poznałem wszelakie nowe rodzaje składni i genialne techniki, nie wiedziałem, co z tą wiedzą mogę zrobić poza pokazywaniem przykładów. Czułem się tak, jakbym był w kuchni pełnej wszelakich składników jadła, ale bez żadnego przepisu. Znałem rozmaite sztuczki języka JavaScriptu i miałem różne kawałki kodu, ale nie potrafiłem tego zastosować do rozwiązania typowych problemów na stronach sieciowych. Oczywiście niektóre książki zawierały aplikacje JavaScript, ale nie były one odpowiednie do stosowania w Sieci. Oczko to świetna gra, arkusz kalkulacyjny to ciekawa aplikacja, ale trudno je zamieszczać na swoich stronach sieciowych. W tej książce znajduje się szereg przepisów. Nie tylko można się dowiedzieć, jak sprawdzić używaną przeglądarkę czy umożliwić przewijanie obrazków, ale można również znaleźć tu kompletne aplikacje, których będziesz mógł używać na swoich stronach. Aplikacje te nie będą tworzone krok po kroku, od razu zostaną zaprezentowane w całości. Można je kopiować do katalogu serwera sieciowego (lub komputera lokalnego) i natychmiast uruchamiać. Rozdziały tej książki naszpikowane są kodem JavaScript, który ma pomóc użytkownikom w realizowaniu typowych zadań, takich jak przeszukiwanie witryny, sporządzenie spisów treści, umożliwienie przewijania obrazków, oglądanie prezentacji, robienie zakupów i wiele innych. Oczywiście można te przykłady modyfikować tak, aby najlepiej pasowały do naszych potrzeb, ale i tak są one mniej więcej gotowe do użycia. Oprócz tego do każdej aplikacji dołączono dokładne objaśnienie jej działania, więc można sobie sprawdzać, jak zadziała zmiana poszczególnych fragmentów kodu.
Co powinieneś wiedzieć Nie jest to książka dla początkujących, gdyż nikt nie będzie tu nikogo uczył JavaScriptu, ale będzie można się dowiedzieć się, jak go używać. Nie trzeba być wiarusem JavaScriptu z trzyletnim stażem, jeśli jednak info.replace(/
Użycie czcionek Kursywa używana jest do podawania nazw plików, ścieżek katalogów, adresów URL. Pismo o stałej szerokości czcionki używana do przedstawiania znaczników HTML, fragmentów kodu, funkcji, nazw obiektów i innych odwołań do kodu. Kursywa o stałej szerokości czcionki stosowana jest do tekstu wprowadzanego przez użytkownika i tekstu podmienianego. Pismo o stałej szerokości czcionki, pogrubione zastosowano do tekstu wyświetlanego na ekranie.
11
Wstęp
Układ książki Większość rozdziałów ma podobny, czteroczęściowy układ.
Wymagania programu Ta krótka część określa środowisko wymagane do uruchomienia aplikacji. Zwykle podawana jest potrzebna wersja przeglądarek Netscape Navigator i Microsoft Internet Explorer. Tutaj także nakreśla się tło, omawiając zagadnienia związane ze skalowaniem i monitorowanie wydajności.
Struktura programu Kiedy już czytelnikowi znudzi się zabawa z aplikacją i będzie chciał zobaczyć, „co tam siedzi w środku”, powinien przeczytać tę część. Tutaj znajdzie omówienie kodu, zwykle wiersz po wierszu. Jest to część najdłuższa, więc warto usiąść sobie wygodnie, zanim zacznie się ją studiować.
Techniki języka JavaScript Kiedy będziemy przebijać się przez składnię, pojawią się miejsca, gdzie warto będzie zatrzymać się na chwilę i wskazać techniki, które można dodać do arsenału swoich środków.
Kierunki rozwoju W tej części sugerowane są metody dalszego rozwijania aplikacji. Czasami są to sugestie, a czasem gotowy kod. Zdarza się, że nie potrafię się powstrzymać i piszę za czytelnika kod znajdujący się w archiwum, które można załadować z Sieci. Tak czy inaczej, warto poczuć powiew twórczej weny, a nie ograniczać się tylko do upartego zapytywania: „Niezłe, jak to wprowadzić na moją stronę?”.
O kodzie Cała ta książka mówi o aplikacjach. Nie powinno być zatem zaskoczeniem, że znajduje się tutaj mnóstwo kodu. Niektóre aplikacje mają kilkaset wierszy, większość z nich to kolejne strony kodu. W niektórych wypadkach kod jest nawet powtarzany, aby czytelnik nie musiał co chwilę przerzucać kartek między kodem programu a jego omówieniem. Jedną z wad umieszczania kodu w książce jest... no cóż, właśnie umieszczenie go w książce. Często strona jest zbyt wąska, aby umieścić w jednym wierszu tyle, ile by się chciało. Fragment kodu często kontynuowany jest w następnym wierszu, i jeszcze w następnym... Aby zwiększyć czytelność, pominięto komentarze, choć znajdziemy je w plikach. Osoby odpowiedzialne za skład nieźle się napracowały, aby sformatować kod w miarę czytelnie, ale i tak czasem wygodniej może być czytać tenże kod w edytorze. Jako że kod powinien być używany, a nie tylko czytany, wszystkie aplikacje dostępne są w pliku ZIP, który można załadować ze strony sieciowej wydawnictwa O’Reilly. Warto udać się pod adres http://www.oreilly.com/catalog/jscook/index.html i odnaleźć łącze Download. W każdym rozdziale będziemy odwoływać się do tego pliku.
Programowanie i testowanie Poniżej – bez jakiejkolwiek szczególnej kolejności – podałem sprzęt i programy używane podczas tworzenia kodu prezentowanego w tej książce. W większości przypadków wszystko testowano w środowisku Windows, ale w środowiskach Unix i Mac także nie powinno być zbyt wielu problemów, a może nie będzie ich w ogóle. Sprzęt: IBM ThinkPad 55/P75/16M, Compaq Presario/P233/100M, IBM Aptiva C23/P120/128M, DELL OptiPlex/P2266/128M, Sun SPARC 20. Systemy operacyjne: Win95, WinNT Workstation 4.0, WinNT Server 4.0, Solaris 2.5. Przeglądarki: Netscape Navigator 3.0, 3.04 Gold, 4.0, 4.04, 4.07, 4.08, 4.5; Microsoft Internet Explorer 3.0, 3.02, 4.0, 4.01, 5.00. Rozdzielczości: 640x480, 800x600, 1024x768, 1152x900, 1280x1024. Oczywiście nie wszystkie aplikacje były testowane we wszystkich kombinacjach, jednak starałem się kodować jak najbardziej ostrożnie, aby w większości możliwych środowisk wszystko działało poprawnie.
12
Podziękowania Moje nazwisko znajduje się na okładce, mam więc obowiązek i przyjemność podziękować w tym miejscu wszystkim, którzy przyczynili się do powstania tej książki. Kwestie techniczne pomogli mi rozstrzygnąć: Steve Quint, James Chan, Jim Esten, Bill Anderson, Roland Chow, Rodney Myers, Matthew Mastracci, Giorgio Braga, Brock Beauchamp i inni – dlatego im właśnie chciałbym podziękować, zwłaszcza za wspomaganie mnie swoją znajomością JavaScriptu, a także innych zagadnień programowania. Specjalne podziękowania kieruję w stronę Patricka Clarka, którego kod był inspiracją aplikacji obsługi pomocy. Dziękuję redaktorowi Richardowi Komanowi, który był otwarty na moje pomysły i umożliwił przelanie ich na papier, a także Tarze McGoldrick i Robowi Romano za ich pracę ważną, choć niewidoczną. Serdeczne słowa podziękowania składam mojej żonie, Róndine Bradenbaugh, za to, że wytrzymała ze mną, kiedy przez miesiące wpatrywałem się w monitor i szaleńczo stukałem w klawiaturę, noc w noc. Chciałbym podziękować wreszcie moim rodzicom za ich wsparcie i zachęcanie mnie do rozwijania umiejętności pisarskich. Chciałbym też podziękować komuś, o kim często się zapomina – czytelnikom. To właśnie czytelnicy zostawiają w księgarni ciężko zarobione pieniądze, aby powstanie książki było w ogóle możliwe. Mam nadzieję, że wybór tej książki okaże się dla czytelnika inwestycją wartą wydanych pieniędzy.
Wprowadzenie Ta książka jest niezwykła, gdyż mówi o pisaniu w JavaScripcie dużych aplikacji sieciowych. Większość ludzi inaczej widzi zastosowania tego języka. JavaScript zwykle jest (a przynajmniej bywał) stosowany do dodawania do obrazków suwaków przewijania, realizacji liczników gości, określania stosowanej przeglądarki i temu podobnych rzeczy.
Zalety języka JavaScript Żaden język i żadna technologia nie uzyskały statusu najlepszego rozwiązania tworzenia aplikacji sieciowych. Każde z tych rozwiązań ma zalety i wady. Ostatnie postępy w języku JavaScript i innych upowszechniających się technik, jak DHTML, Java, a nawet Macromedia Flash, umożliwiają JavaScriptowi korzystanie z tych rozwiązań i tworzenie naprawdę złożonych systemów sieciowych. Oto jeszcze kilka innych argumentów przemawiających za tworzeniem aplikacji w JavaScripcie.
Prostota, szybkość i efektywność Jako że JavaScriptu dość łatwo jest się nauczyć, można od razu zacząć go używać. Jest to idealne rozwiązanie, kiedy chcemy dodać swojej witrynie nieco dodatkowej funkcjonalności. Kiedy masz już za sobą podstawy, do tworzenia naprawdę interesujących aplikacji już niedaleko. JavaScript jest językiem wysokiego poziomu z dużymi możliwościami. Nie sposób zrobić w tym języku niczego na poziomie maszynowym, ale dostępne są różne właściwości przeglądarki, stron, a czasami także systemu, w którym przeglądarka działa. JavaScript nie musi być kompilowany, jak Java™ czy C, a przeglądarka nie wymaga ładowania maszyny wirtualnej do uruchomienia kodu. Po prostu aplikację się koduje i uruchamia. JavaScript działa także w architekturze obiektowej, podobnie jak Java i C++. Cechy języka, takie jak konstruktory i dziedziczenie oparte na prototypach, dodają nowy poziom abstrakcji. Dzięki temu możliwe jest wielokrotne wykorzystanie tego kodu w naprawdę dużym stopniu.
Wszechobecność JavaScript jest zdecydowanie najbardziej popularnym językiem skryptowym w Sieci. Nie tysiące, ale miliony witryn sieciowych zawierają JavaScript. Język ten jest obsługiwany przez większość najpopularniejszych przeglądarek (choć tak naprawdę mówimy o JScript w Internet Explorerze). Zarówno Netscape, jak i Microsoft zdają się stale poszukiwać
14 sposobów rozbudowania tego języka. Takie wsparcie oznacza, że mamy naprawdę duże szanse na to, że przeglądarka naszego gościa będzie ten język obsługiwała.1
Redukcja obciążenia serwera To właśnie była jedna z podstawowych przyczyn tak powszechnego przyjęcia JavaScriptu. Język ten pozwala zrealizować po stronie klienta wiele funkcji, które inaczej musiałyby być wykonywane na serwerze. Jednym z najlepszych przykładów jest sprawdzanie poprawności danych. Programiści starej szkoły mogą pamiętać, jak kilka lat temu jedyną metodą sprawdzania poprawności danych, wprowadzonych w formularzu HTML, było przesłanie tych danych na serwer sieciowy i przekazanie ich skryptowi CGI w celu ich sprawdzenia. Jeśli dane nie miały żadnych błędów, skrypt CGI działał dalej normalnie. Jeśli znalezione zostały jakieś błędy, skrypt zwracał użytkownikowi komunikat, opisując problem. Zastanówmy się teraz, jakiego obciążenia zasobów to wymaga. Przesłanie formularza wymaga żądania HTTP z serwera. Po podróży danych przez Sieć skrypt CGI powtórnie jest uruchamiany. Za każdym razem, kiedy użytkownik się pomyli, cały proces się powtarza. Użytkownik musi czekać na przesłanie komunikatu o błędzie, zanim dowie się, gdzie się pomylił. Teraz mamy do dyspozycji JavaScript, dlatego możemy sprawdzać zawartość poszczególnych elementów formularza przed odesłaniem ich na serwer sieciowy. Dzięki temu zmniejsza się liczba transakcji HTTP i zdecydowanie zmniejsza się prawdopodobieństwo, że użytkownik pomyli się przy wprowadzaniu danych. JavaScript może też odczytywać i zapisywać „ciasteczka” (cookies), co dotąd wymagało odpowiedniego użycia nagłówków w serwerze sieciowym.
JavaScript rozwija się Kiedy pojawił się JavaScript 1.1, nowe właściwości: obiekt Image oraz tablica document.images, pozwalające przewijać obrazki – spowodowały szerokie poruszenie. Później pojawił się JavaScript 1.2. Otworzyły się nowe możliwości: obsługa DHTML, warstwy i mnóstwo innych udoskonaleń zdumiały wielu programistów. Wszystko to było zbyt piękne, by mogło być prawdziwe. Na tym jednak się nie skończyło. JavaScript stał się standaryzowanym językiem skryptowym powszechnego zastosowania, zgodnym z EMCA-262. Przynajmniej jedna firma stworzyła środowisko mogące uruchamiać JavaScript z wiersza poleceń. Firma Macromedia wstawiła odwołania JavaScript do technologii Flash. Do ColdFusion firmy Allaire włączono JavaScript do technologii opartej na XML Web Distributed Data Exchange (WDDX, Wymiana Danych Rozproszonych przez Sieć). JavaScript ma coraz więcej właściwości, coraz więcej opcji... i coraz więcej pułapek.
Być może nie ma wyboru Czasami mamy przed sobą tylko jedną możliwą drogę. Załóżmy, że nasz dostawca usług internetowych nie pozwala uruchamiać skryptów CGI. Co teraz, jeśli chcemy dodać do swojej strony wysyłanie poczty elektronicznej z formularza lub pragniemy skorzystać z ciasteczek? Musimy zająć się rozwiązaniami działającymi po stronie klienta. JavaScript spośród tego typu rozwiązań jest zdecydowanie najlepszy.
I wiele innych zalet Istnieje jeszcze sporo innych zalet stosowania JavaScriptu, a i czytelnik z pewnością może tę listę jeszcze dalej rozszerzyć. Najważniejsze jest to, że mimo szeregu zalet technologii realizowanych po stronie serwera aplikacje JavaScript mają swoje miejsce w Sieci.
Podstawowa strategia programowania w JavaScript Kiedy budujemy jakąkolwiek aplikację, czy to w JavaScript, czy nie, w naszym dobrze zrozumianym interesie jest mieć jakąś strategię działania. Dzięki temu łatwiej będzie uporządkować swoje pomysły, szybciej także uda się wszystko zakodować i przetestować. Istnieje mnóstwo publikacji opisujących dokładnie tworzenie aplikacji. Czytelnik musi wybrać strategię działania najlepiej mu odpowiadającą, nie sposób zatem tutaj zbytnio w ten temat się zagłębiać. Jeśli jednak będziemy pisać coś między znacznikami <SCRIPT> a , to pamiętanie o pewnych zasadach projektowych niejednokrotnie zaoszczędzi nam bólu głowy. Jest to naprawdę proste – trzeba odpowiedzieć sobie po prostu na pytania: co? kto? jak?
1
Pamiętajmy jednak, że często użytkownicy wyłączają obsługę JavaScriptu. Dobrze jest od razu sprawdzić, czy użytkownik obsługę tego języka włączył, a przynajmniej na pierwszej stronie witryny poinformować o konieczności włączenia JavaScriptu (przyp. tłum.).
15 Wprowadzenie
Co może aplikacja? Najpierw musimy ustalić, czym aplikacja ma się zajmować. Załóżmy, że chcemy wysyłać pocztę elektroniczną z formularza. Odpowiedzmy na takie pytania: •
Ile pól będzie zawierał formularz?
•
Czy użytkownicy będą sami podawali adres, czy będą go wybierali z listy?
•
Czy dane przed wysłaniem mają być sprawdzane? Jeśli tak, co zamierzamy sprawdzać? Wiadomość? Adres e-mail? Jedno i drugie?
•
Co dzieje się po wysłaniu listu? Czy następuje przekierowanie użytkownika na inną stroną, czy nic się nie zmienia?
Ta seria pytań z pewnością będzie dłuższa. Dobrą nowiną jest to, że jeśli na tyle pytań odpowiemy, będziemy znacznie lepiej wiedzieć, co właściwie chcemy osiągnąć.
Kim są nasi odbiorcy Zidentyfikowanie adresatów informacji jest ogromnie ważne dla określenia wymagań wobec aplikacji. Upewnijmy się, że dokładnie znamy odpowiedzi przynajmniej na pytania podane niżej: •
Jakich przeglądarek będą używali odbiorcy? Netscape Navigator? Jakie wersje: 2.x, 3.x, 4.x czy wyższe?
•
Czy aplikacja będzie używana w Internecie, intranecie, czy lokalnie na komputerze?
•
Czy możemy określić rozdzielczość używanych przez użytkowników monitorów?
•
W jaki sposób użytkownicy będą przyłączeni do Sieci? Modemem 56K, przez łącze ISDN, łączem E1 lub E3?
Można by sądzić, że wszystkie pytania – poza pytaniem o przeglądarkę – nie mają nic wspólnego z JavaScriptem. „Łączność? Kogo to obchodzi? Nie muszę konfigurować routerów ani innych tego typu rzeczy”. Tak, to prawda. Nie trzeba być certyfikowanym inżynierem Cisco. Przejrzyjmy szybko te pytania, jedno po drugim, i zobaczmy, co może być szczególnie ważne. Używana przeglądarka jest jedną z najważniejszych rzeczy. W zasadzie im nowsza przeglądarka, tym nowszą wersję JavaScriptu obsługuje. Jeśli na przykład nasi odbiorcy są wyjątkowo przywiązani do NN 2.x i MSIE 3.x (choć nie ma żadnego po temu powodu), automatycznie możemy wykreślić przewijanie obrazków – wersje JavaScript i JScript nie obsługują obiektów Image ani document.images.2 Jako że większość użytkowników przeszła na wersje 4.x tych przeglądarek, przewijanie obrazków jest dopuszczalne. Teraz jednak musimy radzić sobie z konkurującymi modelami obiektów. Oznacza to, że Twoje aplikacje muszą być przenośne między przeglądarkami lub musisz pisać osobne aplikacje dla każdej przeglądarki i jej wersji (co może być nadaremną nauką). Gdzie będzie znajdowała się aplikacja? W Internecie, w intranecie, czy też może na pojedynczym komputerze przerobionym na stanowisko informacyjne? Odpowiedź na to pytanie da także szereg innych wytycznych. Jeśli na przykład aplikacja będzie działała w Internecie, możemy być pewni, że do strony będą dobijały się wszelkie istniejące przeglądarki i będą używały aplikacji (a przynajmniej będą próbowały to zrobić). Jeśli aplikacja działa tylko w intranecie lub na pojedynczym komputerze, przeglądarki będą standardowe dla danego miejsca. Kiedy to piszę, jestem konsultantem w firmie będącej jednym z dużych sklepów Microsoftu. Jeśli używany przeze mnie kod okazuje się zbyt dużym wyzwaniem dla Netscape Navigatora i przeglądarka ta sobie z nim nie radzi, to nie muszę się tym przejmować, gdyż użytkownicy i tak korzystają z Internet Explorera. Ważną rzeczą jest rozdzielczość monitora. Jeśli na stronę wstawiliśmy tabelę o szerokości 900 pikseli, a użytkownicy mają do dyspozycji rozdzielczość tylko 800x600, nie zauważą części nasz ciężkiej pracy. Czy można liczyć na to, że wszyscy użytkownicy będą używali jakiejś określonej rozdzielczości? W przypadku Internetu odpowiedź jest negatywna. Jeśli chodzi o intranet, można mieć szczęście. Niektóre firmy standaryzują komputery PC, oprogramowanie, przeglądarki, monitory, a nawet stosowaną rozdzielczość. Nie można też pominąć zagadnień związanych z łącznością. Załóżmy, że stworzyliśmy sekwencję animowaną, która zarobi tyle, co średni film Stevena Spielberga (jeśli to się uda, to może powinniśmy... hm... współpracować). Dobrze, ale użytkownicy modemów 56K zapewne przed ściągnięciem tego filmu zdążą pójść do kina i ten film obejrzeć, zanim ściągną nasz film. Większość użytkowników jest w stanie pogodzić się z tym, że Sieć może się na chwilę zapchać, ale 2
Niektóre przeglądarki Internet Explorer 3.x dla Maca obsługiwały jednak przewijanie obrazków (przyp. aut.).
16 po jakiejś minucie większość z nich uda się na inne strony. Trzeba więc w swoich planach brać pod uwagę także przepustowość łączy.
Jak radzić sobie z przeszkodami? Żonglowanie wszystkimi tymi zagadnieniami może wydawać się dość proste, ale rzecz nie jest wcale taka łatwa. Może się okazać, że nie będzie się w stanie obsłużyć wszystkich wersji przeglądarek, rozdzielczości ekranu lub szczegółów związanych z łącznością. Co teraz? Jak uszczęśliwić wszystkich i nadal zachwycać ich obrazkiem o wielkości 500 kB? Warto rozważyć jedną lub więcej z poniższych propozycji. Przeczytaj je wszystkie, aby móc podjąć decyzję, mając komplet informacji.
Uwzględniaj wszelkie używane przeglądarki Ta szalenie demokratyczna metoda polega na daniu możliwie najlepszych wyników jak największej liczbie odbiorców. Takie kodowanie jest zapewne najpowszechniej stosowanym i najlepszym podejściem. Oznacza to, że starasz się przede wszystkim obsłużyć użytkowników używających Internet Explorera 4.x i 5.x oraz Netscape Navigatora 4.x. Jeśli zrealizujesz wykrywanie ważniejszych przeglądarek i zakodujesz aplikację tak, aby korzystała z najlepszych cech wersji 4.x, uwzględniając przy tym różnice między przeglądarkami, będziesz mógł zrobić wrażenie na naprawdę dużej części użytkowników.
Dyskretnie obniżaj jakość To jest naturalny wniosek wynikający z poprzedniej strategii. Jeśli na przykład mój skrypt zostanie załadowany do przeglądarki nieobsługującej nowych cech, otrzymam paskudne błędy JavaScriptu. Warto używać wykrywania przeglądarki, aby w przypadku niektórych przeglądarek wyłączyć nowe cechy. Analogicznie można ładować różne strony stosownie do różnych rozdzielczości monitora.
Mierz nisko To podejście polega na założeniu, że wszyscy używają przeglądarki Netscape Navigator 2.0, ekranu w rozdzielczości 640x480, modemu 14,4K oraz procesora Pentium 33 MHz. Od razu zła wiadomość: nie można zastosować niczego poza JavaScriptem 1.0. Nie ma mowy o przewijaniu, o warstwach, wyrażeniach regularnych czy technologiach zewnętrznych (pozostaje podziękować za możliwość użycia ramek). Teraz wiadomość dobra: nasza aplikacja zawędruje pod strzechy. Jednak wobec ostatnich zmian samego JavaScriptu nawet to ostatnie nie musi być prawdą. Mierzę naprawdę nisko, ale rozsądnym założeniem wydaje mi się zakładanie użycia NN 3.x i MSIE 3.x. Pozostawanie nieco z tyłu ma swoje zalety.
Mierz wysoko Jeśli odbiorca nie ma Internet Explorera 5.0, nie zobaczy naszej aplikacji, a tym bardziej – nie będzie jej używał. Dopiero w tej wersji można śmiało korzystać z obiektowego modelu dokumentów Internet Explorera, modelu zdarzeń, wiązania danych i tak dalej. Nadmierne oczekiwania dotyczące wersji przeglądarek odbiorców mogą zdecydowanie okroić krąg potencjalnej publiczności.
Udostępniaj wiele wersji jednej aplikacji Można napisać szereg wersji swojej aplikacji, na przykład jedną dla Netscape Navigatora, inną dla Internet Explorera. Taki sposób działania nadaje się jednak tylko dla osób dobrze znoszących monotonię, jednak może on przynieść jedną wyraźną korzyść. Przypomnijmy sobie, co było mówione o łączności z Siecią. Jako że często nie da się sprawdzić szerokości pasma użytkowników, można pozwolić im dokonać wyboru. Część łączy ze strony głównej umożliwi użytkownikom z połączeniem E1 ładować pełną grafikę, natomiast użytkownicy modemów będą mogli skorzystać z wersji okrojonej.
Użycie języka JavaScript w prezentowanych aplikacjach Opisane strategie są strategiami podstawowymi. W przykładach z tej książki użyto różnych strategii. Warto jeszcze wspomnieć o konwencjach programowania w JavaScripcie. W ten sposób lepiej zrozumiesz przyjęte przeze mnie rozwiązania oraz ustalisz, czy są one dobre w każdej sytuacji. Pierwsze pytanie o aplikację powinno dotyczyć tego, czy przyda się ona do czegoś gościom odwiedzającym stronę. Każda aplikacja rozwiązuje jeden lub więcej podstawowych problemów. Wyszukiwanie i wysyłanie wiadomości, pomoc kontekstowa, sprawdzanie lub zbieranie informacji, przewijanie obrazków i tak dalej – to są rzeczy, które lubią
17 Wprowadzenie sieciowi żeglarze. Jeśli planowana aplikacja nie znalazła dostatecznego uzasadnienia swojego zaistnienia, nie poświęcałem jej swojego czasu. Następną kwestią jest to, czy JavaScript pozwala osiągnąć potrzebną mi funkcjonalność. To było dość łatwe. Jeśli odpowiedź brzmiała „tak”, stawałem do walki. Jeśli nie, to tym gorzej dla JavaScriptu. Potem przychodziła kolej na edytor tekstowy. Oto niektóre zasady używane w prezentowanych kodach.
Wielokrotne użycie kodu przyszłością narodu To właśnie tutaj do głosu dochodzą pliki źródłowe JavaScriptu. W aplikacjach tych używane są pliki z kodem, ładowane za pomocą następującej składni: <SCRIPT LANGUAGE="JavaScript1.1" SRC="jakisPlikJS.js">
Plik jakisPlikJS.js zawiera kod, który będzie używany przez różne skrypty. W wielu aplikacjach tej książki używane są pliki źródłowe JavaScriptu. To się sprawdza. Po co wymyślać coś od nowa? Można także użyć plików źródłowych w celu ukrycia kodu źródłowego przed resztą aplikacji. Wygodne może być umieszczenie bardzo dużej tablicy JavaScriptu w pliku źródłowym. Użycie plików źródłowych jest tak ważne, że poświęciłem im cały rozdział 6. Niektóre aplikacje zawierają kod po prostu wycinany i wklejany z jednego miejsca w inne. Kod taki może być dobrym kandydatem na wydzielenie w plik źródłowy. Wklejanie stosowałem po to, aby zbyt często nie powtarzać „zajrzyj do kodu pliku bibliotecznego trzy rozdziały wcześniej”. W ten sposób cały czas masz kod przed oczami, póki go nie zrozumiesz. Kiedy już aplikacja na stronie będzie działała tak, jak sobie tego życzysz, zastanów się nad wydzieleniem części kodu do plików źródłowych.
Wydzielanie JavaScriptu Między znacznikami i należy umieszczać możliwie dużo kodu w pojedynczym zestawie <SCRIPT>.
Deklarowanie zmiennych globalnych i tablic na początku Nawet jeśli globalne zmienne i tabele mają początkowo wartości pustych ciągów lub wartości nieokreślone, definiowanie ich blisko początku skryptu jest dobrym sposobem poradzenia sobie z nimi, szczególnie jeśli używane są w różnych miejscach skryptu. W ten sposób nie musisz przeglądać zbyt wiele kodu, aby zmienić wartość zmiennej: wiesz, że znajdziesz ją gdzieś na początku.
Deklarowanie konstruktorów po zmiennych globalnych W zasadzie funkcje tworzące obiekty definiowane przez użytkownika umieszczam blisko początku skryptu. Robię tak po prostu dlatego, że większość obiektów musi powstać na początku działania skryptu.
Definiowanie funkcji zgodnie z porządkiem „chronologicznym” Innymi słowy – staram się definiować funkcje w takiej kolejności, w jakiej będą wywoływane. Pierwsza funkcja definiowana w skrypcie jest wywoływana na początku, druga funkcja jest wywoływana jako następna, i tak dalej. Czasem może to być trudne lub wręcz niemożliwe, ale dzięki temu przynajmniej poprawia się sposób zorganizowania aplikacji i pojawia się szansa, że funkcje wywoływane po sobie będą w kodzie obok siebie.
Każda funkcja realizuje jedno zadanie Staram się ograniczyć funkcjonalność poszczególnych funkcji tak, aby każda z nich realizowała dokładnie jedno zadanie: sprawdzała dane od użytkownika, odczytywała lub ustawiała ciasteczka, przeprowadzała pokaz slajdów, pokazywała lub ukrywała warstwy i tak dalej. Teoria jest doskonała, ale czasem trudno ją wcielić w życie. W rozdziale 5. zasadę tę bardzo mocno naruszam. Funkcje tamtejsze realizują pojedyncze zadania, ale są naprawdę długie.
W miarę możliwości używaj zmiennych lokalnych Stosuję tę zasadę w celu zaoszczędzenia pamięci. Jako że zmienne lokalne JavaScriptu znikają zaraz po zakończeniu realizacji funkcji, w której się znajdują, zajmowana przez nie pamięć jest zwracana do puli systemu. Jeśli zmienna nie musi istnieć przez cały czas działania aplikacji, nie tworzę jej jako globalnej, lecz jako lokalną.
18
Następny krok Teraz powinieneś mieć już jakieś pojęcie o tym, jak przygotować się do tworzenia aplikacji JavaScript i jak tworzę swoje aplikacje. Bierzmy się więc do zabawy.
Cechy aplikacji: Wydajne wyszukiwanie po stronie klienta Wiele algorytmów wyszukiwania Sortowanie i dzielenie wyników wyszukiwania Skalowalność Łatwe zapewnienie zgodności z JavaScriptem 1.0 Prezentowane techniki: Użycie separatorów wewnątrz łańcuchowych do rozdzielania pól rekordów Zagnieżdżanie pętli for Rozsądne użycie metody document.write()
1
Zastosowanie operatora trójargumentowego
Wyszukiwanie danych po stronie klienta Mechanizm wyszukiwawczy może się przydać w każdej witrynie, czy jednak trzeba zmuszać serwer do przetwarzania wszystkich zgłaszanych w ten sposób zapytań? Prezentowane tu rozwiązanie umożliwia realizację przeszukiwania stron WWW całkowicie po stronie klienta. Zamiast przesyłać zapytania do bazy danych lub do serwera aplikacji, użytkownik pobiera „bazę danych” wraz z żądanymi stronami. Baza ta jest zwykłą tablicą JavaScriptu, zawierającą w każdym elemencie pojedynczy rekord. Takie podejście daje kilka znaczących korzyści – głównie redukcję obciążenia serwera i skrócenie czasu odpowiedzi. Nie należy zapominać, że opisywana tu aplikacja jest ograniczona zasobami klienta, szczególnie szybkością procesora i dostępną pamięcią. Mimo to w wielu przypadkach może ona okazać się doskonałym narzędziem. Kod programu zamieszczono w katalogu ch01 archiwum przykładów, zaś na rysunku 1.1 pokazano pierwszy ekran aplikacji. Program udostępnia dwie metody wyszukiwania, nazwane umownie3 AND i OR. Poszukiwanie informacji może odbywać się według tytułu i opisu lub według adresu URL dokumentu. Z punktu widzenia użytkownika obsługa aplikacji jest całkiem prosta: wystarczy wpisać szukany termin i nacisnąć Enter. Możliwości wyszukiwania są następujące: •
Wprowadzenie słów rozdzielonych znakami spacji zwróci wszystkie rekordy zawierające dowolny z podanych terminów (logiczne „lub” – OR).
•
Umieszczenie znaku plus (+) przed wyszukiwanym łańcuchem spowoduje wybranie rekordów zawierających wszystkie podane hasła (logiczne „i” – AND).
•
Wpisanie przed częścią lub całym adresem ciągu url: spowoduje wybranie rekordów odpowiadających fragmentowi lub całości podanego adresu URL.
3
Nazwy te pochodzą od oznaczeń operatorów warunkowych (przyp. tłum.).
20
Rysunek 1.1. Rozpoczynamy wyszukiwanie Pamiętaj o archiwum przykładów! Jak napisano we wstępie, wszystkie prezentowane w książce programy można pobrać w postaci pliku ZIP z witryny internetowej wydawcy; plik znajduje się pod adresem http://www.helion.pl/catalog/jscook/ index.html.
Na rysunku 1.2 pokazano wyniki wykonania prostego zapytania. Wykorzystano tu domyślną metodę wyszukiwania (bez przedrostków), a szukanym słowem było javascript. Każde przeszukanie powoduje utworzenie „na bieżąco” strony zawierającej wyniki, po których znajduje się łącze pozwalające odwołać się do strony z krótką instrukcją obsługi. Przydatna byłaby też możliwość wyszukiwania danych według adresów URL. Na rysunku 1.3 pokazano stronę wyników wyszukiwania zrealizowanego przy użyciu przedrostka url:, który nakazuje wyszukiwarce sprawdzanie jedynie adresów URL. W tym przypadku szukanym łańcuchem był tekst html, co spowodowało zwrócenie wszystkich rekordów zawierających w adresie ten właśnie ciąg. Krótki opis dokumentu poprzedzony jest tym razem jego adresem URL. Metoda wyszukiwania adresów URL ograniczona jest do pojedynczych dopasowań (tak jak przy wyszukiwaniu domyślnym), nie powinno to jednak być problemem – niewielu użytkowników ma ambicje tworzenia złożonych warunków wyszukiwania adresów. Opisywana tu aplikacja pozwala ograniczyć liczbę wyników prezentowanych na pojedynczej stronie i umieszcza na niej przyciski pozwalające na wyświetlenie strony następnej lub poprzedniej, co chroni użytkownika przed zagubieniem w kilometrowym tasiemcu wyników. Liczba jednocześnie prezentowanych wyników zależy od programisty; ustawieniem domyślnym jest 10.
21 Rozdział 1 - Wyszukiwanie danych po stronie klienta
Rysunek 1.2. Typowa strona z wynikami wyszukiwania
Rysunek 1.3. Strona z wynikami wyszukiwania według adresów URL
Wymagania programu Nasza aplikacja wymaga przeglądarki obsługującej język JavaScript 1.1. Jest to dobra wiadomość dla osób używających przeglądarek Netscape Navigator 3 i 4 oraz Internet Explorer 4 i 5, zła natomiast dla użytkowników IE 3. Osoby, którym zależy na zgodności z wcześniejszymi wersjami, nie muszą się jednak martwić. Nieco dalej, w podrozdziale Kierunki rozwoju, pokażemy jak zadowolić także użytkowników Internet Explorera 3 (choć kosztem możliwości funkcjonalnych programu). Wszelkie programy uruchamiane po stronie klienta zależne są od zasobów wykonującego je komputera, co w naszym przypadku jest szczególnie widoczne. O ile można założyć, że zasoby klienta całkowicie wystarczą do uruchomienia samego kodu, przekazanie mu dużej bazy danych (ponad 6-7 tysięcy rekordów) spowoduje spadek wydajności, a w skrajnym przypadku może doprowadzić do zablokowania komputera.
22 Testując bazę danych zawierającą nieco mniej niż 10 tysięcy rekordów w przeglądarkach Internet Explorer 4 i Netscape Navigator 4, autor nie doświadczył żadnych problemów. Plik źródłowy z danymi miał jednak ponad 1 MB; używany komputer miał od 24 do 128 MB pamięci RAM. Próba wykonania tego samego zadania z użyciem przeglądarki Netscape Navigator 3.0 Gold doprowadziła jednak do przepełnienia stosu – po prostu tablica zawierała zbyt wiele rekordów. Z drugiej strony wersja zakodowana w języku JavaScript 1.0 i wykonywana w przeglądarce Internet Explorer 3.02 na komputerze IBM ThinkPad pozwalała wykorzystać co najwyżej 215 rekordów. Nie należy jednak przerażać się tą liczbą – używany do testowania laptop był tak stary, że słychać było, jak szczur biegając w kółko napędza dynamo do zasilania procesora. Większość użytkowników powinna dysponować sprzętem umożliwiającym przetworzenie większej ilości danych.
Struktura programu Omawiana aplikacja składa się z trzech plików HTML (index.html, nav.html oraz main.html) i pliku źródłowego zapisanego w JavaScripcie (records.js). Trzy dokumenty w języku HTML zawierają uproszczony zestaw ramek, stronę początkową pozwalającą wprowadzać wyszukiwane hasła oraz stronę z instrukcjami, wyświetlaną domyślnie w głównej ramce.
Plik nav.html Najważniejsza część aplikacji znajduje się w pliku o nazwie nav.html. Okazuje się zresztą, że jedynym miejscem, w którym jeszcze można znaleźć kod w języku JavaScript, są generowane na bieżąco strony wyników. Przyjrzyjmy się treści przykładu 1.1. Przykład 1.1. Zawartość pliku nav.html 1 2 3 4 5 6
Tekst źródłowy jest dość obszerny. Aby zrozumieć, co się tutaj dzieje, najprościej będzie rozpocząć analizę od początku i stopniowo posuwać się coraz dalej. Na szczęście kod zapisano tak, aby układ poszczególnych funkcji był mniej więcej zgodny z kolejnością ich użycia. Analizę kodu źródłowego przeprowadzimy w następującej kolejności: •
plik źródłowy records.js,
•
zmienne globalne,
•
funkcje,
•
kod w języku HTML.
Plik records.js Na początek zajmiemy się plikiem źródłowym records.js. Odwołanie do niego umieszczono w znaczniku <SCRIPT> w wierszu 5. Plik ten zawiera dość długą tablicę o nazwie profiles. Ze względu na spore rozmiary, jego zawartość została w książce pominięta. Po rozpakowaniu pliku ZIP trzeba będzie zatem uruchomić edytor tekstów i otworzyć plik ch01/records.js (uwaga: to baza danych, z której będziemy korzystać!). Każdy element bazy jest trzyczęściowym łańcuchem o postaci np.: "http://www.serve.com/hotsyte|HotSyte-Zasoby JavaScriptu|Witryna" + "HotSyte zawiera łącza, samouczki, darmowe skrypty i inne"
Elementy rekordu rozdzielone są znakami kreski pionowej (|). Znaki te zostaną użyte w chwili wyświetlania odszukanych rekordów bazy na ekranie. Drugą część rekordu stanowi tytuł dokumentu (nie mający jednak nic wspólnego z zawartością znacznika TITLE), część trzecia to opis dokumentu, zaś pierwszy element rekordu to adres URL. Na marginesie – nie ma żadnych przeciwwskazań odnośnie rozdzielania elementów rekordu znakami (lub ciągami znaków) innymi niż „|”. Należy tylko zapewnić, że nie będzie to żaden ze znaków, które użytkownik mógłby wpisać w treści zapytania (mamy do dyspozycji choćby ciągi &^ czy ~[%). Nie należy także stosować lewego ukośnika (\): znak ten interpretowany jest przez JavaScript jako początek sekwencji unikowej i jego użycie może spowodować zwrócenie dziwacznych wyników wyszukiwania lub nawet zawieszenie aplikacji. Dlaczego wszystkie te dane umieszczono w pliku źródłowym zapisanym w JavaScripcie? Są ku temu dwie przesłanki: modułowość kodu i czystość zapisu. W przypadku witryn zawierających więcej niż kilkaset pojedynczych stron, plik rekordów najwygodniej będzie generować z użyciem programu uruchamianego na serwerze; zapisanie danych w postaci pliku źródłowego w JavaScripcie jest w tym przypadku rozwiązaniem nieco lepszym. Opisanej tu bazy danych można też użyć w innych aplikacjach wyszukujących, po prostu wstawiając w kodzie odwołanie do pliku records.js. Co więcej, włączenie całego kodu w JavaScripcie do pliku HTML i wyświetlenie go w postaci źródłowej byłoby wysoce niepożądane.4
Zmienne globalne W wierszach 9 do 16 przykładu 1.1 deklarujemy i inicjalizujemy zmienne globalne: var var var var var var var var
I jeszcze jedno: jeśli użytkownik wyłączy obsługę JavaScriptu w przeglądarce, będzie bardzo zdziwiony, kiedy pobierając stronę z serwera stwierdzi, że zawierający ją plik ma ponad 1 MB. Można sądzić, że szybko opuści taką stronę, aby już na nią nie wracać (przyp. tłum.).
26
Techniki języka JavaScript: użycie separatorów wewnątrz łańcuchowych do rozdzielania pól rekordów Opisywana tu aplikacja bazuje na wyszukiwaniu fragmentów informacji, podobnie jak ma to miejsce w bazie danych. Aby zrealizować podobny schemat wyszukiwania, program w JavaScripcie może analizować (przeszukiwać) tablicę jednolicie sformatowanych danych. Na pierwszy rzut oka mogłoby się wydawać, że wystarczy umieścić każdy element (adres URL lub tytuł strony) w oddzielnym elemencie tablicy. Rozwiązanie takie będzie działało, ale może sprawiać sporo kłopotów. Liczbę elementów tablicy globalnej można znacznie zredukować, łącząc poszczególne łańcuchy za pomocą separatora (na przykład |) w jeden element. Podczas analizowania poszczególnych elementów tablicy używa się następnie metody split() obiektu String w celu utworzenia oddzielnej tablicy dla każdego z elementów. Innymi słowy, zamiast globalnej tablicy: var records = new Array("Czterej pancerni", "pies", "i ich wehikuł")
można wewnątrz funkcji zdefiniować tablicę lokalną, na przykład var records = "Czterej pancerni|pies|i ich wehikuł".split('|');
Można by pomyśleć: „sześć takich, pół tuzina innych – co za różnica?”. Otóż w pierwszej wersji deklarujemy trzy elementy globalne, które zajmują pamięć, póki się ich nie pozbędziemy. W drugim przypadku deklarujemy tylko jeden element globalny. Trzy elementy tworzone przez funkcję split('|') podczas przeszukiwania są tylko tymczasowe, gdyż tworzone są lokalnie. Interpreter JavaScriptu likwiduje zmienną records po wykonaniu funkcji wyszukiwania, zwalniając tym samym pamięć; zmniejsza się także ilość kodu. Autor preferuje drugą możliwość. Do zagadnienia tego wrócimy po przyjrzeniu się fragmentowi kodu odpowiedzialnemu za analizę danych. Oto znaczenie poszczególnych zmiennych: SEARCHANY
Nakazuje wyszukiwanie dowolnego z wprowadzonych słów. SEARCHALL
Nakazuje wyszukiwanie wszystkich wprowadzonych słów. SEARCHURL
Nakazuje wyszukiwanie według adresu URL (dowolnego z wprowadzonych słów). searchType
Określa sposób szukania (zmienna ta uzyskuje wartość SEARCHANY, SEARCHALL lub SEARCHURL). showMatches
Określa liczbę rekordów wyświetlanych na jednej stronie wyników. currentMatch
Identyfikuje rekord wyświetlany jako pierwszy na bieżącej stronie wyników. copyArray
Przechowuje kopię tymczasowej tablicy wyników, używaną do wyświetlania następnej lub poprzedniej grupy. docObj
Identyfikuje dokument znajdujący się w drugiej ramce. Nie jest to zbyt istotne dla samej aplikacji, ale pomaga utrzymać porządek w kodzie, gdyż podczas wyświetlania wyników wyszukiwania trzeba wielokrotnie odwoływać się do wspomnianego obiektu (parent.frames[1].document). Zastąpienie tego odwołania zmienną docObj pozwala zmniejszyć ilość kodu i tworzy centralny punkt, w którym dokonuje się wszelkich zmian.
Funkcje Przyjrzyjmy się teraz najważniejszym funkcjom.
27 Rozdział 1 - Wyszukiwanie danych po stronie klienta
validate() Kiedy użytkownik naciśnie klawisz Enter, funkcja validate() z wiersza 18 ustala, czego i jak należy szukać. Pamiętajmy tu o trzech zdefiniowanych wcześniej możliwościach: •
Wyszukiwanie tytułu i opisu dokumentu; wymagane jest dopasowanie dowolnego hasła.
•
Wyszukiwanie tytułu i opisu dokumentu; wymagane jest dopasowanie wszystkich haseł.
•
Wyszukiwanie adresu URL lub ścieżki dokumentu; wymagane jest dopasowanie dokładnie jednego hasła.
Funkcja validate()określa przedmiot i sposób wyszukiwania na podstawie pierwszych kilku przekazanych jej znaków. Metodę wyszukiwania ustala się za pomocą zmiennej searchType. Jeśli użytkownik chce wyszukiwać dane według dowolnego z podanych haseł, zmienna ta ma wartość SEARCHANY. W przypadku wyszukiwania według wszystkich podanych wyrazów, przyjmuje ona wartość SEARCHALL (jest to zresztą ustawienie domyślne). Jeśli wreszcie użytkownik wybierze wyszukiwanie według adresów, zmienna searchType przyjmuje wartość SEARCHURL. Cały proces przebiega następująco: W wierszu 19 za pomocą metody charAt() obiektu String sprawdzamy, czy pierwszym znakiem napisu jest plus (+). Jeśli zostanie on odnaleziony, należy użyć drugiej metody wyszukiwania (iloczyn logiczny). if (entry.charAt(0) == "+") { entry = entry.substring(1,entry.length); searchType = SEARCHALL; }
W wierszu 23 wykorzystujemy metodę substring() obiektu String do wyszukania ciągu url:. W przypadku jego odnalezienia ustawiana jest odpowiednio zmienna searchType: if (entry.substring(0,4) == "url:") { entry = entry.substring(5,entry.length); searchType = SEARCHURL; }
A co robi metoda substring() w wierszach 20 i 24? Kiedy funkcja validate() ustali już, jak ma być wykonywane wyszukiwanie, odpowiednie znaki (+ oraz url:) przestają być potrzebne. Wobec tego validate() usuwa odpowiednią liczbę znaków z początku łańcucha i kontynuuje działanie. Jeśli na początku napisu nie ma żadnego z powyższych ciągów, zmiennej searchType nadawana jest wartość SEARCHANY. Przed wywołaniem funkcji convertString() wykonywane jest jeszcze drobne czyszczenie – instrukcje while w wierszach 28 i 32 usuwają zbędne odstępy (białe znaki) z początku i końca łańcucha. Po określeniu sposobu wyszukiwania i usunięciu odstępów należy upewnić się, że zostało jeszcze coś do wyszukiwania. W wierszu 36 sprawdzamy, czy poszukiwany łańcuch ma przynajmniej trzy znaki. Wyniki wyszukiwania krótszego łańcucha mogą być mało przydatne, ale ustawienie to można w razie potrzeby zmienić: if (entry.length < 3) { alert("Nie możesz wyszukiwać tak krótkich łańcuchów. Wysil się trochę."); document.forms[0].query.focus(); return;
Jeśli wszystko poszło prawidłowo, validate() wywołuje funkcję convertString(), przekazując jej gotowy łańcuch zapytania (entry).
convertString() Funkcja convertString() realizuje dwie związane z sobą operacje: rozdziela łańcuch na elementy tablicy i wywołuje odpowiednią funkcję wyszukiwania. Metoda split() obiektu String dzieli wprowadzony przez użytkownika napis w miejscach wystąpienia znaków spacji, a wynik wstawia do tablicy searchArray. Realizowane jest to w pokazanym niżej wierszu 45: var searchArray = reentry.split(" ");
Jeśli na przykład użytkownik wpisze w polu wyszukiwania tekst „aplikacje JavaScript klient”, w tablicy searchArray znajdą się wartości aplikacje, JavaScript i klient (odpowiednio w elementach 0, 1 i 2). Następnie, zależnie od wartości searchType, funkcja convertString() wywołuje odpowiednią funkcję (wiersze 46 i 47): if (searchType == (SEARCHALL)) { requireAll(searchArray); } else { allowAny(searchArray); }
Jak widać, wywoływana jest jedna z dwóch funkcji – allowAny() lub requireAll(). Oba warianty zachowują się podobnie, ale też nieco się różnią. Omówimy je poniżej.
28
allowAny() Jak sugeruje sama nazwa (ang. może być dowolny), funkcja ta jest wywoływana w przypadku, gdy aplikacja ma zwrócić rekordy pasujące do przynajmniej jednego słowa. Oto zawartość wierszy 50–68: function allowAny(t) { var findings = new Array(0); for (i = 0; i < profiles.length; i++) { var compareElement = profiles[i].toUpperCase(); if(searchType == SEARCHANY) { var refineElement = compareElement.substring(0,compareElement.indexOf('|HTTP')); } else { var refineElement = compareElement.substring(compareElement.indexOf('|HTTP'), compareElement.length); } for (j = 0; j < t.length; j++) { var compareString = t[j].toUpperCase(); if (refineElement.indexOf(compareString) != -1) { findings[findings.length] = profiles[i]; break; }
Obydwie funkcje wyszukujące działają w oparciu o porównywanie napisów w zagnieżdżonych pętlach for. Więcej informacji na ten temat zamieszczono w ramce Zagnieżdżanie pętli. Pętle for dochodzą do głosu w wierszach 52 i 63. Pierwsza z nich ma za zadanie przejrzenie wszystkich elementów tablicy profiles (z pliku źródłowego). Dla każdego elementu tej tablicy druga pętla sprawdza wszystkie elementy zapytania przekazane przez funkcję convertString(). Aby zabezpieczyć się przed pominięciem któregoś z wyszukiwanych rekordów na skutek wpisania haseł z użyciem różnej wielkości liter, w wierszach 53 i 64 zadeklarowano zmienne lokalne compareElement i compareString, przypisując im następnie rekord i szukany łańcuch zapisane wielkimi literami. Dzięki temu nie będzie miało znaczenia, czy użytkownik wpisze słowo „JavaScript”, „javascript”, czy nawet „jAvasCRIpt”. W funkcji allowAny() nadal trzeba zdecydować, czy przeszukiwać bazę według tytułu i opisu dokumentu, czy według adresu URL. Wobec tego zmienną lokalną refineElement, zawierającą napis porównywany z szukanymi słowami, należy ustawić stosownie do wartości searchType (wiersze 55 lub 59). Jeśli searchType ma wartość SEARCHANY, zmiennej refineElement przypisywany jest fragment tekstu zawierający tytuł i opis dokumentu pobrany z rekordu. W przeciwnym przypadku searchType musi mieć wartość SEARCHURL, wobec czego wartością refineElement staje się tekst zawierający adres URL dokumentu. Przypomnijmy sobie symbole kreski pionowej, pozwalające programowi na rozdzielenie poszczególnych części rekordów. Metoda substring() zwraca łańcuch zaczynający się od pozycji zerowej i kończący się przed ciągiem „|HTTP” lub napis zaczynający się od pierwszego „|HTTP” i ciągnący się aż do końca elementu tablicy. Teraz można porównywać rekord z danymi wpisanymi przez użytkownika (wiersz 65): if (refineElement.indexOf(compareString) != -1) { findings[findings.length] = profiles[i]; break; }
Znalezienie ciągu compareString w łańcuchu refineElement oznacza trafienie (najwyższy czas!). Pierwotna zawartość rekordu (zawierająca adres URL) przepisywana jest do tablicy findings w wierszu 66. Przy dopisywaniu nowych elementów jako indeksu można użyć wartości findings.length. Po znalezieniu pasującego elementu nie ma już powodu dalej sprawdzać rekordu. W wierszu 67 znajduje się instrukcja break, która przerywa działanie pętli porównującej for. Nie jest to konieczne, ale zmniejsza ilość pracy, którą trzeba wykonać. Po przeszukaniu wszystkich rekordów i znalezieniu wszystkich szukanych słów, w wierszach 95 do 101 funkcja searchAny() przekazuje znalezione rekordy z tablicy findings do funkcji verifyManage().Sukces wyszukiwania powoduje wywołanie funkcji formatResults() wyświetlającej dane. W przeciwnym przypadku funkcja noMatch()informuje użytkownika, że nie udało się znaleźć żądanych przez niego informacji. Funkcje formatResults() oraz noMatch() zostaną omówione w dalszej części rozdziału. Teraz zakończmy badanie metod wyszukiwania, omawiając funkcję requireAll().
29 Rozdział 1 - Wyszukiwanie danych po stronie klienta
requireAll() Jeśli na początku wyszukiwanego łańcucha znajdzie się znak plus (+), wywołana zostanie funkcja requireAll(). Jest ona niemal identyczna, jak allowAny(), wyszukuje jednak wszystkie wpisane przez użytkownika słowa. W przypadku allowAny() rekord był dodawany do zbioru wynikowego, gdy tylko dopasowano którykolwiek wyraz; tym razem trzeba poczekać na porównanie z rekordem wszystkich słów; dopiero wtedy można (ewentualnie) dodać go do zbioru wynikowego. Całość zaczyna się w wierszu 74. function requireAll(t) { var findings = new Array(); for (i = 0; i < profiles.length; i++) { var allConfirmation = true; var allString = profiles[i].toUpperCase(); var refineAllString = allString.substring(0, allString.indexOf('|HTTP')); for (j = 0; j < t.length; j++) { var allElement = t[j].toUpperCase(); if (refineAllString.indexOf(allElement) == -1) { allConfirmation = false; continue; } } if (allConfirmation) { findings[findings.length] = profiles[i]; } } verifyManage(findings); }
Na pierwszy rzut oka funkcja ta jest bardzo podobna do allowAny(). Zagnieżdżone pętle for, konwersja wielkości liter, zmienna potwierdzająca – to wszystko już znamy. Różnica pojawia się w wierszach 79–80: var refineAllString = allString.substring(0, allString.indexOf('|HTTP'));
Zwróćmy uwagę, że nie sprawdzamy zawartości zmiennej searchType, jak miało to miejsce w funkcji allowAny() w wierszu 50. Nie ma takiej potrzeby – requireAll() wywoływana jest tylko wtedy, gdy zmienna searchType ma wartość SEARCHALL (wiersz 46). Wyszukiwanie według adresów URL nie umożliwia użycia iloczynu wszystkich słów, wiadomo zatem, że porównywać należy tytuł i opis dokumentu. Funkcja requireAll() jest nieco bardziej wymagająca. Jako że w porównywanym napisie należy znaleźć wszystkie podane przez użytkownika wyrazy, warunki wyboru będą bardziej wymagające niż w allowAny(). Przyjrzyjmy się wierszom 83 do 86: if (refineAllString.indexOf(allElement) == -1) { allConfirmation = false; continue; }
Znacznie łatwiej będzie odrzucić rekord w momencie stwierdzenia pierwszej niezgodności, aniżeli sprawdzać, czy liczba „trafień” odpowiada liczbie wyszukiwanych haseł. Zatem jeśli tylko rekord nie spełni któregoś z warunków, instrukcja continue nakaże programowi pominąć go i przejść do analizy następnego rekordu. Jeżeli po porównaniu z zawartością rekordu wszystkich szukanych słów zmienna allConfirmation ma nadal wartość true, oznacza to spełnienie kryteriów wyszukiwania. allConfirmation przyjmuje wartość false, jeśli rekord nie pasuje do któregokolwiek z szukanych wyrazów. Rekord bieżący dodawany jest do zbioru wynikowego zawartego w tymczasowej tablicy findings (wiersz 89). Tym razem trudniej jest spełnić warunek, ale wyniki będą zapewne dokładniejsze. Po sprawdzeniu w ten sposób wszystkich rekordów, wartość zmiennej findings przekazywana jest do funkcji verifyManage(). Jeśli stwierdzono jakieś trafienia, wywoływana jest funkcja formatResults(). W przeciwnym przypadku verifyManage() wywołuje funkcję noMatch(), przekazującą użytkownikowi złe wieści.
30
Techniki języka JavaScript: zagnieżdżanie pętli Obie funkcje wyszukujące – allowAny() i requireAll() – używają zagnieżdżonych pętli for. Jest to wygodna technika obsługi tablic wielowymiarowych. W języku JavaScript tablice są for malnie rzecz biorąc jednowymiarowe, ale możliwe jest też symulowanie tablicy wielowymiarowej, jak opisano poniżej. Przyjrzyjmy się pięcioelementowej, jednowymiarowej tablicy: var liczby = ("jeden", "dwa", "trzy", "cztery", "pięć");
Aby porównać łańcuch z kolejnymi wartościami, wystarczy wykonać pętlę for (lub while) porównując kolejne elementy tablicy z zadanym tekstem: for (var i = 0; i < liczby.length; i++) { if (myString == liczby[i]) { alert("Znalazłem!"); break; } }
Nic trudnego, możemy zatem podjąć następne wyzwanie. Tablica wielowymiarowa to po prostu tablica tablic, na przykład: var new new new );
liczby = new Array { Array("jeden", "dwa", "trzy", "cztery", "pięć"), Array("uno", "dos", "tres", "cuatro", "cinco"), Array("won", "too", "tree", "for", "fife")
Pojedyncza pętla for tutaj nie wystarczy – trzeba się bardziej przyłożyć. Pierwsza tablica liczby jest jednowymiarowa (1×5), ale jej nowa wersja jest już tablicą wielowymiarową (3×5). Przeanalizowanie piętnastu elementów (3×5) oznacza, że będziemy potrzebować dodatkowej pętli: for (var i = 0; i < liczby.length; i++) { for (var j = 0; j < liczby[i].length; j++) { if (myString == liczby[i][j]) { alert("Znalazłem!"); break; } } }
// pierwsza... // i druga
Tym razem kolejne odpowiedzi sprawdzamy w dwóch wymiarach. Pójdźmy teraz jeszcze o krok dalej i załóżmy, że chcemy stworzyć tablicę z „bezpieczną” paletą 216 kolorów, których można używać we wszystkich przeglądarkach, po jednym kolorze w komórce? Odpowiedzią są zagnieżdżone pętle for. Tym razem jednak użyjemy tablicy zaledwie jednowymiarowej. W notacji szesnastkowej „bezpieczne” kolory zapisywane są w postaci sześciu cyfr (po dwie na każdą barwę składową) przy czym wszystkie składniki muszą być parami cyfr: 33, 66, 99, AA, CC lub FF. Tablica będzie zatem wyglądała tak: var hexPairs = new Array("33", "66", "99", "AA", "CC", "FF");
„Lipa! Tu jest tylko jedna tablica jednowymiarowa – oddawać pieniądze!” Spokojnie, nie ma co jeszcze biec do księgarni. Będą trzy wymiary, tyle że dla każdego użyjemy tej samej tablicy: var str = ''; // Utworzenie tablicy document.writeln('
Bezpieczna paleta WWW
' + '
'); for (var i = 0; i < hexPairs.length; i++) { // tworzenie wiersza document.writeln('
'); for (var j = 0; j < hexPairs.length; j++) { for (var k = 0; k < hexPairs.length; k++) { // Tworzenie ciągu "pustych" komórek wiersza // Zauważmy, że kolor tła jest tworzony z elementów hexPairs str += '
' + '  
'; }
31 Rozdział 1 - Wyszukiwanie danych po stronie klienta // Wypisz wiersz komórek i "wyzeruj" str document.writeln(str); str = ''; } // Koniec wiersza document.writeln('
'); } // Koniec tablicy document.writeln('
');
Uruchomienie tego kodu w przeglądarce (plik znajduje się w archiwum przykładów pod nazwą \Ch01\websafe.html) da nam tablicę 6×36, czyli z 216 (6×6×6) kolorami, których można bezpiecznie używać w każdej przeglądarce . Trzy pętle for odpowiadają trzem wymiarom. Oczywiście tablicę z paletą można modyfikować na różne sposoby, nam jednak chodzi o pokazanie, jak można sobie radzić z różnymi problemami programistycznymi przy użyciu zagnieżdżonych pętli for.
verifyManage() Jak można się było domyślać, funkcja ta określa, czy wyszukiwanie dało jakieś rezultaty i wywołuje jedną z wyprowadzających je funkcji. Zaczynamy od wiersza 95: function verifyManage(resultSet) { if (resultSet.length == 0) { noMatch(); } else { copyArray = resultSet.sort(); formatResults(copyArray, currentMatch, showMatches); } }
Zarówno allowAny(), jak i requireAll() wywołują funkcję verifyManage() po zrealizowaniu odpowiedniego schematu wyszukiwania, przekazując jej tablicę findings jako argument. W wierszu 96 pokazano wywołanie funkcji noMatch() w przypadku, gdy tablica resultSet (kopia findings) jest pusta. Jeśli resultSet zawiera co najmniej jeden rekord pasujący do szukanych haseł, globalnej zmiennej copyArray nadawana jest wartość stanowiąca alfabetycznie uporządkowaną wersję zbioru wszystkich elementów tablicy resultSet. Sortowanie nie jest konieczne, ale wydatnie pomaga w uporządkowaniu zbioru wynikowego, a ponadto zwalnia od troszczenia się o kolejność dodawania nowych rekordów do tablicy profiles. Można je dodawać zawsze na końcu – i tak zostaną w końcu posortowane, jeśli tylko będą wybrane. Po co zatem tworzyć od nowa zestaw danych, które i tak już mamy? Pamiętajmy, że findings jest zmienną lokalną, a więc tymczasową. Po zakończeniu wyszukiwania (czyli wykonaniu jednej z funkcji wyszukujących), tablica findings zniknie. I bardzo dobrze: po co trzymać wszystko w pamięci, która może nam się przydać do innych celów? Dane trzeba jednak gdzieś przechować. Jako że przeglądarka wyświetla, dajmy na to, 10 rekordów na stronie, użytkownik widzieć będzie jedynie część wyników. Zmienna copyArray jest globalna, więc sortowanie danych i wpisywanie ich do niej nie zaszkodzi zawartości zbioru wynikowego. Użytkownik może oglądać wyniki w grupach po 10, 15 i tak dalej; zmienna copyArray będzie przechowywała wszystkie znalezione rekordy aż do chwili wykonania nowego zapytania. Ostatnią czynnością funkcji verifyManage() jest wywołanie formatResults() i przekazanie jej wartości currentMatch, będącej indeksem rekordu, który ma być wyświetlony jako pierwszy, oraz wartości showMatches, określającej liczbę wyświetlanych na stronie rekordów. Zarówno currentMatch, jak i showMatches to zmienne globalne, które nie znikają po wykonaniu funkcji. Będziemy potrzebować ich podczas całego czasu pracy aplikacji.
noMatch() Funkcja noMatch() (ang. brak dopasowania) robi to, co sugeruje jej nazwa. Jeśli zapytanie nie zwróci żadnych wyników, ma ona przekazać użytkownikowi złe wieści. Jest krótka i prosta, zaś generowana przez nią strona wyników (a raczej braku wyników) informuje, że wpisane przez użytkownika hasła nie odpowiadają żadnemu z rekordów bazy. Zaczyna się od wiersza 103: function noMatch() { docObj.open(); docObj.writeln('<TITLE>Wyniki wyszukiwania' + '' +
32 '
' + '
' + '"' + document.forms[0].query.value + '" - nic nie znaleziono.' + '
formatResults() Zadaniem tej funkcji jest eleganckie zaprezentowanie użytkownikowi znalezionych wyników. Nie jest ona nadmiernie skomplikowana, ale realizuje sporo zadań. Oto elementy składające się na listę wyników: •
Nagłówek, tytuł i treść dokumentu HTML.
•
Tytuł znalezionego dokumentu, jego opis oraz adres URL dla każdego znalezionego rekordu, wraz z łączem do zawartego w rekordzie adresu URL.
•
Przyciski „Poprzedni” i „Następny”, służące do wyświetlania rekordów poprzednich i następnych, o ile takowe istnieją.
Nagłówek i tytuł dokumentu HTML Utworzenie nagłówka i tytułu strony jest proste. Wiersze 116 do 129 drukują nagłówek, tytuł i początek treści dokumentu HTML: function formatResults(results, reference, offset) { var currentRecord = (results.length < reference + offset ? results.length : reference + offset); docObj.open(); docObj.writeln('\n\n<TITLE>Wyniki wyszukiwania\n ' + '' + '
Przed wydrukowaniem nagłówka i tytułu trzeba sprawdzić, od którego rekordu należy zacząć wyprowadzanie. Wiadomo, że pierwszy wyświetlany rekord znajduje się w elemencie results [reference]. Należy wyświetlić offset rekordów, chyba że wartość reference + offset jest większa niż liczba rekordów. Aby to sprawdzić, znów używamy operatora trójargumentowego, zaś odpowiednią wartość umieszczamy w zmiennej currentRecord w wierszu 117. Użyjemy jej wkrótce. Teraz funkcja formatResults() wyprowadza nagłówek i tytuł dokumentu HTML. Treść dokumentu zaczyna się od wyśrodkowanej tabeli i poziomej linii. Wyświetlenie treści zapytania, pobranej z odpowiedniego pola formularza, nie jest żadnym problemem (wiersz 125): parent.frames[0].document.forms[0].query.value
W wierszu 126 rzecz się trochę komplikuje, zaczyna się bowiem zbiór wynikowy. Najpierw wyświetlana jest informacja o prezentowanym właśnie podzbiorze – jego wielkość i liczba wszystkich wyników, na przykład: Wyniki wyszukiwania:
1 - 10 z 38
Potrzeba do tego trzech liczb: numeru pierwszego rekordu podzbioru, liczby wyświetlanych jednorazowo rekordów oraz rozmiaru tablicy copyArray, w której znajdują się wybrane rekordy. Przyjrzyjmy się im po kolei, pamiętając, że nie chodzi tutaj o pokazanie samych wyników, ale o poinformowanie użytkownika, ile jest rekordów i od którego zaczynamy. Dzieje się to tak: 1. Numer bieżącego rekordu przypisujemy zmiennej reference i wyświetlamy ją. 2. Dodajemy do reference wartość offset, określającą liczbę rekordów wyświetlanych na stronie (tutaj 10). 3. Jeśli suma reference + offset jest większa niż liczba znalezionych odpowiedzi, wyświetlamy liczbę odpowiedzi, w przeciwnym przypadku – obliczoną sumę (wartość ta została już określona i znalazła odbicie w zmiennej currentRecord). 4. Wyświetlamy łączną liczbę wyników.
33 Rozdział 1 - Wyszukiwanie danych po stronie klienta Kroki 1 i 2 są proste. Przypomnijmy sobie kod funkcji verifyManage(), szczególnie wiersz 99: formatResults(copyArray, currentMatch, showMatches);
Zmienna lokalna results to kopia tablicy copyArray. Zmienna reference otrzymuje wartość currentMatch, więc suma reference + offset jest równa sumie currentMatch i showResults. W pierwszych kilku wierszach kodu (ściśle – 13 i 14) zmiennej showMatches nadajemy wartość 10, a zmiennej currentMatch wartość 0. Wobec tego reference ma na początku wartość 0, zaś suma reference + offset równa jest 10. Krok 1 wykonywany jest bezpośrednio po wyprowadzeniu zmiennej reference; opisana wyżej arytmetyka obsługuje krok 2. W kroku 3 używamy operatora trójargumentowego (wiersze 117–118) do zdecydowania, czy suma wartości reference i offset jest większa od całkowitej liczby wyników. Innymi słowy, należy sprawdzić, czy po dodaniu offset rekordów do reference uzyskamy liczbę większą od całkowitej liczby wyników. Jeśli reference ma wartość 20, a rekordów jest 38, dodanie wartości 10 do reference da 30 i na ekranie pojawi się: Wyniki wyszukiwania:
20 - 30 z 38
Jeśli jednak reference ma wartość 30, a rekordów jest 38, dodanie do reference wartości 10 da 40, co dałoby: Wyniki wyszukiwania:
30 - 40 z 38
a to jest niedopuszczalne. Wyszukiwarka nie może wyświetlić rekordów 39 i 40, bo znalazła ich tylko 38. Oznacza to osiągnięcie końca zbioru wynikowego, a zatem zamiast sumy należy wyświetlić całkowitą liczbę rekordów. W ten sposób dochodzimy do ostatniego, czwartego kroku: Wyniki wyszukiwania:
30 - 38 z 38
Treść funkcji formatResults() upstrzona jest znakami specjalnymi, jak np. \n i \t. \n to znak nowego wiersza, czyli odpowiednik naciśnięcia klawisza Enter podczas pisania w edytorze tekstów. \t to odpowiednik naciśnięcia klawisza tabulacji. Obecność znaków specjalnych ma poprawić wygląd kodu źródłowego strony wyników wyszukiwania. Wstawiono je tu po to, aby pokazać sposób ich użycia, ale należy pamiętać, że nie są one niezbędne dla programów i że nie mają wpływu na ich działanie. Jeśli ktoś uważa, że zaśmiecają kod, wcale nie musi ich używać. W dalszej części książki będą one stosowane sporadycznie.
Wyświetlanie tytułów, opisów i adresów URL dokumentów Teraz, gdy podzbiór rekordów został już określony, czas go wyświetlić. Do głosu dochodzą wiersze 130 do 143: if (searchType == SEARCHURL) { for (var i = reference; i < currentRecord; i++) { var divide = results[i].split("|"); docObj.writeln('\t
Wiersze 131 i 138 zawierają pętle for, wykonujące na rekordzie currentRecord tę samą operację – jedyną różnicą jest kolejność wyprowadzania poszczególnych fragmentów. Do głosu ponownie dochodzi zmienna searchType. Jeśli jest ona równa SEARCHURL, jako tekst łącza wyświetlany jest adres URL. W przeciwnym razie, tj. dla wartości SEARCHANY lub SEARCHALL – tytuł dokumentu. Sposób wyszukiwania jest już określony, ale jak ładnie wyświetlić rekordy? Wystarczy przebiec w pętli przez cały podzbiór rekordów, rozbijając każdy z nich na tytuł, opis oraz adres URL i rozmieszczając uzyskane elementy w odpowiednim porządku. Oto pętla for używana dla wszystkich przypadków: for (var i = reference; i < lastRecord; i++) {
Teraz zajmijmy się elementami rekordów. Jak pamiętamy z opisu pliku records.js, każdy element tablicy profiles to łańcuch opisujący rekord i składający się z części rozdzielonych znakami |. Rozdzielimy je tak: var divide = results[i].split('|');
34 Dla każdego elementu zmiennej lokalnej divide przypisywana jest tablica łańcuchów rozdzielonych znakami |. Pierwszy jej element (divide[0]) to adres URL, drugi (divide[1]) to tytuł dokumentu, trzeci zaś (divide[2]) to jego opis. Każdy z elementów jest podczas wyprowadzania uzupełniany odpowiednim zestawem znaczników HTML (w naszym przypadku
,
i
). Jeśli wyszukiwanie odbywa się według adresów URL, będą one stanowiły treść łączy; w przeciwnym przypadku użyte zostaną tytuły dokumentów.
Dodanie przycisków „Poprzedni” i „Następny” Ostatnią czynnością jest dodanie przycisków, które pozwolą użytkownikowi oglądać poprzednią i następną porcję wyników. Obsługą tych przycisków zajmuje się funkcja prevNextResults(), którą wkrótce omówimy, ale teraz kilka ostatnich wierszy funkcji formatResults(): docObj.writeln('\n\t
Zacytowany tu kod wywołuje funkcję prevNextResults(), dodaje kilka zamykających znaczników HTML i ustawia kursor w polu treści zapytania.
prevNextResults() Wszystkich, którym udało dobrnąć się aż tutaj, i ta funkcja nie powinna zanadto zmęczyć. Definicja funkcji prevNextResults() zaczyna się w wierszu 152: function prevNextResults(ceiling, reference, offset) { docObj.writeln('
'); }
Funkcja ta wyświetla HTML-owy formularz zawierający dwa przyciski – „Następne” i „Poprzednie” – i wyśrodkowany w dolnej części strony wyników. Na rysunku 1.3 pokazano stronę wyników z obydwoma przyciskami. Mogą one pojawić się w trzech kombinacjach: •
Tylko przycisk „Następne”. Jest on wyświetlany dla pierwszego podzbioru wyników; nie ma jeszcze żadnych poprzednich rekordów.
•
Przyciski „Następne” i „Poprzednie”. Wyświetlane są one dla wszystkich stron poza pierwszą i ostatnią; istnieją rekordy poprzednie, są też rekordy dalsze.
•
Tylko przycisk „Poprzednie”. Pojawia się na ostatniej stronie wyników – dalej nie ma już żadnych rekordów do przeglądania.
Trzy kombinacje, dwa przyciski. Oznacza to, że aplikacja musi wiedzieć, czy dany przycisk ma być wyświetlany, czy też nie. Poniżej opisano warunki określające pojawianie się poszczególnych kombinacji:
Tylko przycisk „Następne” Kiedy powinien pojawić się przycisk „Następne”? Na wszystkich stronach poza ostatnią, czyli zawsze wtedy, gdy ostatni rekord na stronie (reference + offset) ma numer mniejszy od całkowitej liczby wyników. Kiedy nie powinien pojawić się przycisk „Poprzednie”? Na pierwszej stronie wyników, czyli kiedy wartość reference uzyskana ze zmiennej currentMatch wynosi 0.
35 Rozdział 1 - Wyszukiwanie danych po stronie klienta
Przyciski „Następne” i „Poprzednie” Kiedy należy wyświetlić oba przyciski? Skoro przycisk „Następne” powinien znajdować się na wszystkich stronach poza ostatnią, a „Poprzednie” na wszystkich poza pierwszą, przycisku „Poprzednie” będziemy potrzebować, kiedy wartość reference będzie większa od 0, a przycisku „Następne” – gdy suma reference + offset będzie mniejsza od całkowitej liczby wyników.
Tylko przycisk „Poprzednie” Skoro wiemy, kiedy powinien pojawić się przycisk „Poprzednie”, to kiedy powinien zniknąć „Następne”? Kiedy wyświetlana jest ostatnia strona wyników, czyli gdy suma reference + offset jest większa bądź równa całkowitej liczbie wyników. Podany tu opis jest nadal dość ogólny, ale przynajmniej wiadomo już, kiedy i które przyciski mają być wyświetlane, zaś obsługą warunków zajmują się instrukcje if z wierszy 154 i 160. Umieszczają one na stronie jeden lub oba przyciski w zależności od aktualnego podzbioru wynikowego i liczby pozostałych do wyprowadzenia wyników.
36
Techniki języka JavaScript: rozsądne użycie metody document.write() Przyjrzyj się jeszcze raz funkcji formatResults(). Kod w języku HTML zapisywany jest przez wywołanie metody document.write() lub document.writeln(). Przekazywany jej tekst jest zwykle długi i rozciąga się na kolejne wiersze, połączone operatorem +. Można by się spierać, czy taki kod jest czytelniejszy niż wywoływanie dla każdego wiersza metody document.writeln(), istnieje jednak ważki argument przemawiający na korzyść pierwszego sposobu. O co chodzi? Przyjrzyjmy się fragmentowi funkcji formatResults(): function formatResults(results, reference, offset) { docObj.open(); docObj.writeln('\n\n<TITLE>Wyniki wyszukiwania\n' + '' + '
Jak widać, za pomocą pojedynczego wywołania metody zapisujemy tekst całej strony. Alternatywą jest wielokrotne wywoływanie metody w celu wyprowadzenia pojedynczych wierszy: function formatResults(results, reference, offset) { docObj.open(); docObj.writeln('\n\n<TITLE>Wyniki wyszukiwania\n'); docObj.writeln(''); docObj.writeln('
Wygląda to może trochę porządniej, ale każde wywołanie oznacza nieco więcej pracy dla interpretera JavaScriptu. Pomyślmy: czy wygodniej jest pięć razy iść do sklepu, za każdym razem kupując jakiś drobiazg, czy iść do sklepu raz i od razu kupić wszystko, czego nam trzeba? Przekazanie długiego łańcucha „sklejonego” znakami + rozwiązuje problem skutecznie i za jednym zamachem. W wyniku kliknięcia obu przycisków wywoływana jest funkcja formatResults(); jedyna różnica to przekazywane jej parametry, opisujące różne podzbiory wyników. Z technicznego punktu widzenia przyciski są w gruncie rzeczy takie same, różnią się natomiast wyglądem ze względu na różnice w wartościach atrybutu VALUE. Oto początek definicji przycisku „Poprzednie” (wiersze 155-156): docObj.writeln('
Z kolei przycisk „Następne” definiowany jest w wierszach 164–165: docObj.writeln('
Jeśli zmienna trueTop jest większa od 1, howMany przyjmuje wartość „ów”. Jeśli trueTop równa jest 1, howMany zawiera łańcuch pusty. Jak widać w wierszu 165, zawartość zmiennej howMany jest wyprowadzana zaraz za słowem „wynik”. Jeśli zatem do pokazania pozostał jeden rekord, użyte zostanie słowo „wynik”; jeśli jest ich więcej, ujrzymy słowo „wyników”.5 Ostatni krok to określenie akcji wykonywanej w chwili kliknięcia przycisków. Jak już wcześniej wspomniano, zdarzenia onClick obu przycisków obsługiwane są przez funkcję formatResults(). Jej wywołanie konstruowane jest dynamicznie w wierszach 157–158 i 166–167. Oto pierwsze z wywołań: 'onClick="' + parent.frames[0].formatResults(parent.frames[0].copyArray, ' + (reference offset) + ', ' + offset + ')">');
Argumenty określane są przy użyciu operatora trójargumentowego i wypisywane „na bieżąco”. Jak widać, przycisk „Poprzednie” zawsze przekazuje funkcji trzy argumenty: copyArray, reference – offset oraz offset. Warto też zwrócić uwagę na zapis odwołań do funkcji formatResults() i zmiennej copyArray parent.frames[0].formatResults(...);
oraz parent.frames[0].copyArray
W pierwszej chwili może to się wydawać nieco dziwne, ale należy pamiętać, że odwołanie do funkcji formatResults() nie znajduje się w dokumencie nav.html (czyli w ramce parent. frames[0]). Ma ono miejsce w ramce wyników, parent.frames[1], która nie zawiera funkcji formatResults() ani zmiennej copyArray. Stąd właśnie bierze się taka, a nie inna forma odwołań. Przycisk „Następne” wykorzystuje podobną procedurę obsługi zdarzenia, ale... czy nie należałoby przypadkiem uwzględnić faktu, że w ostatnim podzbiorze może znajdować się mniej niż offset wyników (tak jak miało to miejsce podczas wywołania formatResults() w celu wyświetlenia wyników)? Otóż nie. To w funkcji formatResults() podejmuje się stosowną decyzję, zatem wystarczy dodać do siebie wartości reference i offset, a sumę przekazać do funkcji. Przyjrzyjmy się wierszom 166–167, zawierającym ostatni fragment wywołania metody document. writeln(): 'onClick="parent.frames[0].formatResults(parent.frames[0].copyArray, ' + (reference + offset) + ', ' + offset + ')">');
5
W języku polskim, jak już Czytelnicy zapewne zauważyli, rzecz nie jest taka prosta. Po pierwsze, dla liczb 2, 3 i 4 należy użyć formy „wyniki”, nie „wyników”. Po drugie, jeden wynik jest „następny”, nie „następne”. Stosowne poprawki proponujemy wykonać w ramach ćwiczenia (przyp. tłum.).
38
Techniki języka JavaScript: operator trójargumentowy Po ostatniej porcji materiału można się już było tego spodziewać. Operator trójargumentowy jest całkiem przydatny, a zatem czas na nieco teorii. Operatory te, używane w naszej aplikacji jako jednowierszowe odpowiedniki instrukcji warunkowej if-else, wymagają trzech argumentów. Składnia operatora trójargumentowego, cytowana z opublikowanego przez firmę Netscape Communications dokumentu JavaScript Guide for Communicator 4.0 (rozdział 9) wygląda następująco: (warunek) ? wartość1 : wartość2
Po zastąpieniu parametrów odpowiednimi wartościami, operator taki spowoduje zwrócenie wartości1 w przypadku prawdziwości warunku i wartości2 w przypadku jego fałszywości. Po co jednak ten cały wykład? Otóż omawianą tu konstrukcję niejednokrotnie łatwiej jest czytać, niż odpowiednie instrukcje if-else (mniej też trzeba pisać). Operator ten jest też szczególnie użyteczny, kiedy trzeba zakodować kilka zagnieżdżonych wyrażeń. Operator trójargumentowy nie jest jednak żadnym panaceum. Jeśli w przypadku spełnienia lub niespełnienia warunku należy wykonać kilka operacji, trzeba będzie odwołać się do konstrukcji if-else. Jeśli nie – warto spróbować operatora trójargumentowego.
Kod HTML W pliku nav.html jest bardzo niewiele statycznego kodu HTML. Cytujemy go ponownie, zaczynając od wiersza 174:
Nie ma tu żadnych niespodzianek. Formularz rozmieszczony został w komórkach tabeli, zaś jego przesłanie uruchamia omawiany uprzednio kod. Można by co najwyżej zapytać, jak przesyłać dane, skoro nie ma przycisku SUBMIT? Poczynając od specyfikacji HTML 2.0, większość przeglądarek (w tym Netscape Navigator i Internet Explorer) umożliwia uproszczone wysłanie formularza za pomocą pojedynczego pola tekstowego. Oczywiście wcale nie trzeba postępować właśnie w taki sposób – równie dobrze można uatrakcyjnić formularz, wyposażając go w przycisk lub obrazek (przycisk graficzny).
Tworzenie bazy danych w języku JavaScript Zawartość opisanej tu przykładowej bazy danych trzeba będzie prędzej czy później zastąpić własnymi danymi. Robi się to w trzech prostych etapach: 1. Otwórz plik records.js w edytorze tekstów.
39 Rozdział 1 - Wyszukiwanie danych po stronie klienta 2. Usuń znajdujące się tam rekordy tak, by uzyskać następującą zawartość pliku: var profiles = new Array( );
3. Dodaj kolejne rekordy według poniższego wzoru: "Tytuł strony|Opis strony|http://adres.url/plik.html",
Pomiędzy nawiasami można zapisać dowolną liczbę rekordów, pamiętając, by na końcu każdego poza ostatnim umieścić przecinek. Należy też pamiętać o rozdzielaniu tytułów stron, ich opisów i adresów URL znakami kreski pionowej. Znaków tych nie wolno używać w danych, gdyż spowoduje to błędy interpretera JavaScriptu. Na koniec nie należy zapominać, że w przypadku konieczności użycia wewnątrz danych znaku cudzysłowu, należy zacytować go w formie sekwencji unikowej (tj. \" zamiast samego ").
Kierunki rozwoju Nasza wyszukiwarka już w opisanej tu postaci jest całkiem użyteczna, jednak w zmianach i udoskonaleniach można pójść znacznie dalej. Oto wybrane możliwości: •
zapewnienie zgodności z językiem JavaScript 1.0,
•
uodpornienie programu na błędy,
•
wyświetlanie reklam,
•
rozszerzenie możliwości wyszukiwania,
•
użycie zapytań predefiniowanych.
Zgodność z językiem JavaScript 1.0 Wszyscy to wiemy: obie najpopularniejsze wśród użytkowników przeglądarki dostępne są obecnie (tj. w chwili wydawania tej książki – przyp. red.) w późnych wersjach 4.x lub wczesnych 5.x. Obie są bezpłatne. Nadal jednak trafiają się użytkownicy korzystający z takich staroci, jak Internet Explorer 3.02 czy Netscape Navigator 2. Statystyka odwiedzin prowadzonej przez autora witryny HotSyte (http://www.serve.com/hotsyte/) wciąż wskazuje na to, że wersje te są zaskakująco popularne. Jako że wyszukiwarka jest jedną z najważniejszych usług dostępnych w witrynie, warto pokusić się o zakodowanie jej w sposób zgodny ze specyfikacją JavaScript 1.0. Na szczęście jest to dość proste – wystarczy przejrzeć omawiany kod wiersz po wierszu, sprawdzić, które elementy nie są w wersji 1.0 obsługiwane i wymienić je. OK. Autor już to zrobił, ale zapewne większość czytelników kusiło, by zabrać się za to samemu (prawda?). Zmodyfikowaną wersję kodu można znaleźć w katalogu /ch01/searchengineMSIE/. Jak miało to miejsce poprzednio, otwórzmy w przeglądarce plik index.html i przyjrzyjmy się szybko zmianom zapewniającym zgodność aplikacji z JavaScriptem 1.0. Jest ich trzy: •
brak pliku źródłowego skryptu (problem ten jest specyficzny dla przeglądarki),
•
brak sortowania tablicy (metodą sort()),
•
„obejście” braku metody split().
Netscape Navigator 2.x i Microsoft Internet Explorer 3.x nie obsługują plików .js.6 Można sobie z tym poradzić, włączając tablicę profiles do pliku nav.html. Druga zmiana polega na rezygnacji z wywołania resultSet.sort() w wierszu 90. Oznacza to, że dane nie zostaną posortowane alfabetycznie, lecz pozostaną w kolejności, w jakiej zapisano rekordy w tablicy profiles. Trzecia modyfikacja to usunięcie nieobsługiwanej przez JavaScript 1.0 metody split(). Zastosowane rozwiązanie jest skuteczne, ale niestety pogarsza wydajność kodu.
NICTJDO Hasło to napisał na tablicy profesor ekonomii, kiedy autor był studentem pierwszego roku na Uniwersytecie Stanu Floryda. Rozwinięcie tego akronimu to „Nie Istnieje Coś Takiego Jak Darmowy Obiad”. W naszym przypadku znaczy to, że dokonanie zmian pozwoli na korzystanie z wyszukiwarki w starszych przeglądarkach, ale odbędzie się to kosztem możliwości funkcjonalnych i struktury kodu. 6
Nie do końca; niektóre odmiany przeglądarki MSIE 3.02 już to robią.
40 Bez plików .js trzeba będzie upchnąć tablicę profiles w pliku nav.html. Rozwiązanie to jest mało eleganckie, a jeszcze mniej wygodne, jeśli okaże się, że bazę trzeba będzie wykorzystać gdzie indziej. Metoda sort() nie jest dla aplikacji niezbędna, ale okazuje się bardzo przydatna. Brak uporządkowania zbioru wynikowego może zmusić użytkownika do żmudnego przeglądania wszystkich odpowiedzi. Co prawda można by zapisywać rekordy w tablicy alfabetycznie, ale i to nie jest raczej wygodnym rozwiązaniem. Sortowanie dla JavaScriptu w wersji 1.0 można również zaprogramować samodzielnie. Brak metody split() jest zdecydowanie najmniejszym problemem. Wersja aplikacji przeznaczona dla JavaScriptu 1.0 zawiera zresztą rozwiązanie, więc nie ma co rozpaczać.
Odporność na błędy W obecnej postaci aplikacja umożliwia użytkownikowi umieszczenie w zapytaniu znaku kreski pionowej. Warto pokusić się o ulepszenie programu poprzez dodanie kodu usuwającego z łańcucha zapytania znaki wykorzystywane jako separatory danych, co zwiększy odporność wyszukiwarki na błędy.
Wyświetlanie reklam Jeśli w witrynie panuje ruch jak w południe na Marszałkowskiej, czemu by na tym jeszcze trochę nie zarobić? Świetny pomysł, ale jak? Oto jedna z możliwości. Załóżmy, że chcemy wyświetlać losowo pięć reklam (bez jakiejś określonej kolejności). Po umieszczeniu kilku adresów URL obrazków w tablicy, możemy losowo wybierać jeden z nich do wyświetlenia. Oto przykładowa tablica: var adImages = new Array("pcAd.gif", "modemAd.gif", "webDevAd.gif");
Losowe wybranie jednego z nich i wyświetlenie go na stronie z wynikami wyglądałoby tak: document.writeln('');
Rozszerzenie możliwości wyszukiwania Ten pomysł może dać ambitniejszym programistom niezłe pole do popisu. Załóżmy na przykład, że użytkownik mógłby wybierać wyszukiwane elementy z tablicy, a dopiero potem zawężałby odpowiednio zakres interesujących go danych. Jednym z rozwiązań jest wyświetlenie pod polem tekstowym w pliku nav.html następującego zestawu opcji: Dane z roku 1998 Dane z roku 1999 Dane z roku 2000
Pól tych można użyć do zdecydowania, które tablice rekordów należy przeszukać (w naszym przypadku będą to np. tablice profiles98, profiles99 i profiles00). Kryteria wyszukiwania można rozszerzać na wiele innych sposobów. Jednym z prostszych jest umożliwienie wyszukiwania z uwzględnieniem wielkości liter i bez jego uwzględniania. Obecnie wielkość liter nie ma znaczenia, ale można by dodać do formularza pole wyboru pozwalające na takie rozróżnienie. Możliwość tworzenia bardziej złożonych warunków wyszukiwania można też zapewnić, uzupełniając operatory AND i OR funkcjami NOT, ONLY, a nawet LIKE. Oto ogólny opis ich znaczenia: AND Rekord musi zawierać oba hasła połączone operatorem AND. OR Rekord musi zawierać co najmniej jedno z haseł połączonych operatorem OR. NOT Rekord nie może zawierać hasła znajdującego się za operatorem NOT. ONLY Wynik musi zawierać tylko zadany rekord. LIKE Rekord może zawierać terminy podobne (w pisowni lub wymowie) do podanego hasła. Zaprogramowanie tych możliwości (szczególnie LIKE) wymaga sporo pracy, ale jej efekty mogą rzucić użytkowników na kolana.
41 Rozdział 1 - Wyszukiwanie danych po stronie klienta
Zapytania predefiniowane Inną popularną, a przy tym użyteczną techniką wyszukiwania jest tworzenie tzw. zapytań predefiniowanych (ang. cluster set). Zapytanie takie zawiera z góry ustaloną grupę haseł, dla której zdefiniowano gotowy zestaw odpowiedzi. Jeśli na przykład użytkownik umieści w zapytaniu tekst „kredyt obrotowy”, wyszukiwarka może od razu zwrócić gotowy zestaw wyników opisujących wszystkie produkty finansowe firmy. Technika ta wymaga starannego przygotowania danych, ale jej użycie może okazać się niezwykle cenne.
Cechy aplikacji: Interaktywny test z wyborem wielokrotnym Tysiące testów z jednym zbiorem pytań Klarowna prezentacja wyników i ocena Doskonała aplikacja dla sieciowych ankieterów Kontekstowe objaśnienia brakujących pytań Prezentowane techniki: Kombinacje z atrybutem SRC Mieszanie zawartości tablicy Protokół javascript:
2 Test sprawdzany na bieżąco
Przedstawiony w tym rozdziale interaktywny test należy traktować jako aplikację szablonową, na podstawie której można realizować dowolne tego typu zadania działające poprzez Internet. Elastyczność tej aplikacji polega na tym, że: •
Określa się liczbę pytań zadawanych użytkownikowi.
•
Pytania i odpowiedzi są dobierane losowo przy każdym załadowaniu aplikacji lub rozpoczęciu testu, w efekcie czego użytkownik za każdym razem ma do czynienia z nowym testem.
•
Można pytania testowe dodawać i usuwać – aplikacja dostosuje wówczas do tego sposób mieszania pytań, administrację, ocenianie odpowiedzi i ocenę badanego.
•
Bez problemu można z aplikacji usunąć odpowiedzi, dzięki czemu unika się oszukiwania, a odpowiedzi użytkownika można przesłać do działającej po stronie serwera aplikacji oceniającej.
Aplikację tę załadujemy otwierając w przeglądarce plik ch02/index.html. Na rysunku 2.1 pokazano ekran początkowy. Któż zgadłby tutaj, że pytania działają dzięki JavaScriptowi? Spróbujemy rozwiązać test, składający się z 50 pytań, które zwykle sprawiają użytkownikom sporo kłopotów. Pytania obejmują wiele zagadnień naszego języka: jego rdzeń, skrypty działające po stronie klienta i po stronie serwera, LiveConnect, znane błędy i tak dalej. Zadanie nie jest łatwe, ale o to chodzi (istnieje dokumentacja omawiająca wszystkie pytania i odpowiedzi tego testu, ale w razie odkrycia błędów w pytaniach użytkownik może skontaktować się z autorem). Po rozpoczęciu tekstu użytkownik na każde pytanie otrzyma cztery możliwe odpowiedzi. Kiedy, zdecyduje się na którąś z nich, aplikacja automatycznie przejdzie do następnego pytania, zatem nie można się cofać. Każde pytanie to jedna próba. Na rysunku 2.2 pokazano postać pytań i odpowiedzi.
43 Rozdział 2 - Test sprawdzany na bieżąco
Rysunek 2.1. Gotów do testu?
Rysunek 2.2. Pytania z kilkoma możliwymi odpowiedziami Po odpowiedzi na ostatnie pytanie, nasze odpowiedzi będą porównane z tymi właściwymi, po czym zostaniemy ocenieni, a wyniki wyświetlą się na ekranie, tak jak pokazano to na rysunku 2.3. Warto zwrócić uwagę, że teraz na ekranie pokazywane są poszczególne pytania ze wszystkimi możliwymi odpowiedziami i z odpowiedzią przez nas wybraną. Jeśli wybór był poprawny, tekst jest zielony; w przeciwnym wypadku ma kolor czerwony.
44
Rysunek 2.3. Wyniki testu Aby lepiej zrozumieć pytania, na które udzieliło się złych odpowiedzi, można przejrzeć prawidłowe propozycje; jeśli najedzie się kursorem myszki na czerwony tekst, na górze po prawej stronie pojawi się objaśnienie – rysunek 2.4.
Rysunek 2.4. Objaśnienie błędnie udzielonych odpowiedzi W porządku, to był pierwszy kontakt z aplikacją. Wszystko wygląda prosto, ale diagram z rysunku 2.5 da czytelnikowi pewne wyobrażenie o sposobie działania aplikacji z punktu widzenia użytkownika. Kreski przerywane wskazują opcjonalne działania użytkownika lub stan oczekiwania na jego reakcję. Całość składa się z pięciu etapów.
45 Rozdział 2 - Test sprawdzany na bieżąco
Rysunek 2.5. Działanie aplikacji z punktu widzenia użytkownika Oto poszczególne etapy: 1. Użytkownik wybiera przycisk Zaczynamy. Wypisywane jest pierwsze pytanie i aplikacja czeka na odpowiedź użytkownika lub wciśnięcie klawisza Koniec. 2. Jeśli użytkownik wybierze odpowiedź, aplikacja zapisuje dokonany wybór, określa, czy test się już skończył, czy też należy pokazać następne pytanie. Jeśli tekst jest skończony (użytkownik odpowiedział na ostatnie pytanie), idziemy do etapu 4. (ocena testu). W przeciwnym wypadku pokazuje się następne pytanie. 3. Jeśli użytkownik wybierze przycisk Koniec, musi jeszcze swój wybór potwierdzić. W przypadku wybrania OK test jest oceniany (mimo że jeszcze na to za wcześnie). Jeśli użytkownik wybierze Anuluj, test jest kontynuowany. 4. Kiedy test się skończy (lub zostanie przerwany), odpowiedzi użytkownika są porównywane z prawidłowymi odpowiedziami, a na ekranie pokazują się wyniki. 5. Kiedy użytkownik przegląda wyniki, może przesunąć mysz nad dowolny czerwony tekst (czyli swoją błędną odpowiedź), a wówczas pokazane zostaną dodatkowe informacje.
Wymagania programu Mamy tu do czynienia z aplikacją JavaScriptu 1.1, więc Navigator 3.x i MSIE 4.x sobie z nią poradzą. Jeśli chodzi o wielkość aplikacji, obecnie jest 75 pytań. Testowanie przerwałem, kiedy było ich 400. Jako że zapewne nikt nie będzie raczej używał naszej aplikacji do przeprowadzania egzaminu adwokackiego ani magisterskiego, uważam, że 400 to liczba wystarczająca.
Struktura programu Na rysunku 2.5 pokazano działanie aplikacji od początku do końca. Dobrym sposobem na zrozumienie, co się naprawdę dzieje, jest przeanalizowanie dokładniejszego schematu opisującego działanie JavaScriptu, a później przejrzenie odpowiednich plików z kodem.
46 Na rysunku 2.6 przedstawiono działanie JavaScriptu. Prostokąty narysowane linią przerywaną wskazują przetwarzanie zachodzące przed testem lub po nim (na przykład podczas ładowania stron). Strzałki kresek oznaczają możliwe czynności użytkownika lub oczekiwanie na jego odpowiedź.
Rysunek 2.6. Działanie JavaScriptu Funkcje związane z poszczególnymi akcjami (czynnościami) zaznaczono kursywą. Porównanie rysunków 2.5 i 2.6, umożliwi szybkie zorientowanie się w problemie. W zasadzie mamy do czynienia z tym samym działaniem, przy czym w drugim przypadku dodano pewne przetwarzanie przed testem i po nim.
index.html – ramki Aplikacja ta składa się z trzech plików: index.html, administer.html oraz questions.js. Jako że index.html zawiera ramki, zacznijmy od niego – obejrzyjmy przykład 2.1. Przykład 2.1. Plik index.html 1 2 3 <TITLE>JavaScript On-line Test 4 <SCRIPT LANGUAGE="JavaScript1.1"> 5 '; 7 var dummy2 = 'W nauce JavaScriptu nie ma wakacji...'; 8 //--> 9 10 11 12 19
Jak można zauważyć, nie jest to zestaw ramek spotykany w Sieci. Po pierwsze ramki te są zagnieżdżone – czyli mają postać ramek. Zewnętrzny zestaw w wierszu 12 definiuje dwa wiersze: jeden jest wysoki na 90 pikseli, drugi natomiast zajmuje resztę wysokości okna. 90-pikselowa ramka zawiera znów ramki, tym razem składające się z dwóch kolumn: pierwszej o szerokości 250 pikseli i drugiej zajmującej resztę okna. Na rysunku 2.7 pokazano sposób podzielenia okna na ramki. Podano też atrybuty SRC poszczególnych ramek.
Rysunek 2.7. Układ ramek zagnieżdżonych w index.html administer.html w atrybucie SRC znacznika FRAME ma sens, ale co z pozostałymi dwiema ramkami? Dwie zmienne dummy1 i dummy2 definiują niejawne strony HTML – czyli strony bez nazwy pliku. Obie istnieją jedynie w aplikacji.
Zmienne te zdefiniowane są w wierszach 7 i 8. Warto zwrócić uwagę, że każda z nich zawiera nieco kodu HTML – niewiele wprawdzie, ale wystarczy. W pliku index.html używa się protokołu javascript: do interpretacji wartości zmiennych dummy1 oraz dummy2, następnie całość jest zwracana jako zawartość adresu URL wskazanego w atrybucie SRC. Więcej informacji można znaleźć w ramce „Techniki języka JavaScript”. Teraz ramki są już na swoim miejscu. Wszystkie trzy wypełniliśmy, stosując tylko jedną stronę HTML (administer.html). Teraz przejdźmy do sedna rzeczy.
question.js – plik źródłowy JavaScript Zajmijmy się teraz plikiem źródłowym JavaScriptu questions.js wywoływanym przez administer. html, a pokazanym jako przykład 2.2. Przykład 2.2. Początek pliku questions.js 1 function question(answer, support, question, a, b, c, d) { 2 this.answer = answer; 3 this.support = support; 4 this.question = question; 5 this.a = a; 6 this.b = b; 7 this.c = c; 8 this.d = d; 9 return this; 10 } 11 var units = new Array(
Przykład 2.2. Początek pliku questions.js (dokończenie) 12 new question("a", "The others are external objects.", 13 "Choose the built-in JavaScript object:", "Image", "mimeType", 14 "Password", "Area"), 15 // i tak dalej... 16 }
Istnieje oczywiście wersja skrócona tego pliku. Tablica units jest znacznie dłuższa (ma 75 elementów), ale każdy jej element opisuje obiekt question (ang. pytanie), którego strukturę pokazano w wierszach 1–10.
48 Aplikacja jest oparta na obiektach definiowanych przez użytkownika. Jeśli idea obiektów JavaScriptu nie jest do końca jasna, można zajrzeć do dokumentacji Netscape’a pod adresem: http:// developer.netscape.com/docs/manuals/communicator/jsguide4/model.htm. Pozwoli to lepiej zrozumieć model obiektowy JavaScriptu. Tymczasem następnych kilka akapitów warto potraktować jako krótki podręcznik typu „jak tego użyć i sobie nie zaszkodzić”. Obiekt to zestaw danych strukturalnych. Każdy obiekt jest związany z dwoma elementami: właściwościami i metodami. Właściwości zawierają coś, na przykład liczbę 6, wyrażenie a * b lub napis „Jimmy”. Metody coś robią, na przykład wyliczają długość łuku lub zmieniają kolor tła dokumentu. Przyjrzyjmy się obiektowi JavaScriptu document. Każdy dokument coś zawiera (document.gbColor, document.fgColor i tak dalej) oraz coś robi (document.open(), document.write(), document.close()).
Techniki języka JavaScript: oszukany atrybut SRC Ustawienie atrybutu SRC na wartość będącą wynikiem rozwinięcia wyrażenia JavaScript może wyglądać nieco dziwnie, więc zastanówmy się nad tym przez chwilę. Załóżmy, że otwieramy edytor tekstowy i wstawiamy do nowego pliku taki tekst: W nauce JavaScriptu nie ma wakacji...
Teraz nadajemy nazwę temu plikowi zdanie.html i ładujemy go do przeglądarki. Łatwo przewidzieć rezultat. W pliku index.html mamy do czynienia właściwie z taką samą sytuacją, tyle tylko, że powyższy tekst jest wartością zmiennej dummy2, natomiast protokół javascript: tę zmienną ewaluuje. Więcej informacji o tym protokole można znaleźć w ramce dalej w tym rozdziale. Mamy do czynienia z anonimową stroną HTML. Tę technikę nazywam oszukanym atrybutem SRC. W dalszej części rozdziału skorzystamy jeszcze z tej techniki. Obiekty tworzy się przez określenie najpierw funkcji konstruktora, choćby takiego: function mojPierwszyKonstruktor(arg1, arg2, argn) { this.wlasciwosc1 = arg1; this.wlasciwosc2 = arg2; this.wlasciwoscn = argn; return this; }
Wygląda to podobnie jak każda inna funkcja, tyle tylko, że w celu odwołania się do samego siebie obiekt używa słowa kluczowego this. Wszelkie przekazane argumenty mogą zostać przypisane właściwościom lub być przetwarzane inaczej. Kiedy już mamy konstruktor, nowe obiekty tworzymy stosując operator new: var mojPierwszyObiekt= new mojPierwszyKonstruktor(6, a*b, "Jimmy"); var mojDrugiObiekt= new mojPierwszyKonstruktor(6, a*b, "Jimmy"); var mojTrzeciObiekt= new mojPierwszyKonstruktor(6, a*b, "Jimmy");
W przypadku naszego skryptu implementacja obiektów jest faktycznie taka prosta. Obiekty tworzy funkcja-konstruktor question(), przy czym mają one tylko właściwości. W wierszach 2–8 pokazano siedem właściwości każdego pytania: odpowiedź, wyjaśnienie, samo pytanie (tekst) oraz cztery możliwe odpowiedzi – a, b, c i d. Oto wiersze od 1 do 10: function question(answer, support, question, a, b, c, d) { this.answer = answer; this.support = support; this.question = question; this.a = a; this.b = b; this.c = c; this.d = d; return this; }
Właściwości i metody przypisywane są obiektowi przy użyciu takiej właśnie notacji. Wobec tego każdy element units za pomocą operatora new tworzy nowy obiekt question(), przekazując mu siedem parametrów, które będą jego właściwościami. W wierszu 9 mamy zapis:
49 Rozdział 2 - Test sprawdzany na bieżąco return this;
Oznacza to zwrócenie wskazania na zmienną (w naszym wypadku każdy z elementów units), co można porównać z przypieczętowaniem jakiegoś postanowienia. Teraz każdy element units jest pytaniem, question. Stanowi to wygodny sposób tworzenia, usuwania i innego typu obsługi pytań testu. Nowe pytania można tworzyć, stosując tę samą składnię, co w przypadku elementów units: new question("litera_odpowiedzi", "objaśnienie", "treść pytania", "opcja a", "opcja b", "opcja c", "opcja d");
Jeśli ktoś zastanawia się, dlaczego odpowiedź jest pierwszą pozycją, to powinien wiedzieć, że po prostu łatwiej jest umieścić napis składający się z jednej litery na początku listy argumentów, niż na końcu. Niektóre pytania są przecież dosyć długie, więc przy zaproponowanym układzie łatwiej będzie coś znaleźć i poprawić. Tworzenie obiektu pytania dla każdego z nich może wydawać się zbyteczne, ale znacznie ułatwia to dalsze działanie, szczególnie kiedy przyjdzie nam dalej pracować z danymi właściwości poszczególnych pytań. Zajmiemy się tym, kiedy zbadamy jeszcze plik administer.html.
Jeśli w swoich aplikacjach nie używasz obiektów JavaScriptu, warto zastanowić się nad zmianą stylu pisania. Obiekty mają wiele zalet. Dzięki nim kod będzie elegantszy i łatwiejszy w utrzymaniu. Poza tym obiekty umożliwiają dziedziczenie, czyli przenoszenie metod z obiektu pierwotnego do obiektu budowanego na jego bazie. Można ściągnąć plik PDF lub przeczytać dokumentację o JavaScripcie i dziedziczeniu obiektów w Sieci pod adresem http://developer.netscape.com:80/docs/manuals/ communicator/jsobj/contents.htm.
administer.html Teraz mamy już obiekty, zacznijmy więc ich używać. Jest to kolejna aplikacja, w której cały mechanizm JavaScriptu rezyduje w górnej ramce, a dolna ramka służy jako okno interakcji. Można rozbić aplikację na szereg operacji. Zestawiono je w tabeli 2.1 oraz opisano, jak również podano związane z nimi zmienne i funkcje JavaScriptu. Tabela 2.1. Operacje testu i związane z nimi funkcje JavaScriptu Operacja
Opis
Elementy JavaScriptu
Przygotowanie środowiska
Deklarowanie i inicjalizacja zmiennych globalnych, przemieszanie zestawów pytanie– odpowiedź.
Ocena testu Pokazanie wyników Wyœwietlanie wyjaœnieñ Czyszczenie œrodowiska
Zapisanie zestawu pytanie– odpowiedź w oknie, zapis odpowiedzi użytkownika. Porównanie odpowiedzi badanego z poprawnymi odpowiedziami. Pokazanie odpowiedzi poprawnych i błędne wraz z oceną. Wyœwietlanie i chowanie wyjaśnień w parent.frames[1]. Przywracanie zmiennym ich pierwotnych wartości.
Funkcja gradeTest() Funkcja printResults() Funkcje explain() i show() Zmienne: qIdx, correct, stopOK
Tablica keeper Funkcje cleanSlate() i shuffle() Za chwilę przyjrzymy się wszystkiemu po kolei. Na razie obejrzyjmy kod administer.html – przykład 2.3. Przykład 2.3. Kod źródłowy administer.html
50 1 2 3 4 5 6 7 8 9 10 11 12 13
<TITLE>On-line JavaScript Test <SCRIPT LANGUAGE="JavaScript1.1" SRC="questions.js"> <SCRIPT LANGUAGE="JavaScript1.1"> var qIdx = 0; var correct = 0; var howMany = 50; var keeper = new Array(); var rank = new Array('Nie obraź się, ale potrzebna Ci pomoc.', 'Byli i tacy, co zrobili jeszcze gorzej...', 'Cosik tam wiesz. Przynajmniej tego nie zapomnij.', 'Zdaje się, że pracujesz nad swoją wiedzą.', 'Lepiej od przeciętnego niedźwiedzia.',
'; } } results += '\n\r'; qFrame.location.replace("javascript: parent.frames[0].results"); } function show() { parent.status = ''; } function explain(str) { with (aFrame.document) { open(); writeln('' + str + ''); close(); } } function cleanSlate() { aFrame.location.replace('javascript: parent.dummy1'); qFrame.location.replace('javascript: parent.dummy2'); }
Ten dość długi plik można by podzielić na cztery części. W pierwszej wywoływany jest plik źródłowy questions.js. W następnej definiowane są pewne zmienne globalne, po czym przejdziemy do funkcji. W końcu mamy kilka wierszy HTML i od nich właśnie zacznijmy.
Treść HTML Kiedy administer.html skończy się ładować, wywoływana jest funkcja cleanSlate() z wiersza 125:
52 W cleanSlate() używa się metody replace() obiektu location, w ten sposób bieżący adres URL parent.frames[1] (alias aFrame) i parent.frames[2] (alias qFrame) zamieniany jest na zawartość zmiennych dummy1 i dummy2, zdefiniowanych wcześniej w pliku index.html. Spójrzmy na wiersze 119 do 122: function cleanSlate() { aFrame.location.replace('javascript: parent.dummy1'); qFrame.location.replace('javascript: parent.dummy2'); }
Właśnie to robiliśmy w pliku index.html, zgadza się? Tym razem zapewniamy, że jeśli z jakiegoś powodu administer.html ulegnie przeładowaniu, górna ramka zawierać będzie naszą sentencję, a istniejący tam ewentualnie tekst pytania zostanie usunięty. Reszta pliku HTML to po prostu formularz HTML z dwoma przyciskami. To jest już proste. Każdy przycisk – po kliknięciu go – wywołuje inną funkcje. Poniżej przedstawiono kod wierszy 127–132:
Warto zauważyć, że przycisk Zaczynamy wywołuje funkcje itemReset() i buildQuestion(), natomiast Koniec wywołuje chickenOut(). Wszystkie trzy funkcje będą omówione w sekcji im poświęconej.
Zmienne globalne Zaraz za instrukcją powodującą włączenie pliku źródłowego JavaScript questions.js w wierszu 5 można znaleźć zmienne globalne używane w aplikacji. Oto wiersze 5 do 22: var qIdx = 0; var correct = 0; var howMany = 50; var keeper = new Array(); var rank = new Array('Nie obraź się, ale potrzebna Ci pomoc.', 'Byli i tacy, co zrobili jeszcze gorzej...', 'Cosik tam wiesz. Przynajmniej tego nie zapomnij.', 'Zdaje się, że pracujesz nad swoją wiedzą.', 'Lepiej od przeciętnego niedźwiedzia.', 'Jesteś niezłym programistą JavaScriptu.', 'Jesteś znawcą JavaScriptu.', 'Jesteś doskonały w JavaScripcie.', 'Ogłaszam Cię guru JavaScriptu.' ); var stopOK = false; var nextQ = ''; var results = ''; var aFrame = parent.frames[1]; var qFrame = parent.frames[2];
Na poniższej liście opisujemy znaczenie poszczególnych zmiennych. Dokładniej przyjrzymy się im przy omawianiu poszczególnych funkcji. qldx
Zmienna używana do monitorowania bieżącego pytania, wyświetlanego na ekranie. correct
Zmienna rejestrująca liczbę poprawnych odpowiedzi podczas oceny testu. howMany
Niezmienna liczba określająca liczbę pytań, na które odpowiadać ma użytkownik. keeper
Początkowo pusta tablica, zawiera odpowiedzi udzielone przez użytkownika. rank
Tablica napisów określających poziom umiejętności. stopOK
Zmienna logiczna określająca, czy przerwać test. nextQ
Pusty napis, któremu przypisywany jest tekst kolejnych pytań.
53 Rozdział 2 - Test sprawdzany na bieżąco results
Początkowo napis pusty, później ocena testu. aFrame
Prosta metoda odwołania się do drugiej ramki. qFrame
Prosta metoda odwołania się do trzeciej ramki.
Funkcje Teraz przechodzimy do funkcji. Zaczniemy od itemReset(). itemReset() Pierwsza z funkcji wywoływanych w aplikacji to itemReset(). Pojawia się, kiedy użytkownik wciśnie przycisk Zaczynamy (wiersze 128-129):
itemReset() przywraca zmiennym globalnym ich pierwotne wartości i miesza zawartość tablicy obiektów pytań (więcej o mieszaniu już wkrótce). Spójrzmy na wiersze 31–37: function itemReset() { qIdx = 0; correct = 0; stopOK = false; keeper = new Array(); shuffle(); }
Warto zwrócić uwagę, że użytkownik nie widział jeszcze pierwszego pytania, a JavaScript już napracował się przy ustawianiu zmiennych globalnych. Po co? Załóżmy, że już test rozwiązaliśmy i tylko na dwa pytania odpowiedzieliśmy poprawnie wtedy wciskamy jeszcze raz przycisk Zaczynamy. Jednak wiele zmiennych globalnych ma już różne nieoczekiwane wartości i tym właśnie zajmuje się funkcja itemReset(): odświeża wartości tych zmiennych. Zauważmy, że nie dotyczy to zmiennej howMany. Wartość ta pozostaje niezmienna przez cały czas działania aplikacji. Zmienne netQ i results na początku mają ciąg pusty jako wartość, ale ich wartości nie są zerowane. Nie ma po prostu takiej potrzeby. Zajrzyjmy do wierszy 43 i 86, a zobaczymy, jak te zmienne są ustawiane na bieżąco. Kiedy zmienne są już odpowiednio ustawione, można wywołać w wierszu 36 funkcję shuffle(). shuffle() Ta mała funkcja daje administratorowi testu ogromną elastyczność – zmienia ona losowo kolejność pytań, dzięki czemu daje prawie pewność, że testowany dostanie za każdym razem inny zestaw. Aby unaocznić wynikające z tego możliwości, przypomnijmy, że liczba możliwych kombinacji (różnych uporządkowań) pytań testu wynosi n(n-1), przy czym n to liczba pytań. Zatem najmniejszy nawet tekst z dziesięciu pytań da 10*(10–1) kombinacji, czyli 90. W przypadku testu z 20 pytań możliwości jest już 380. Z kolei test z 50 pytań oznacza 2 450 możliwych kombinacji. To może być nieciekawa wiadomość dla oszustów. Test jest także niepowtarzalny dlatego, że choć cała tablica units ma 75 pytań, zmienna howMany ustawiana jest na 50. Kiedy skończy się mieszanie, wybieranych jest 50 pierwszych pytań. Wobec tego istnieje duża szansa, że zestaw następnych 50 pytań jest inny, niż pierwsze 50. Oznacza to, że w teście tym istnieją tysiące możliwych kombinacji pytań. Zdumiewające, jak prosty jest proces mieszania tychże pytań. Oto wiersze 23 do 30: function shuffle() { for (var i = 0; i < units.length; i++) { var j = Math.floor(Math.random() * units.length); var tempUnit = units[i]; units[i] = units[j]; units[j] = tempUnit; } }
Dla każdego elementu tablicy units: 1. Wybierana jest przypadkowa liczba między 0 a units.length - 1. 2. Wartość zmiennej lokalnej tempUnit ustawiana jest na bieżący indeks (units[i]).
54 3. Wartość elementu bieżącego indeksu (units[i]) ustawiana jest na wartość elementu o przypadkowym indeksie całkowitym (units[j]). 4. Wartość elementu o przypadkowym indeksie staje się równa wartości zmiennej lokalnej tempUnit. Innymi słowy, kolejno przeglądane są wszystkie elementy tablicy i parami są zamieniane wartości z losowo wybranym elementem. Pytania zostały już zatem losowo wymieszane i czekają na użytkownika. buildQuestion() Funkcja ta pełni rolę administratora testu. Jak zapewne łatwo zauważyć na poprzednim schemacie, buildQuestion() jest używana kilkakrotnie. Spoczywa na niej wielka odpowiedzialność. Zaczyna się w wierszu 38, a kończy w wierszu 54: function buildQuestion() { if (qIdx == howMany) { gradeTest(); return; } nextQ = '' + '
Prześledźmy wszystko po kolei: najpierw buildQuestion() sprawdza, czy zmienna qIdx równa jest zmiennej howMany. Jeśli tak, użytkownik odpowiedział na ostatnie pytanie i przyszedł czas na ocenę. Funkcja gradeTest() wywoływana jest w wierszu 40.
Techniki języka JavaScript: mieszanie zawartości tablicy W naszym teście zmieniamy przypadkowo układ elementów tablicy. Jest to zachowanie pożądane w przypadku tej aplikacji, ale nietrudno przychodzi też zapisywać inne, bardziej kontrolowane metody przestawiania danych w tablicy. Poniższa funkcja jako parametry przyjmuje kopię tablicy do uporządkowania oraz liczbę całkowitą, wskazującą, co ile jej elementy mają być porządkowane: function shakeUp(formObj, stepUp) { setUp = (Math.abs(parseInt(stepUp)) > 0 ? Math.abs(parseInt(stepUp)) : 1); var nextRound = 1; var idx = 0; var tempArray = new Array(); for (var i = 0; i < formObj.length; i++) { tempArray[i] = formObj[idx]; if (idx + stepUp >= formObj.length) { idx = nextRound; nextRound++; } else { idx += stepUp; } } formObj = tempArray; }
Jeśli na przykład tablica ma 10 elementów i porządkujemy je co drugi (0, 2, 4, 6, 8, następnie 1, 3, 5, 7, 9), wywołujemy shakeUp(twojaTablica, 2). Jeśli przekażemy 0, domyślny przyrost to 1. Więcej tego typu funkcji znajdziesz w rozdziale 6.
55 Rozdział 2 - Test sprawdzany na bieżąco Jeśli test jeszcze się nie skończył, buildQuestion() idzie dalej, co oznacza, że generowana jest treść następnego pytania w postaci strony HTML (wiersze 43 do 50). Jeśli zbada się nextQ, można zauważyć, że strona ta zawiera wskaźnik numeru pytania i całkowitą liczbę pytań testu. Oto wiersz 44: '
Pytanie ' + (qIdx + 1) + ' z ' + howMany + '
'
Dalej znajdziemy otwierający znacznik FORM, a za nim treść pytania. Warto pamiętać, że treść znajduje się we właściwości question poszczególnych elementów units. Nie należy być zatem zaskoczonym, że wiersz 45 wygląda następująco: ''; qFrame.location.replace("javascript: parent.frames[0].nextQ");
Wartość nextQ ładowana jest do dolnej ramki. Zauważmy, że w aplikacji używana jest metoda replace obiektu location, nie ustawiamy więc tym razem właściwości location.href ani nie stosujemy document.write(). W tej aplikacji jest to istotne. Funkcja replace() ładuje wskazany adres URL do przeglądarki (w naszym przypadku adres URL to napis HTML rozwijany przez użycie protokołu javascript:), przy czym nowa strona zastępuje bieżącą. Dzięki temu użytkownik nie może wrócić do strony wcześniejszej ani zmieniać odpowiedzi. Jeśli użytkownik wciśnie w przeglądarce przycisk Back, załaduje się strona, która była oglądana przed index.html. Ostatnią rzeczą, którą trzeba zrobić przed opuszczeniem funkcji buildQuestion(), jest drobne uporządkowanie, pokazane w wierszach 52–53: qIdx++; if(qIdx >= 2 && !stopOK) { stopOK = true; }
Zwiększenie qIdx o 1 przygotowuje sytuację do następnego wywołania buildQuestion(). Pamiętaj, że w wierszu 39 sprawdzamy, czy qIdx jest większa od liczby pytań w teście (zmienna howMany); jeśli tak, czas na ocenę. Instrukcja if z wiersza 53 sprawdza, czy użytkownik zamierza przerwać test. Kod w obecnej jego postaci wymaga udzielenia odpowiedzi na przynajmniej jedno pytanie. Jeśli użytkownik chce, może to już sam poprawić.
56
Techniki języka JavaScript: protokół javascript: Wcześniej już zetknęliśmy się z powyższym protokołem w tej książce. Protokół ten pozwala JavaScriptowi ewaluować dowolne wyrażenie. Jeśli na przykład chcemy, aby po kliknięciu przez użytkownika łącza stało się coś innego niż zwykłe przeładowanie strony, można użyć odpowiedniego wywołania w atrybucie HREF znacznika : Kliknij mnie
Można też ustawiać w taki sposób atrybuty SRC innych znaczników HTML. Szczegóły znajdziemy w ramce opisującej „oszukane” atrybuty SRC, wcześniej w tym rozdziale. Teraz jeszcze ostrzeżenie: jeśli w zdefiniowanej przez siebie funkcji używamy protokołu javascript:, nie próbujemy ewaluować zmiennych lokalnych funkcji. To nie zadziała.
Omawiany protokół ma zasięg globalny, więc „widzi” jedynie globalne zmienne, globalne obiekty i tak dalej. Wiersz 51 to klasyczny przykład ilustrujący tę zasadę: qFrame.location.replace("javascript: parent.frames[0].nextQ");
Zmienna nextQ właściwie mogłaby zostać zdefiniowana jako lokalna, w końcu przecież jest używana jedynie w funkcji buildQuestion(). Jednak, jako że w wierszu 51 znajduje się odwołanie do protokołu javascript:, poniższy kod nie zadziałałby: qFrame.location.replace("javascript: nextQ");
Jeśli zmienna nextQ byłaby lokalna, javascript: nie mógłby jej ewaluować. gradeTest() Funkcja gradeTest() realizuje dwa zadania. Najpierw porównuje odpowiedzi użytkownika z tymi prawidłowymi, zapamiętując liczbę uzyskanych dotąd właściwych odpowiedzi. Po drugie gradeTest() wylicza wskaźnik oceny i na podstawie liczby prawidłowych odpowiedzi wybiera odpowiedź. Oto całość gradeTest(), wiersze 66–76: function gradeTest() { for (var i = 0; i < qIdx; i++) { if (keeper[i] == units[i].answer) { correct++; } } var idx = Math.ceil((correct/howMany) * rank.length - 1) < 0 ? 0 : Math.ceil((correct/howMany) * rank.length - 1); printResults(rank[idx]); itemReset(); }
Tablica keeper zawiera litery (a, b, c lub d) związane z odpowiedzią wybraną przez użytkownika. Każdy element tablicy units to obiekt pytania zawierający właściwość answer – też a, b, c lub d. gradeTest() przegląda kolejne elementy keeper i porównuje wartość właściwości answer z odpowiednim elementem z units. Jeśli pasują do siebie, zmienna correct zwiększana jest o 1. Warto zauważyć, że zapamiętaniu nie podlega to, które odpowiedzi były prawidłowe. Funkcja jedynie określa liczbę prawidłowych odpowiedzi i podaje ocenę na postawie tej liczby. Jeszcze raz użyjemy tablicy keeper, kiedy będziemy omawiać funkcję printResults(). Zwróćmy uwagę też na to, że gradeTest() nie używa zmiennej howMany. Nie ma znaczenia, ile jest pytań w teście; ważne jest tylko, na ile użytkownik udzielił odpowiedzi. Kiedy już mamy wyniki, zmienna correct zawiera liczbę odpowiedzi poprawnych. Teraz gradeTest() musi tylko ocenić użytkownika – wiersze 72–73: var idx = Math.ceil((correct/howMany) * rank.length - 1) < 0 ? 0 : Math.ceil((correct/howMany) * rank.length - 1);
Oto jak rzecz działa, gdy chcemy przypisać użytkownikowi jedną z ocen z tabeli rank z wiersza 9. Aby wybrać element, musimy mieć liczbę między 0 a rank.length - 1. Funkcja gradeTest() wybiera liczbę całkowitą w trzystopniowym procesie: 1. Wylicza procent odpowiedzi poprawnych (correct / howMany). 2. Mnoży uzyskane procenty przez (rank.length - 1).
57 Rozdział 2 - Test sprawdzany na bieżąco 3. Zaokrągla uzyskany iloczyn w górę. Wynik przypisuje się zmiennej lokalnej idx, która jest (w postaci liczby całkowitej) określeniem skuteczności użytkownika między zerem a rank.length. Innymi słowy, ile by nie było pytań, użytkownik zawsze otrzyma ocenę opartą na procencie prawidłowo udzielonych odpowiedzi. W zrozumieniu tego powinien pomóc następujący przykład. Załóżmy, że mamy taką oto tablicę rank: var rank = new Array( "byli lepsi", "jako-tako", "dobrze", "bardzo dobrze", "doskonale");
rank.length równe jest 5, jeśli więc nasz test ma 50 pytań, skala ocen jest następująca:7
Poprawne odpowiedzi
Wyznaczona liczba
Ocena (rank[int])
0–9 10–19 20–29 30–39 40–50
0 1 2 3 4
byli lepsi jako-tako dobrze bardzo dobrze doskonale
Mamy zatem mniej więcej howMany / rank.length odpowiedzi na jedną ocenę (poza oceną najwyższą). Nie ma znaczenia, czy pytania są 2, czy jest ich 2008 – wszystko działa tak samo. Zaproponowany system ocen jest skuteczny, ale jest dość zgrubny. Stosowane powszechnie systemy ocen zwykle są bardziej złożone. Większość szkół amerykańskich używa systemu literowego, gdzie A to ponad 90%, B to 80–89%, C to 70–79%, D to 60–69%, a F to mniej niż 60%. Być może ktoś zechce użyć jakiejś podobnej krzywej. W sekcji opisującej możliwe rozszerzenia aplikacji znajdzie się kilka innych pomysłów. gradeTest() w zasadzie skończyła już swoje zadanie. Zmienna rank[idx] przekazywana jest do funkcji wyświetlającej printResults(), a później wywoływana jest funkcja czyszcząca itemReset().
printResults() Aplikacja wie, jakie wyniki osiągnął użytkownik, czas więc poinformować go o tym. Służy do tego funkcja printResults() wyświetlająca następujące rzeczy: •
Stosunek liczby odpowiedzi poprawnych do ilości wszystkich pytań.
•
Ocenę użytkownika wyznaczoną przez gradeTest().
•
Wszystkie pytania testu wraz z kompletem czterech odpowiedzi.
•
Informację, czy użytkownik wybrał dobrą odpowiedź, czy też nie.
•
Przyłączony tekst pozwalający użytkownikowi uzyskać dodatkowe informacje o pytaniach, na które nie odpowiedział prawidłowo.
Pierwsze dwa punkty realizowane są w wierszach 77 do 84: function printResults(ranking) { results = '' + '' + '
Zmienne correct i howMany oznaczają odpowiednio liczbę odpowiedzi poprawnych oraz liczbę zadanych pytań, rank[rankIdx] to zapis zawierający ocenę użytkownika. Jeśli chodzi o wyświetlanie pytań i możliwych czterech odpowiedzi, obejrzyjmy wiersze 85 do 91. Nie powinno nikogo zaskakiwać pojawienie się pętli for: for (var i = 0; i < howMany; i++) { results += '\n\r\n\r\n\rPytanie ' + (i + 1) + ' ' + 7
Jest tutaj pewna pułapka: jeśli użytkownik nie będzie znał odpowiedzi na żadne pytanie, może wybierać odpowiedzi losowo. Przy teście z czterema dopuszczalnymi odpowiedziami średnia prawidłowych odpowiedzi wyniesie 12,5 na 50 pytań, czyli już „jako-tako”. Jeśli zamierzamy, wykorzystać taką aplikację, powinniśmy zmienić jej skalę (żeby minimalna ocena promująca była większa od oczekiwanej wartości dobrych odpowiedzi przy „strzelaniu”). No, chyba że chodzi o poprawienie humoru testowanemu (przyp. tłum.).
8
Nie całkiem – dopóki pytań jest mniej niż możliwych ocen, część ocen nie będzie mogła wystąpić w ogóle (przyp. tłum.).
W każdej iteracji od 0 do howMany - 1 podawana jest jako tekst liczba (i + 1), tekst pytania (units[i].question) oraz cztery możliwe odpowiedzi (units[i].a, units[i].b, units[i].c, units[i].d). Trochę zamieszania wprowadzają użyte znaczniki HTML. Ostatni fragment wydruku to wyświetlenie dobrych odpowiedzi użytkownika na zielono i złych – z dodatkowym objaśnieniem – na czerwono. Oto odpowiednie wiersze – 92 do 106: if (keeper[i] == units[i].answer) { results += '' + 'Na to odopwiedziałeś poprawnie (' + keeper[i] + ').\n\r
Przetwarzaniu ulegają wszystkie kolejne pytania, niezależnie od trafności odpowiedzi udzielonej przez użytkownika. Nie należy być zatem zaszokowanym, gdy się widzi wewnątrz instrukcję if-else. Jeśli keeper[i] równe jest units[i].answer, użytkownik wybrał dobrze, a odpowiedź wyświetla się na zielono. Jeśli równość nie zachodzi, czerwony tekst wskazuje dostępność dodatkowego objaśnienia pytania w parent.frames[1]. Ramka ta nie była dotąd właściwie używana, więc teraz przyszedł i na nią czas. Pytania, na które użytkownik udzielił jedynej słusznej odpowiedzi, są zwykłym tekstem, tymczasem pytania ze złymi odpowiedziami są jednak tekstem łącza. Zdarzenie onMouseOver tych łącz wywołuje przed zwróceniem true dwie funkcje: show() oraz explain(). Funkcja show() jest bardzo prosta: pokazuje pusty napis w pasku stanu (aby uniknąć dodatkowego zamieszania związanego z umieszczeniem myszy nad łączem) – wiersz 110: function show() { parent.status = ''; }
Funkcja explain() jako parametr przyjmuje napis i używa go w metodzie document.write() do wyświetlenia HTML w cierpliwie czekającej parent.frames[1]. Oto wiersze 111–118: function explain(str) { with (aFrame.document) { open(); writeln('' + str + ''); close(); } }
Mimo że zatroszczyliśmy się o zdarzenie onMouseOver, explain(), nadal ma jeszcze nieco pracy. Zwróćmy uwagę, że explain() wywoływana jest znów w wierszu 101 w obsłudze zdarzenia onMouseOut. Tym razem jednak funkcji explain() przekazywany jest pusty napis, więc aFrame będzie czyszczona po każdym zdarzeniu onMouseOut. Jedyne, co nam zostało, to zabezpieczenie się przed jakąkolwiek akcją w przypadku kliknięcia naszego łącza przez użytkownika. W wierszu 102 znajdziesz zapis onClick="return false;". Dzięki temu dokument wskazany w atrybucie HREF nie zostanie załadowany. Należy pamiętać, że nadal jesteśmy w pętli for. Powyższy proces ma miejsce dla każdej odpowiedzi, od 0 do howMany - 1. Kiedy pętla for zakończy swoje działanie, zmienna results jest długim napisem, zawierającym liczbę prawidłowych odpowiedzi, liczbę wszystkich pytań, tekst pytań z możliwymi odpowiedziami i wyborami zrobionymi przez użytkownika oraz informacją o prawidłowości jego odpowiedzi. W wierszach 107–109 dodajemy jeszcze końcowe znaczniki HTML, ładujemy całość do dolnej ramki i zamykamy funkcję: results += '\n\r'; qFrame.location.replace("javascript: parent.frames[0].results"); }
59 Rozdział 2 - Test sprawdzany na bieżąco chickenOut() Istnieje jeszcze jedna drobna kwestia: co się stanie, jeśli użytkownik zawczasu zakończy test. Oczywiście można by się tym nie przejmować, warto jednak dodać tę funkcję właśnie po to, aby aplikację rozszerzyć. Oto kod wierszy 60 do 65: function chickenOut() { if(stopOK && confirm('Masz już dość? Tchórzysz?')) { gradeTest(); } }
Jeśli użytkownik potwierdzi rezygnację, wywoływana jest gradeTest(). Pamiętajmy, że użytkownik może się wycofać po odpowiedzeniu na przynajmniej jedno pytanie. Zmienna stopOK początkowo ustawiana jest na false, a na true wtedy, kiedy qIdx ma wartość większą od 1 – wiersz 53. Chodzi o to, że gradeTest() porównuje odpowiedzi z pytaniami, nawet jeśli użytkownik na nie nie odpowiedział. Można by pokusić się o stwierdzenie, że czyni to straszliwe spustoszenie w morale testowanego, ale taka jest cena tchórzostwa.
Kierunki rozwoju Aplikację tę można modyfikować na wiele sposobów. Dwa narzucające się rozszerzenia to zabezpieczenie przed oszustwami przez stworzenie serwera oceniającego oraz zmiana aplikacji na badanie ankietowe.
Uodpornienie na oszustwa Jedną z pierwszych myśli po zapoznaniu się z aplikacją może być obawa, że użytkownik sprawdzi odpowiedzi. Wyszukiwanie odpowiedzi może okazać się trudne, otwierając plik źródłowy JavaScript, ale da się to zrobić. Możemy to zagrożenie usunąć, jeśli po prostu nie będziemy wysyłać wraz z aplikacją odpowiedzi, ale zażądamy od użytkownika przekazania sobie jego odpowiedzi. Nie będziemy się tutaj dokładnie zajmować serwerem oceniającym, ale rzecz nie będzie trudniejsza od funkcji gradeTest(). Może trzeba uwzględnić trochę więcej zagadnień, ale zasady pozostają bez zmian. Aby usunąć odpowiedzi z aplikacji i dodać przesyłanie odpowiedzi użytkownika, należy: •
Usunąć z obiektów i tablicy wszelkie dane zawierające prawidłowe odpowiedzi w questions.js.
•
Usunąć funkcję gradeTest() i zamienić jej wywołanie w buildQuestion() wraz z printResults().
•
Zmodyfikować printResults() tak, aby użytkownik mógł obejrzeć swoje odpowiedzi i umożliwić przesyłanie ich w postaci HTML do oczekującego serwera.
Usuwanie odpowiedzi z tablicy Usuń z konstruktora pytań w question.js this.answer oraz this.support. Zmień poniższy zapis: function question(answer, support, question, a, b, c, d) { this.answer = answer; this.support = support; this.question = question; this.a = a; this.b = b; this.c = c; this.d = d; return this; }
na następujący: function question(question, a, b, c, d) { this.question = question; this.a = a; this.b = b; this.c = c; this.d = d; return this; }
60 warto zwrócić uwagę, że usunięto też zmienne answer i support. Teraz, kiedy usunąłeś je z konstruktora, można pozbyć się ich ze wszystkich wywołań operatora new dla każdego elementu units. Innymi słowy, z każdego elementu units należy usunąć pierwsze dwa parametry.
Usuwanie gradeTest() i modyfikacja buildQuestion() Jako że nie ma już odpowiedzi ani wyjaśnień, nie ma powodu lokalnie oceniać testu czy wyświetlać jego wyników. Oznacza to, że możesz pozbyć się funkcji gradeTest(). Po prostu w pliku administer.html należy usunąć wiersze 66 do 76. Można też pozbyć się wywołania gradeTest() w buildQuestion() w wierszu 40. Warto zastąpić to wywołaniem printResults(), aby użytkownik mógł zobaczyć swoje odpowiedzi w postaci HTML. Wiersze 39 do 42 zmień z poniższej wartości: if (qIdx == howMany) { gradeTest(); return; }
na: if (qIdx == howMany) { printResults(); return; }
Modyfikacja printResults() Największe zmiany czekają nas właśnie w funkcji printResults(). Wiersz 84 w administer.html, wyglądający obecnie tak: '
Zastąpmy też wierze 92 do 105 w sposób następujący: results += 'Wybrałeś ' + keeper[i] + '
';
W rezultacie usuwamy z funkcji decyzję, czy użytkownik odpowiedział prawidłowo, i wyświetlanie tekstu zielonego lub czerwonego. W końcu wiersz 107 wyglądający tak: results += '\n\r