Jakiś czas temu na GoldenLine wywiązała się ciekawa dyskusja na temat tego, w jaki sposób powinno się dołączać zdarzenia do strony i rozdzielać różne warstwy w typowej aplikacji internetowej.
Było tam co nieco na temat korzystania z różnego rodzaju bibliotek, włączaniu kodu JavaScript bezpośrednio do HTML, mieszaniu logiki z prezencją i inne. W notce tej chciałbym napisać o swoim stanowisku w tej sprawie.
Otóż jestem zwolennikiem tworzenia semantycznego kodu HTML. Oznacza to mniej więcej tyle, że staram się rozdzielać kod JavaScript, CSS oraz HTML.
Dzięki temu mogę budować strony w trzech etapach:
-
Najpierw tworzę strukturę dokumentu HTML opartą na DIV, menu na listach UL/OL, dane tabelaryczne przedstawiam w tabelach, itp. – tak by strona była stworzona w sposób semantyczny i dostępny dla różnych urządzeń i osób z różnymi schorzeniami.
1
2
3
4<ul class="panel_article">
<li>Wersja do druku</li>
<li>Poprzednia strona</li>
</ul> -
Po utworzeniu głównej struktury dokumentu zabieram się za wygląd strony – tutaj staram się jak najwięcej rzeczy delegować do zewnętrznych plików ze stylami CSS, które odpowiadają za prezencję strony.
Często wymaga to poprawiania wcześniej stworzonej struktury dokumentu HTML – dodawanie identyfikatorów (atrybut id), przypisywanie klas CSS (atrybut class) i inne.
Na szczęście wraz z rozwojem CSS3 i rozbudową przeglądarek w tym zakresie, coraz częściej mogę stosować selektory CSS, dzięki czemu dużo prościej manipulować dokumentem HTML, jak i dostać się do najbardziej zagnieżdżonych elementów.
1
2
3
4
5
6
7
8
9
10
11
12ul.panel_article, ul.panel_article li {
list-style-type: none;
display: inline;
}
ul.panel_article li {
margin-right: 2em;
}
a[href $= '.pdf']:after {
content: ' (PDF)';
} - Na sam koniec, gdy cała aplikacja działa na większości popularnych przeglądarek, zabieram się za skryte dołączenie skryptów JavaScript (metoda ta nazywa się unobtrusively JavaScript).
Tak więc do dokumentu dopisuję znacznik SCRIPTS, dołączając tym samym zewnętrzne pliki z kodem JavaScript. Znacznik ten umieszczam na samym dole dokumentu HTML, tuż przed zamknięciem elementu BODY (ponieważ wtedy pozostały kod jest już wczytany przez przeglądarkę i mogę spokojnie operować na drzewie DOM – jak i wczytywanie skryptów nie koliduje z wczytywaniem dokumentu, a następuje na sam koniec).
1
2
3
4$("a[class~='back']").click(function(e) {
history.back();
e.preventDefault();
});
Może i pracochłonna jest taka technika, jednak daje bardzo dobre rezultaty. Sprawia bowiem, iż strona jest dostępna także dla osób z wyłączoną obsługą JavaScript (Kto normalny wyłącza JavaScript?!).
Strategia takiego tworzenia aplikacji określana jest jako Progressive Enhancement. Również udziela się tutaj idea zwana Graceful degradation, ponieważ nawet w przypadku nie załadowania przez przeglądarkę części aplikacji (np. skryptów JS) strona ta będzie nadal dostępna i możliwa do przeglądania i użytkowania!
Standardy W3C, WCAG i inne
O tym, że standardy tworzenia aplikacji są niezwykle ważne wie chyba każdy. No może nie każdy, dlatego krótko napiszę o istocie ich stosowania.
Tworząc aplikację zgodną ze standardami nie musimy się obawiać, że strona którą napiszemy dzisiaj, nie będzie za pół roku obsługiwana w nowoczesnych przeglądarkach.
Nie musimy się także obawiać złej indeksacji przez roboty wyszukiwarek, martwić osobami niepełnosprawnymi próbującymi uzyskać dostęp do naszej aplikacji, czytnikami stron, mobilnymi przeglądarkami, etc.
Oczywiście to wszystko w dużym uproszczeniu. Aby strona była dobrze zoptymalizowana pod wyszukiwarki należy poczynić znacznie więcej niż samo zadbanie o standardy, jednak Ty jako Webdeveloper nie powinieneś się tym zbytnio martwić. Ważne, że stosujesz nagłówki Hx, element title, meta tagi czy alt dla obrazków.
To samo dla przeglądarek tekstowych, czytników tekstu, osób upośledzonych, etc. Tworząc stronę zgodną ze standardami i semantyczną, wykorzystując odpowiednie elementy języka HTML w odpowiednich miejscach i z ich przeznaczeniem sprawiasz, że Twoje strony są dla większości osób dostępne w sposób zadowalający, przez wyszukiwarki premiowane oraz szybciej renderowane przez przeglądarki.
Wykorzystanie zewnętrznych bibliotek
Uwielbiam korzystać z jQuery. Wspaniały framework, niesamowicie ułatwia pracę i sprawia, że budowanie aplikacji jest naprawdę przyjemne – eliminuje ułomności przeglądarek w zakresie modelu DOM, zarządzania zdarzeniami, upraszcza do minimum wykorzystanie Ajaksa, poruszanie się po modelu DOM (traversing). Można by tak wypisywać naprawdę długo :-)
Niemniej jednak, każda taka biblioteka to kolejnych kilkadziesiąt KB dla naszej strony, które każdy klient musi niestety pobrać. Nawet skompresowany kod takiej biblioteki waży całkiem sporo, co powoduje dwa problemy:
- serwer zużywa więcej transferu,
- klient musi dłużej czekać na pełne załadowanie strony.
W dobie łącz szerokopasmowych i serwerów o wręcz gigantycznym transferze to nie powinien być problem, niby. Jednak dla osób korzystających z Internetu mobilnego czy łącz modemowych to problem. Sam wliczam się do tej grupy osób (Simplus -> 50MB za 5zł), ponadto w telefonie nie mam karty pamięci, przez co biblioteka nie może być zbuforowana i trochę zżera mi to transferu.
Dlatego zewnętrzne biblioteki, mimo iż tak wspaniałe i pomocne, staram się wykorzystywać tylko wtedy kiedy muszę – nie przy każdym możliwym problemie! Czasem warto poświęcić kilkanaście minut i napisać własną klasę do tworzenia obiektów Ajaksa (XHR), niż specjalnie zaprzęgać do tego jQuery, Prototype i inne wynalazki.
Sposób wczytywania skryptów JavaScript
Jak już wcześniej wspomniałem, dobrą praktyką jest zamieszczanie elementów SCRIPT przy samym końcu dokumentu HTML, gdyż wtedy mamy pewność, iż załadowano pełne drzewo DOM dokumentu przez przeglądarkę.
Przy wykorzystaniu zewnętrznych bibliotek, takich jak jQuery możemy dołączyć zewnętrzne skrypty JS na samym początku dokumentu, lub nawet w deklaracji HEAD (co nie jest zalecane), a następnie wykorzystać metodę ready biblioteki do wywołania funkcjonalności JavaScript dopiero po pełnym załadowaniu drzewa DOM:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | var MyUI = { start: function() { this.zrobCos(); this.zrobCosJeszczeInnego(); }, zrobCos: function() { }, zrobCosJeszczeInnego: function() { } }; $(document).ready(function() { MyUI.start() }); |
Podsumowanie
Choć rozwijam się głównie w kierunku back-end developera i działam po stronie serwera, to nie jest mi obca praca front-end’owca.
Oczywiście specjalistą w tym zakresie nie jestem i mam jedynie podstawowe doświadczenie, dlatego chętnie dowiem się jakie techniki budowania aplikacji po stronie klienta stosują inni developerzy :-)
Wiedza taka niezmiernie mi się przyda, gdyż od czasu do czasu wykonuję zlecenia i pracuję na zasadach freelance, a wtedy zamawiam jedynie grafikę, a resztą zajmuję się samodzielnie. Tak więc zapraszam do dzielenia się własnym doświadczeniem.
Pojęcie „unobtrusively JavaScript” rozumuję zupełnie inaczej – mniej więcej tak: jak nie ma JS’ów wszystko działa tak jak powinno, choć nie wygląda tak ładnie i z bajerem.
Nie mniej jednak. Sam proces tworzenia u mnie wygląda dość podobnie – z tą różnicą, że zaczynam od ogólnych elementów od razu je opisując w CSS’ie i idę w bardziej szczegółowe elementy.
Co do skryptów na końcu – jest taka fama. Ja się do niej nie stosuję. Głównie dlatego, że lwia część skryptów, które pisałem/używam (a że używam praktycznie tego co sam napiszę :D) – może z powodzeniem rozpocząć działanie po wgraniu DOM’u, a przed wgraniem pozostałych elementów (tj. obrazków). Szczególnie że Prototype.js umożliwia start po wgraniu DOM’u i po wgraniu całości.
Heh… selektory CSS3 są fantastyczne, przydają się przy chodzeniu po DOM, ale w praktyce – jeszcze za wcześnie na nie. IE6 wbrew pozorom wciąż odgrywa znaczną rolę w niektórych branżach.
Co do przystosowywania stron do „mobilków” – najczęściej odchudzam layout o zbędne wodotryski – a tym samym, eliminuję skrypty. No i dochodzi kilka zmian w CSS’ie umożliwiające pracę na ekranikach różnej wielkości.
Najczęściej – jest to lekko zmieniony bazowy CSS.
Aby strona była dobrze zoptymalizowana pod wyszukiwarki należy poczynić znacznie więcej niż samo zadbanie o standardy, jednak Ty jako Webdeveloper nie powinieneś się tym zbytnio martwić
A teraz wyskakuj mi na maila ze swojej wiedzy :D
Dam ci w zamian pomacać najnowszą wersję frameworka :)
U mnie na szczęście sprawa z IE6 nie jest tak tragiczna – na większości stron osób użytkujących tego dinozaura jest poniżej 5%. Niemniej jednak staram się tworzyć strony tak, by odwiedzający przez Internet Explorer 6 widział co nieco, a przynajmniej mógł odczytać treści.
Później odezwę się na maila – z chęcią zobaczę co tam nowego w fw ;)
(metoda ta nazywa się unobtrusively JavaScript)
Tą metodę nazywa się dokładnie “Unobtrusive JavaScript” i jej tłumaczenie – znacznie lepiej oddające naturę tej techniki – to “Nieinwazyjny JavaScript”.
Jako ciekawostkę, leciutko powiązaną z tematem notki, dodam, że Jeff Atwood – każdy programista-inżynier powinien znać tego blogera – przyrównał kiedyś budowę stron internetowych do MVC (HTML = Model, CSS = Widok, JavaScript = Kontroler).
P.S. Kamilu, zdublowałem poprzedni komentarz – skasuj pierwszy z nich, bo zrobiłem w nim literówkę. Dzięki.
Alan, dziękuję za komentarz uzupełniający wpis o nowe informacje :-) Słyszałem o koncepcji budowania front-endu na wzór MVC – gdzieś w jakiejś książce wyczytałem, nie pamiętam gdzie.
Sam staram się do tego stosować i rozdzielam poszczególne warstwy, ładując kod najlepiej do osobnych plików.
Jest jeszcze jeden sposób na nieinwazyjny js, oparty o MVC. Jak pewnie wiesz każdy event bąbelkuje – przepływa od miejsca gdzie został odpalony na szczyt drzewa dom. Wystarczy go tam złapać i obsłużyć w zależności od tego skąd się wziął i jakiego jest typu. Taki globalny event handler.
@sokzzuka: wiem o czym mowa, jednak w dobie frameworków nie chciałoby mi się pisać mechanizmu do przechwytywania zdarzeń, jestem na to za leniwy.
Zamiast tego wolę z wykorzystaniem jQuery przypinać zdarzenia do określonych elementów, wykonywać konkretne akcje, po czym blokować dalsze działania przeglądarki (preventDefault).
W prototype.js nie ma problemu z właśnie takim przechwytywaniem zdarzeń.
Ot po prostu zamiast
pisze się
Zaś w funkcji reagującej na zdarzenie:
2
3
4
5
if(target) {
evt.stop();
...
}
:) Czyżby kolejna przewaga nad jBadziew? :D
No i zapomniał bym o dość ważnym efekcie bombelkowania – dzięki niemu, można sprawdzać czy zdarzenie, wywołane przez dany element pochodzi z danego kontenera czy nie i zależności od wyniku – odpowiednio obsłużyć.
Np. w sytuacji gdy mam kilkanaście kontenerów
2
3
4
....
<a href="#" rel="remove">Usuń</a>
</div>
Przy kliknięciu na link rel=”remove” nie szukam linka wywołującego zdarzenie, a kontenera w którym się znajduje.
No i, obsługę zdarzenia daję na dokument, nie na każdy kontener z osobna :)
Ale jestem głupi, wcześniej coś mi się pomyliło :D jQuery bowiem posiada metodę do takiego zarządzania zdarzeniami – live. Jakby tego było mało, czasem używam tej metody i nie jest mi obca.
„Czasem warto poświęcić kilkanaście minut i napisać własną klasę do tworzenia obiektów Ajaksa (XHR), niż specjalnie zaprzęgać do tego jQuery, Prototype i inne wynalazki.”
Narzekasz też wcześniej na ilość danych jakie trzeba będzie pobrać – zapomniałeś o jednym – CDN. Umieszczając na stronie linki zamiast do siebie, do serwisów hostujących te pliki masz bardzo dużą szansę, że ktoś wczyta skrypt z cache, a ty masz nieporównywalnie większe możliwości niż w przypadku własnego rozwiązania.
@Tomasz: generalnie się z tym zgodzę, teraz tak wielu developerów używa bibliotek z CDN, że większość użytkowników ma pewnie dany obiekt w buforze (przynajmniej te popularne, jak jquery). A nawet jeśli nie to pobranie pliku z serwerów krawędziowych CDN jest dużo szybsze niż z normalnego serwera.
Niemniej jednak nie wszędzie można dawać jquery, czasem lepiej zastosować kilka prostych funkcji. Mowa tutaj chociażby o telefonach – nie ma tam miejsca na cache, a każdy kolejny MB kosztuje (Simplus = 5zł za 50 MB)
No tak, tylko że komórkowe przeglądarki za bardzo z tego nie skorzystają. ;)
Poza tym, popadamy tu w skrajność – zajdzie potrzeba ulepszenia serwisu, to i tak przyjdzie doczepić kolejnego frameworka. Czysty JS owszem, ale do czasu. Jeśli liczba linijek zaczyna przekraczać pewne dopuszczalne maksimum, olewam klepanie koła na nowo.
W PHP jest sens nieraz napisania wszystkiego na czysto, bez fw, ale we front-endzie już nie. Chociażby ze względu na różnice w implementacji pewnych funkcji przez przeglądarki.
eRIZ, dziękuję za komentarz :-)
W zdaniu, które zacytowałeś chodziło mi właśnie o sytuację, gdy potrzebujemy tylko minimalnej obsługi JavaScript, a w związku z tym nie ma co zaprzęgać do tego frameworków. Niektóre rzeczy są stosunkowo proste w wykonaniu i warto zwolnić trochę łącza. Oczywiście w przypadku konieczności zaimplementowania szerszej funkcjonalności w JS to framework jest wręcz niezbędny, by nie wymyślać koła na nowo oraz by skrócić czas pracy.
Nie zgodzę się jednak co do telefonów. Opera Mini obsługuje JavaScript, a przynajmniej niewielką część funkcjonalności. Przykładowo: zdarzenie onfocus dodane do pola input zadziała na telefonie, jednak będzie wymagało odświeżenia strony. Jest to dość uporczywe i męczące (dużo serwisów dodaje zdarzenia na polach formularzy, menu rozwijanych), jednak działa.
Też korzystam z OMini, ale to, co przytoczyłeś, to jest praktycznie… bezużyteczne? ;)
Bezużyteczne i wkurzające, jednak potwierdza, że JavaScript częściowo działa w przeglądarkach mobilnych. Więc gdyby ktoś budował prostą stronę na telefon to mógłby użyć JavaScript, ale nie polecałbym dołączanie biblioteki do jedynie kilkukrotnego wykorzystania metody bind czy live.
Czasem warto się pomęczyć trochę więcej, napisać prostą obsługę zdarzeń w „czystym” JS i nie katować klienta biblioteką ważącą prawie 50 KB ;>
Btw. obsługa zdarzeń jest tutaj przykładem, to tyczy się każdej funkcjonalności. Tak jak jednak napisałeś wcześniej, wszystko z umiarem – jeśli tej funkcjonalności jest za dużo i dochodzi operowanie na modelu DOM czy coś jeszcze to dopiero wtedy warto zastanowić się nad użyciem frameworka :D